diff --git a/docs/schema/V1/schema.verified.graphql b/docs/schema/V1/schema.verified.graphql index 76293900a..0430b46c2 100644 --- a/docs/schema/V1/schema.verified.graphql +++ b/docs/schema/V1/schema.verified.graphql @@ -1,5 +1,6 @@ schema { query: Queries + mutation: Mutations subscription: Subscriptions } @@ -11,6 +12,10 @@ interface SearchDialogError { message: String! } +interface SetSystemLabelError { + message: String! +} + type Activity { id: UUID! createdAt: DateTime @@ -155,6 +160,10 @@ type Localization { languageCode: String! } +type Mutations { + setSystemLabel(input: SetSystemLabelInput!): SetSystemLabelPayload! +} + type Queries @authorize(policy: "enduser") { dialogById(dialogId: UUID!): DialogByIdPayload! searchDialogs(input: SearchDialogInput!): SearchDialogsPayload! @@ -211,6 +220,15 @@ type SeenLog { isCurrentEndUser: Boolean! } +type SetSystemLabelEntityNotFound implements SetSystemLabelError { + message: String! +} + +type SetSystemLabelPayload { + success: Boolean! + errors: [SetSystemLabelError!]! +} + type Subscriptions { "Requires a dialog token in the 'DigDir-Dialog-Token' header." dialogEvents(dialogId: UUID!): DialogEventPayload! @authorize(policy: "enduserSubscription", apply: VALIDATION) @@ -265,6 +283,11 @@ input SearchDialogInput { searchLanguageCode: String } +input SetSystemLabelInput { + dialogId: UUID! + label: SystemLabel! +} + enum ActivityType { "Refers to a dialog that has been created." DIALOG_CREATED @@ -336,6 +359,12 @@ enum HttpVerb { CONNECT } +enum SystemLabel { + DEFAULT + BIN + ARCHIVE +} + enum TransmissionType { "For general information, not related to any submissions" INFORMATION diff --git a/docs/schema/V1/swagger.verified.json b/docs/schema/V1/swagger.verified.json index 2af8134f1..85147c0a5 100644 --- a/docs/schema/V1/swagger.verified.json +++ b/docs/schema/V1/swagger.verified.json @@ -261,6 +261,15 @@ } ] }, + "systemLabel": { + "description": "Set the system label of the dialog Migration purposes ", + "nullable": true, + "oneOf": [ + { + "$ref": "#/components/schemas/SystemLabel_Values" + } + ] + }, "transmissions": { "description": "The immutable list of transmissions associated with the dialog", "items": { @@ -2093,6 +2102,14 @@ } ] }, + "systemLabel": { + "description": "Current display state.", + "oneOf": [ + { + "$ref": "#/components/schemas/SystemLabel_Values" + } + ] + }, "transmissions": { "description": "The immutable list of transmissions associated with the dialog", "items": { @@ -2254,6 +2271,14 @@ } ] }, + "systemLabel": { + "description": "Current display state.", + "oneOf": [ + { + "$ref": "#/components/schemas/SystemLabel_Values" + } + ] + }, "transmissions": { "description": "The immutable list of transmissions associated with the dialog", "items": { @@ -2551,6 +2576,18 @@ }, "type": "object" }, + "LabelAssignmentLogActorDto": { + "additionalProperties": false, + "properties": { + "actorId": { + "type": "string" + }, + "actorName": { + "type": "string" + } + }, + "type": "object" + }, "LocalizationDto": { "additionalProperties": false, "properties": { @@ -3180,6 +3217,14 @@ } ] }, + "systemLabel": { + "description": "Current display state.", + "oneOf": [ + { + "$ref": "#/components/schemas/SystemLabel_Values" + } + ] + }, "updatedAt": { "description": "The date and time when the dialog was last updated.", "example": "2022-12-31T23:59:59Z", @@ -3295,6 +3340,14 @@ } ] }, + "systemLabel": { + "description": "Current display state.", + "oneOf": [ + { + "$ref": "#/components/schemas/SystemLabel_Values" + } + ] + }, "updatedAt": { "description": "The date and time when the dialog was last updated.", "example": "2022-12-31T23:59:59Z", @@ -3310,6 +3363,25 @@ }, "type": "object" }, + "SearchDialogLabelAssignmentLogDto": { + "additionalProperties": false, + "properties": { + "action": { + "type": "string" + }, + "createdAt": { + "format": "date-time", + "type": "string" + }, + "name": { + "type": "string" + }, + "performedBy": { + "$ref": "#/components/schemas/LabelAssignmentLogActorDto" + } + }, + "type": "object" + }, "SearchDialogSeenLogDto": { "additionalProperties": false, "properties": { @@ -3386,6 +3458,34 @@ }, "type": "object" }, + "SetDialogSystemLabelCommand": { + "additionalProperties": false, + "properties": { + "ifMatchDialogRevision": { + "format": "guid", + "nullable": true, + "type": "string" + }, + "label": { + "$ref": "#/components/schemas/SystemLabel_Values" + } + }, + "type": "object" + }, + "SystemLabel_Values": { + "description": "", + "enum": [ + "Default", + "Bin", + "Archive" + ], + "type": "string", + "x-enumNames": [ + "Default", + "Bin", + "Archive" + ] + }, "UpdateDialogContentDto": { "additionalProperties": false, "properties": { @@ -4237,6 +4337,20 @@ "type": "string" } }, + { + "description": "Filter by Display state ", + "explode": true, + "in": "query", + "name": "systemLabel", + "schema": { + "items": { + "$ref": "#/components/schemas/SystemLabel_Values" + }, + "nullable": true, + "type": "array" + }, + "style": "form" + }, { "description": "Search string for free text search. Will attempt to fuzzily match in all free text fields in the aggregate", "in": "query", @@ -4472,6 +4586,64 @@ ] } }, + "/api/v1/enduser/dialogs/{dialogId}/labellog": { + "get": { + "operationId": "SearchDialogLabelAssignmentLog", + "parameters": [ + { + "in": "path", + "name": "dialogId", + "required": true, + "schema": { + "format": "guid", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/SearchDialogLabelAssignmentLogDto" + }, + "type": "array" + } + } + }, + "description": "Success" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "content": { + "application/problem\u002Bjson": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + }, + "description": "Not Found" + }, + "410": { + "description": "" + } + }, + "security": [ + { + "JWTBearerAuth": [] + } + ], + "tags": [ + "Enduser" + ] + } + }, "/api/v1/enduser/dialogs/{dialogId}/seenlog": { "get": { "description": "Gets all seen log records for a dialog. For more information see the documentation (link TBD).", @@ -4592,6 +4764,97 @@ ] } }, + "/api/v1/enduser/dialogs/{dialogId}/systemlabels": { + "put": { + "operationId": "SetDialogLabel", + "parameters": [ + { + "in": "path", + "name": "dialogId", + "required": true, + "schema": { + "format": "guid", + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SetDialogSystemLabelCommand" + } + } + }, + "description": "", + "required": true, + "x-name": "SetDialogSystemLabelCommand", + "x-position": 1 + }, + "responses": { + "204": { + "description": "No Content" + }, + "400": { + "content": { + "application/problem\u002Bjson": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + }, + "description": "Bad Request" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "content": { + "application/problem\u002Bjson": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + }, + "description": "Not Found" + }, + "410": { + "description": "" + }, + "412": { + "content": { + "application/problem\u002Bjson": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + }, + "description": "" + }, + "422": { + "content": { + "application/problem\u002Bjson": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + }, + "description": "" + } + }, + "security": [ + { + "JWTBearerAuth": [] + } + ], + "tags": [ + "Enduser" + ] + } + }, "/api/v1/enduser/dialogs/{dialogId}/transmissions": { "get": { "description": "Gets the list of transmissions belonging to a dialog", @@ -4897,6 +5160,20 @@ "type": "string" } }, + { + "description": "Filter by Display state ", + "explode": true, + "in": "query", + "name": "systemLabel", + "schema": { + "items": { + "$ref": "#/components/schemas/SystemLabel_Values" + }, + "nullable": true, + "type": "array" + }, + "style": "form" + }, { "description": "Search string for free text search. Will attempt to fuzzily match in all free text fields in the aggregate", "in": "query", diff --git a/src/Digdir.Domain.Dialogporten.Application/Externals/IDialogDbContext.cs b/src/Digdir.Domain.Dialogporten.Application/Externals/IDialogDbContext.cs index 178824cf5..98afe9270 100644 --- a/src/Digdir.Domain.Dialogporten.Application/Externals/IDialogDbContext.cs +++ b/src/Digdir.Domain.Dialogporten.Application/Externals/IDialogDbContext.cs @@ -5,6 +5,7 @@ using Digdir.Library.Entity.Abstractions.Features.Identifiable; using Microsoft.EntityFrameworkCore; using System.Linq.Expressions; +using Digdir.Domain.Dialogporten.Domain.DialogEndUserContexts.Entities; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Contents; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Transmissions; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Transmissions.Contents; @@ -42,6 +43,8 @@ public interface IDialogDbContext DbSet OutboxMessages { get; } DbSet OutboxMessageConsumers { get; } DbSet SubjectResources { get; } + DbSet DialogEndUserContexts { get; } + DbSet LabelAssignmentLogs { get; } /// /// Validate a property on the using a lambda diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogLabelAssignmentLog/Queries/Search/MappingProfile.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogLabelAssignmentLog/Queries/Search/MappingProfile.cs new file mode 100644 index 000000000..12c4780e4 --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogLabelAssignmentLog/Queries/Search/MappingProfile.cs @@ -0,0 +1,13 @@ +using AutoMapper; +using Digdir.Domain.Dialogporten.Domain.DialogEndUserContexts.Entities; + +namespace Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.DialogLabelAssignmentLog.Queries.Search; + +public sealed class MappingProfile : Profile +{ + public MappingProfile() + { + CreateMap(); + CreateMap(); + } +} diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogLabelAssignmentLog/Queries/Search/SearchDialogLabelAssignmentLogDto.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogLabelAssignmentLog/Queries/Search/SearchDialogLabelAssignmentLogDto.cs new file mode 100644 index 000000000..00b0ae9ec --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogLabelAssignmentLog/Queries/Search/SearchDialogLabelAssignmentLogDto.cs @@ -0,0 +1,21 @@ +namespace Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.DialogLabelAssignmentLog.Queries.Search; + +public sealed class SearchDialogLabelAssignmentLogDto +{ + public DateTimeOffset CreatedAt { get; set; } + + public string Name { get; set; } = null!; + + public string Action { get; set; } = null!; + + public LabelAssignmentLogActorDto PerformedBy { get; set; } = null!; + +} + +public sealed class LabelAssignmentLogActorDto +{ + + public string ActorName { get; set; } = null!; + + public string ActorId { get; set; } = null!; +} diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogLabelAssignmentLog/Queries/Search/SearchDialogLabelAssignmentLogQuery.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogLabelAssignmentLog/Queries/Search/SearchDialogLabelAssignmentLogQuery.cs new file mode 100644 index 000000000..0959ba117 --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogLabelAssignmentLog/Queries/Search/SearchDialogLabelAssignmentLogQuery.cs @@ -0,0 +1,61 @@ +using AutoMapper; +using Digdir.Domain.Dialogporten.Application.Common; +using Digdir.Domain.Dialogporten.Application.Common.ReturnTypes; +using Digdir.Domain.Dialogporten.Application.Externals; +using Digdir.Domain.Dialogporten.Application.Externals.AltinnAuthorization; +using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities; +using MediatR; +using Microsoft.EntityFrameworkCore; +using OneOf; + +namespace Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.DialogLabelAssignmentLog.Queries.Search; + +public sealed class SearchDialogLabelAssignmentLogQuery : IRequest +{ + public Guid DialogId { get; set; } +} + +[GenerateOneOf] +public sealed partial class SearchDialogLabelAssignmentLogResult : OneOfBase, EntityNotFound, EntityDeleted>; + +internal sealed class SearchDialogLabelAssignmentLogQueryHandler : IRequestHandler +{ + private readonly IDialogDbContext _dialogDbContext; + private readonly IMapper _mapper; + private readonly IAltinnAuthorization _altinnAuthorization; + + public SearchDialogLabelAssignmentLogQueryHandler(IDialogDbContext dialogDbContext, IMapper mapper, IAltinnAuthorization altinnAuthorization) + { + _dialogDbContext = dialogDbContext ?? throw new ArgumentNullException(nameof(dialogDbContext)); + _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper)); + _altinnAuthorization = altinnAuthorization ?? throw new ArgumentNullException(nameof(altinnAuthorization)); + } + + public async Task Handle(SearchDialogLabelAssignmentLogQuery request, CancellationToken cancellationToken) + { + var dialog = await _dialogDbContext.Dialogs + .AsNoTracking() + .Include(x => x.DialogEndUserContext) + .ThenInclude(x => x.LabelAssignmentLogs) + .ThenInclude(x => x.PerformedBy) + .FirstOrDefaultAsync(x => x.Id == request.DialogId, cancellationToken: cancellationToken); + + if (dialog == null) + { + return new EntityNotFound(request.DialogId); + } + + var authorizationResult = await _altinnAuthorization.GetDialogDetailsAuthorization(dialog, cancellationToken: cancellationToken); + if (!authorizationResult.HasAccessToMainResource()) + { + return new EntityNotFound(request.DialogId); + } + + if (dialog.Deleted) + { + return new EntityDeleted(request.DialogId); + } + + return _mapper.Map>(dialog.DialogEndUserContext.LabelAssignmentLogs); + } +} diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogSystemLabels/Commands/Set/SetDialogSystemLabelCommand.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogSystemLabels/Commands/Set/SetDialogSystemLabelCommand.cs new file mode 100644 index 000000000..71edd6831 --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogSystemLabels/Commands/Set/SetDialogSystemLabelCommand.cs @@ -0,0 +1,72 @@ +using Digdir.Domain.Dialogporten.Application.Common; +using Digdir.Domain.Dialogporten.Application.Common.ReturnTypes; +using Digdir.Domain.Dialogporten.Application.Externals; +using Digdir.Domain.Dialogporten.Application.Externals.AltinnAuthorization; +using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities; +using MediatR; +using Microsoft.EntityFrameworkCore; +using OneOf; +using OneOf.Types; + +namespace Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.DialogSystemLabels.Commands.Set; + +public sealed class SetDialogSystemLabelCommand : SetDialogSystemLabelDto, IRequest +{ + public Guid? IfMatchDialogRevision { get; set; } +} + +[GenerateOneOf] +public sealed partial class SetDialogSystemLabelResult : OneOfBase; + +internal sealed class SetDialogSystemLabelCommandHandler : IRequestHandler +{ + private readonly IDialogDbContext _db; + private readonly IUnitOfWork _unitOfWork; + private readonly IUserRegistry _userRegistry; + private readonly IAltinnAuthorization _altinnAuthorization; + + public SetDialogSystemLabelCommandHandler(IDialogDbContext db, IUnitOfWork unitOfWork, IUserRegistry userRegistry, IAltinnAuthorization altinnAuthorization) + { + _db = db ?? throw new ArgumentNullException(nameof(db)); + _unitOfWork = unitOfWork ?? throw new ArgumentNullException(nameof(unitOfWork)); + _userRegistry = userRegistry ?? throw new ArgumentNullException(nameof(userRegistry)); + _altinnAuthorization = altinnAuthorization ?? throw new ArgumentNullException(nameof(altinnAuthorization)); + } + + public async Task Handle( + SetDialogSystemLabelCommand request, + CancellationToken cancellationToken) + { + var dialog = await _db.Dialogs + .Include(x => x.DialogEndUserContext) + .FirstOrDefaultAsync(x => x.Id == request.DialogId, cancellationToken: cancellationToken); + + if (dialog is null) + { + return new EntityNotFound(request.DialogId); + } + + if (dialog.Deleted) + { + return new EntityDeleted(request.DialogId); + } + + var authorizationResult = await _altinnAuthorization.GetDialogDetailsAuthorization(dialog, cancellationToken: cancellationToken); + if (!authorizationResult.HasAccessToMainResource()) + { + return new EntityNotFound(request.DialogId); + } + + var currentUserInformation = await _userRegistry.GetCurrentUserInformation(cancellationToken); + + dialog.DialogEndUserContext.UpdateLabel(request.Label, currentUserInformation.UserId.ExternalIdWithPrefix); + + var saveResult = await _unitOfWork + .EnableConcurrencyCheck(dialog.DialogEndUserContext, request.IfMatchDialogRevision) + .SaveChangesAsync(cancellationToken); + return saveResult.Match( + success => success, + domainError => domainError, + concurrencyError => concurrencyError); + } +} diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogSystemLabels/Commands/Set/SetDialogSystemLabelCommandValidator.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogSystemLabels/Commands/Set/SetDialogSystemLabelCommandValidator.cs new file mode 100644 index 000000000..3596c5267 --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogSystemLabels/Commands/Set/SetDialogSystemLabelCommandValidator.cs @@ -0,0 +1,12 @@ +using FluentValidation; + +namespace Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.DialogSystemLabels.Commands.Set; + +public sealed class SetDialogSystemLabelCommandValidator : AbstractValidator +{ + public SetDialogSystemLabelCommandValidator() + { + RuleFor(x => x.Label) + .NotNull(); + } +} diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogSystemLabels/Commands/Set/SetDialogSystemLabelDto.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogSystemLabels/Commands/Set/SetDialogSystemLabelDto.cs new file mode 100644 index 000000000..8a6a9cac7 --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogSystemLabels/Commands/Set/SetDialogSystemLabelDto.cs @@ -0,0 +1,9 @@ +using Digdir.Domain.Dialogporten.Domain.DialogEndUserContexts.Entities; + +namespace Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.DialogSystemLabels.Commands.Set; + +public class SetDialogSystemLabelDto +{ + public Guid DialogId { get; set; } + public SystemLabel.Values Label { get; set; } +} 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 0d24847f9..fd30852b2 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.Application.Features.V1.Common.Localizations; using Digdir.Domain.Dialogporten.Domain.Actors; using Digdir.Domain.Dialogporten.Domain.Attachments; +using Digdir.Domain.Dialogporten.Domain.DialogEndUserContexts.Entities; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actions; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities; @@ -115,6 +116,10 @@ public sealed class GetDialogDto /// The aggregated status of the dialog. /// public DialogStatus.Values Status { get; set; } + /// + /// Current display state. + /// + public SystemLabel.Values SystemLabel { get; set; } /// /// The dialog unstructured text content 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 7360f0707..e2a9267f5 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 @@ -79,9 +79,10 @@ public async Task Handle(GetDialogQuery request, CancellationTo .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)) + .Where(x => x.CreatedAt >= x.Dialog.UpdatedAt) + .OrderBy(x => x.CreatedAt)) .ThenInclude(x => x.SeenBy) + .Include(x => x.DialogEndUserContext) .Where(x => !x.VisibleFrom.HasValue || x.VisibleFrom < _clock.UtcNowOffset) .IgnoreQueryFilters() .FirstOrDefaultAsync(x => x.Id == request.DialogId, cancellationToken); @@ -152,7 +153,7 @@ private static void DecorateWithAuthorization(GetDialogDto dto, foreach (var apiAction in dto.ApiActions.Where(a => a.Action == action)) { if ((apiAction.AuthorizationAttribute is null && resource == Constants.MainResource) - || (apiAction.AuthorizationAttribute is not null && resource == apiAction.AuthorizationAttribute)) + || (apiAction.AuthorizationAttribute is not null && resource == apiAction.AuthorizationAttribute)) { apiAction.IsAuthorized = true; } @@ -161,7 +162,7 @@ private static void DecorateWithAuthorization(GetDialogDto dto, foreach (var guiAction in dto.GuiActions.Where(a => a.Action == action)) { if ((guiAction.AuthorizationAttribute is null && resource == Constants.MainResource) - || (guiAction.AuthorizationAttribute is not null && resource == guiAction.AuthorizationAttribute)) + || (guiAction.AuthorizationAttribute is not null && resource == guiAction.AuthorizationAttribute)) { guiAction.IsAuthorized = true; } 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 f1bc4258d..50092e856 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 @@ -2,6 +2,7 @@ using Digdir.Domain.Dialogporten.Application.Common; using Digdir.Domain.Dialogporten.Application.Features.V1.Common.Content; using Digdir.Domain.Dialogporten.Domain.Attachments; +using Digdir.Domain.Dialogporten.Domain.DialogEndUserContexts.Entities; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actions; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities; @@ -18,7 +19,8 @@ public MappingProfile() CreateMap() .ForMember(dest => dest.Revision, opt => opt.MapFrom(src => src.Revision)) .ForMember(dest => dest.Status, opt => opt.MapFrom(src => src.StatusId)) - .ForMember(dest => dest.SeenSinceLastUpdate, opt => opt.Ignore()); + .ForMember(dest => dest.SeenSinceLastUpdate, opt => opt.Ignore()) + .ForMember(dest => dest.SystemLabel, opt => opt.MapFrom(src => src.DialogEndUserContext.SystemLabelId)); CreateMap() .ForMember(dest => dest.SeenAt, opt => opt.MapFrom(src => src.CreatedAt)); 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 7c7db38da..800faa7c4 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 @@ -27,7 +27,8 @@ public MappingProfile() .Count(x => x.Urls .Any(url => url.ConsumerTypeId == AttachmentUrlConsumerType.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)); + .ForMember(dest => dest.Status, opt => opt.MapFrom(src => src.StatusId)) + .ForMember(dest => dest.SystemLabel, opt => opt.MapFrom(src => src.DialogEndUserContext.SystemLabelId)); CreateMap() .ForMember(dest => dest.SeenAt, opt => opt.MapFrom(src => src.CreatedAt)); diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/Search/SearchDialogDtoBase.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/Search/SearchDialogDtoBase.cs index b236174bc..359ff6170 100644 --- a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/Search/SearchDialogDtoBase.cs +++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/Search/SearchDialogDtoBase.cs @@ -1,5 +1,6 @@ using Digdir.Domain.Dialogporten.Application.Features.V1.Common.Localizations; using Digdir.Domain.Dialogporten.Domain.Actors; +using Digdir.Domain.Dialogporten.Domain.DialogEndUserContexts.Entities; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities; @@ -93,6 +94,10 @@ public class SearchDialogDtoBase /// public DialogStatus.Values Status { get; set; } + /// + /// Current display state. + /// + public SystemLabel.Values SystemLabel { get; set; } /// /// The latest entry in the dialog's activity log. /// 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 6656aa0e8..590c02a99 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 @@ -8,6 +8,7 @@ using Digdir.Domain.Dialogporten.Application.Common.ReturnTypes; using Digdir.Domain.Dialogporten.Application.Externals; using Digdir.Domain.Dialogporten.Application.Externals.AltinnAuthorization; +using Digdir.Domain.Dialogporten.Domain.DialogEndUserContexts.Entities; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities; using Digdir.Domain.Dialogporten.Domain.Localizations; using MediatR; @@ -85,6 +86,11 @@ public sealed class SearchDialogQuery : SortablePaginationParameter public string? Process { get; init; } + /// + /// Filter by Display state + /// + public List? SystemLabel { get; set; } + /// /// Search string for free text search. Will attempt to fuzzily match in all free text fields in the aggregate /// @@ -170,6 +176,7 @@ public async Task Handle(SearchDialogQuery request, Cancella .WhereIf(request.DueAfter.HasValue, x => request.DueAfter <= x.DueAt) .WhereIf(request.DueBefore.HasValue, x => x.DueAt <= request.DueBefore) .WhereIf(request.Process is not null, x => EF.Functions.ILike(x.Process!, request.Process!)) + .WhereIf(!request.SystemLabel.IsNullOrEmpty(), x => request.SystemLabel!.Contains(x.DialogEndUserContext.SystemLabelId)) .WhereIf(request.Search is not null, x => x.Content.Any(x => x.Value.Localizations.AsQueryable().Any(searchExpression)) || x.SearchTags.Any(x => EF.Functions.ILike(x.Value, request.Search!)) diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/Search/SearchDialogQueryValidator.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/Search/SearchDialogQueryValidator.cs index cd560685a..ecf96f38d 100644 --- a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/Search/SearchDialogQueryValidator.cs +++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/Search/SearchDialogQueryValidator.cs @@ -51,5 +51,6 @@ public SearchDialogQueryValidator() .When(x => x.Process is not null); RuleForEach(x => x.Status).IsInEnum(); + RuleForEach(x => x.SystemLabel).IsInEnum(); } } 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 3093325ef..5801a040d 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 @@ -2,12 +2,17 @@ using AutoMapper; using Digdir.Domain.Dialogporten.Application.Common; using Digdir.Domain.Dialogporten.Application.Common.Authorization; +using Digdir.Domain.Dialogporten.Application.Common.Extensions; using Digdir.Domain.Dialogporten.Application.Common.ReturnTypes; using Digdir.Domain.Dialogporten.Application.Externals; +using Digdir.Domain.Dialogporten.Application.Externals.Presentation; +using Digdir.Domain.Dialogporten.Domain.Actors; using Digdir.Domain.Dialogporten.Domain.Common; +using Digdir.Domain.Dialogporten.Domain.DialogEndUserContexts.Entities; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Transmissions; +using Digdir.Domain.Dialogporten.Domain.Parties; using MediatR; using OneOf; using OneOf.Types; @@ -27,8 +32,10 @@ internal sealed class CreateDialogCommandHandler : IRequestHandler Handle(CreateDialogCommand request, Cancel _domainContext.AddError(new DomainFailure(nameof(DialogEntity.Org), "Cannot find service owner organization shortname for current user. Please ensure that you are logged in as a service owner.")); } - + CreateDialogEndUserContext(request, dialog); await EnsureNoExistingUserDefinedIds(dialog, cancellationToken); await _db.Dialogs.AddAsync(dialog, cancellationToken); var saveResult = await _unitOfWork.SaveChangesAsync(cancellationToken); @@ -71,6 +79,26 @@ public async Task Handle(CreateDialogCommand request, Cancel concurrencyError => throw new UnreachableException("Should never get a concurrency error when creating a new dialog")); } + private void CreateDialogEndUserContext(CreateDialogCommand request, DialogEntity dialog) + { + dialog.DialogEndUserContext = new(); + if (!request.SystemLabel.HasValue) + { + return; + } + + if (!_user.TryGetOrganizationNumber(out var organizationNumber)) + { + _domainContext.AddError(new DomainFailure(nameof(organizationNumber), "Cannot find organization number for current user.")); + return; + } + + dialog.DialogEndUserContext.UpdateLabel( + request.SystemLabel.Value, + $"{NorwegianOrganizationIdentifier.PrefixWithSeparator}{organizationNumber}", + ActorType.Values.ServiceOwner); + } + private async Task EnsureNoExistingUserDefinedIds(DialogEntity dialog, CancellationToken cancellationToken) { var existingDialogIds = await _db.GetExistingIds([dialog], cancellationToken); 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 fc8d2d8d1..3722710c3 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 @@ -2,6 +2,7 @@ using Digdir.Domain.Dialogporten.Application.Features.V1.Common.Localizations; using Digdir.Domain.Dialogporten.Domain.Actors; using Digdir.Domain.Dialogporten.Domain.Attachments; +using Digdir.Domain.Dialogporten.Domain.DialogEndUserContexts.Entities; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actions; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities; @@ -102,6 +103,10 @@ public class CreateDialogDto /// public DialogStatus.Values Status { get; set; } + /// + /// Set the system label of the dialog Migration purposes + /// + public SystemLabel.Values? SystemLabel { get; set; } /// /// The dialog unstructured text content /// diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Update/UpdateDialogCommand.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Update/UpdateDialogCommand.cs index f01e95315..4aaac0441 100644 --- a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Update/UpdateDialogCommand.cs +++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Update/UpdateDialogCommand.cs @@ -1,15 +1,20 @@ using AutoMapper; using Digdir.Domain.Dialogporten.Application.Common; using Digdir.Domain.Dialogporten.Application.Common.Authorization; +using Digdir.Domain.Dialogporten.Application.Common.Extensions; using Digdir.Domain.Dialogporten.Application.Common.Extensions.Enumerables; using Digdir.Domain.Dialogporten.Application.Common.ReturnTypes; using Digdir.Domain.Dialogporten.Application.Externals; -using Digdir.Domain.Dialogporten.Domain.Common; +using Digdir.Domain.Dialogporten.Application.Externals.Presentation; +using Digdir.Domain.Dialogporten.Domain.Actors; using Digdir.Domain.Dialogporten.Domain.Attachments; +using Digdir.Domain.Dialogporten.Domain.Common; +using Digdir.Domain.Dialogporten.Domain.DialogEndUserContexts.Entities; 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.Transmissions; +using Digdir.Domain.Dialogporten.Domain.Parties; using MediatR; using Microsoft.EntityFrameworkCore; using OneOf; @@ -30,6 +35,7 @@ public sealed partial class UpdateDialogResult : OneOfBase { private readonly IDialogDbContext _db; + private readonly IUser _user; private readonly IMapper _mapper; private readonly IUnitOfWork _unitOfWork; private readonly IDomainContext _domainContext; @@ -38,12 +44,14 @@ internal sealed class UpdateDialogCommandHandler : IRequestHandler Handle(UpdateDialogCommand request, Cancel .Include(x => x.ApiActions) .ThenInclude(x => x.Endpoints) .Include(x => x.Transmissions) + .Include(x => x.DialogEndUserContext) .IgnoreQueryFilters() .Where(x => resourceIds.Contains(x.ServiceResource)) .FirstOrDefaultAsync(x => x.Id == request.Id, cancellationToken); @@ -140,6 +149,7 @@ public async Task Handle(UpdateDialogCommand request, Cancel return forbiddenResult; } + UpdateLabel(dialog); var saveResult = await _unitOfWork .EnableConcurrencyCheck(dialog, request.IfMatchDialogRevision) .SaveChangesAsync(cancellationToken); @@ -149,6 +159,20 @@ public async Task Handle(UpdateDialogCommand request, Cancel domainError => domainError, concurrencyError => concurrencyError); } + private void UpdateLabel(DialogEntity dialog) + { + + if (!_user.TryGetOrganizationNumber(out var organizationNumber)) + { + _domainContext.AddError(new DomainFailure(nameof(organizationNumber), "Cannot find organization number for current user.")); + return; + } + + dialog.DialogEndUserContext.UpdateLabel( + SystemLabel.Values.Default, + $"{NorwegianOrganizationIdentifier.PrefixWithSeparator}{organizationNumber}", + ActorType.Values.ServiceOwner); + } private void ValidateTimeFields(DialogEntity dialog) { @@ -318,11 +342,11 @@ private void UpdateApiActions(IEnumerable CreateAttachments(IEnumerable creatables) { return creatables.Select(attachmentDto => - { - var attachment = _mapper.Map(attachmentDto); - attachment.Urls = _mapper.Map>(attachmentDto.Urls); - return attachment; - }); + { + var attachment = _mapper.Map(attachmentDto); + attachment.Urls = _mapper.Map>(attachmentDto.Urls); + return attachment; + }); } private void UpdateAttachments(IEnumerable> updateSets) 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 8182b048d..9cf903e58 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 @@ -2,6 +2,7 @@ using Digdir.Domain.Dialogporten.Application.Features.V1.Common.Localizations; using Digdir.Domain.Dialogporten.Domain.Actors; using Digdir.Domain.Dialogporten.Domain.Attachments; +using Digdir.Domain.Dialogporten.Domain.DialogEndUserContexts.Entities; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actions; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities; 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 462129a65..454dfa9df 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.Application.Features.V1.Common.Localizations; using Digdir.Domain.Dialogporten.Domain.Actors; using Digdir.Domain.Dialogporten.Domain.Attachments; +using Digdir.Domain.Dialogporten.Domain.DialogEndUserContexts.Entities; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actions; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities; @@ -127,6 +128,11 @@ public sealed class GetDialogDto /// public DialogStatus.Values Status { get; set; } + /// + /// Current display state. + /// + public SystemLabel.Values SystemLabel { get; set; } + /// /// The dialog unstructured text content /// 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 51e9e80b6..acb9898e5 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 @@ -83,6 +83,7 @@ public async Task Handle(GetDialogQuery request, CancellationTo .Where(x => x.CreatedAt >= x.Dialog.UpdatedAt) .OrderBy(x => x.CreatedAt)) .ThenInclude(x => x.SeenBy) + .Include(x => x.DialogEndUserContext) .IgnoreQueryFilters() .Where(x => resourceIds.Contains(x.ServiceResource)) .FirstOrDefaultAsync(x => x.Id == request.DialogId, cancellationToken); 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 58a50301b..9bd4a98d7 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 @@ -16,7 +16,8 @@ public MappingProfile() { CreateMap() .ForMember(dest => dest.Status, opt => opt.MapFrom(src => src.StatusId)) - .ForMember(dest => dest.SeenSinceLastUpdate, opt => opt.Ignore()); + .ForMember(dest => dest.SeenSinceLastUpdate, opt => opt.Ignore()) + .ForMember(dest => dest.SystemLabel, opt => opt.MapFrom(src => src.DialogEndUserContext.SystemLabelId)); 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 14d19819b..a6e5c140e 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 @@ -26,7 +26,8 @@ public MappingProfile() .Count(x => x.Urls .Any(url => url.ConsumerTypeId == AttachmentUrlConsumerType.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)); + .ForMember(dest => dest.Status, opt => opt.MapFrom(src => src.StatusId)) + .ForMember(dest => dest.SystemLabel, opt => opt.MapFrom(src => src.DialogEndUserContext.SystemLabelId)); CreateMap() .ForMember(dest => dest.SeenAt, opt => opt.MapFrom(src => src.CreatedAt)); diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Queries/Search/SearchDialogDtoBase.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Queries/Search/SearchDialogDtoBase.cs index 213d6d837..b8b98c385 100644 --- a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Queries/Search/SearchDialogDtoBase.cs +++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Queries/Search/SearchDialogDtoBase.cs @@ -1,5 +1,6 @@ using Digdir.Domain.Dialogporten.Application.Features.V1.Common.Localizations; using Digdir.Domain.Dialogporten.Domain.Actors; +using Digdir.Domain.Dialogporten.Domain.DialogEndUserContexts.Entities; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities; @@ -104,6 +105,11 @@ public class SearchDialogDtoBase /// public DialogStatus.Values Status { get; set; } + /// + /// Current display state. + /// + public SystemLabel.Values SystemLabel { get; set; } + /// /// The latest entry in the dialog's activity log. /// 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 312c6ab6a..3cd661e74 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 @@ -8,6 +8,7 @@ using Digdir.Domain.Dialogporten.Application.Common.ReturnTypes; using Digdir.Domain.Dialogporten.Application.Externals; using Digdir.Domain.Dialogporten.Application.Externals.AltinnAuthorization; +using Digdir.Domain.Dialogporten.Domain.DialogEndUserContexts.Entities; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities; using Digdir.Domain.Dialogporten.Domain.Localizations; using MediatR; @@ -94,6 +95,11 @@ public sealed class SearchDialogQuery : SortablePaginationParameter public string? Process { get; init; } + /// + /// Filter by Display state + /// + public List? SystemLabel { get; set; } + /// /// Search string for free text search. Will attempt to fuzzily match in all free text fields in the aggregate /// @@ -108,6 +114,7 @@ public string? SearchLanguageCode init => _searchLanguageCode = Localization.NormalizeCultureCode(value); } } + public sealed class SearchDialogQueryOrderDefinition : IOrderDefinition { public static IOrderOptions Configure(IOrderOptionsBuilder options) => @@ -177,9 +184,10 @@ public async Task Handle(SearchDialogQuery request, Cancella .WhereIf(request.UpdatedBefore.HasValue, x => x.UpdatedAt <= request.UpdatedBefore) .WhereIf(request.DueAfter.HasValue, x => request.DueAfter <= x.DueAt) .WhereIf(request.DueBefore.HasValue, x => x.DueAt <= request.DueBefore) - .WhereIf(request.Process is not null, x => EF.Functions.ILike(x.Process!, request.Process!)) + .WhereIf(request.Process is not null, x => EF.Functions.ILike(x.Process!, request.Process!)) .WhereIf(request.VisibleAfter.HasValue, x => request.VisibleAfter <= x.VisibleFrom) .WhereIf(request.VisibleBefore.HasValue, x => x.VisibleFrom <= request.VisibleBefore) + .WhereIf(!request.SystemLabel.IsNullOrEmpty(), x => request.SystemLabel!.Contains(x.DialogEndUserContext.SystemLabelId)) .WhereIf(request.Search is not null, x => x.Content.Any(x => x.Value.Localizations.AsQueryable().Any(searchExpression)) || x.SearchTags.Any(x => EF.Functions.ILike(x.Value, request.Search!)) diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Queries/Search/SearchDialogQueryValidator.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Queries/Search/SearchDialogQueryValidator.cs index 4f3f4d1c4..0ce1a1a3c 100644 --- a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Queries/Search/SearchDialogQueryValidator.cs +++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Queries/Search/SearchDialogQueryValidator.cs @@ -53,6 +53,7 @@ public SearchDialogQueryValidator() RuleForEach(x => x.Status).IsInEnum(); + RuleForEach(x => x.SystemLabel).IsInEnum(); RuleFor(x => x.Process) .IsValidUri() .MaximumLength(Constants.DefaultMaxUriLength) diff --git a/src/Digdir.Domain.Dialogporten.Domain/DialogEndUserContexts/Entities/DialogEndUserContext.cs b/src/Digdir.Domain.Dialogporten.Domain/DialogEndUserContexts/Entities/DialogEndUserContext.cs new file mode 100644 index 000000000..e7c49bdfe --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.Domain/DialogEndUserContexts/Entities/DialogEndUserContext.cs @@ -0,0 +1,67 @@ +using Digdir.Domain.Dialogporten.Domain.Actors; +using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities; +using Digdir.Library.Entity.Abstractions; +using Digdir.Library.Entity.Abstractions.Features.Aggregate; +using Digdir.Library.Entity.Abstractions.Features.Versionable; + +namespace Digdir.Domain.Dialogporten.Domain.DialogEndUserContexts.Entities; + +public sealed class DialogEndUserContext : IEntity, IVersionableEntity +{ + private readonly List _labelAssignmentLogs = []; + + public Guid Id { get; set; } + public DateTimeOffset CreatedAt { get; set; } + public DateTimeOffset UpdatedAt { get; set; } + public Guid Revision { get; set; } + + public Guid? DialogId { get; set; } + public DialogEntity? Dialog { get; set; } + + public SystemLabel.Values SystemLabelId { get; private set; } = SystemLabel.Values.Default; + public SystemLabel SystemLabel { get; private set; } = null!; + + [AggregateChild] + public IReadOnlyCollection LabelAssignmentLogs => _labelAssignmentLogs.AsReadOnly(); + + public void UpdateLabel(SystemLabel.Values newLabel, string userId, ActorType.Values actorType = ActorType.Values.PartyRepresentative) + { + var currentLabel = SystemLabelId; + if (newLabel == currentLabel) + { + return; + } + + // remove old label then add new one + if (currentLabel != SystemLabel.Values.Default) + { + _labelAssignmentLogs.Add(new() + { + Name = currentLabel.ToNamespacedName(), + Action = "remove", + PerformedBy = new() + { + ActorTypeId = actorType, + ActorId = userId + } + }); + } + + if (newLabel != SystemLabel.Values.Default) + { + _labelAssignmentLogs.Add(new() + { + Name = newLabel.ToNamespacedName(), + Action = "set", + PerformedBy = new() + { + ActorTypeId = actorType, + ActorId = userId + } + }); + } + + SystemLabelId = newLabel; + } + +} diff --git a/src/Digdir.Domain.Dialogporten.Domain/DialogEndUserContexts/Entities/LabelAssignmentLog.cs b/src/Digdir.Domain.Dialogporten.Domain/DialogEndUserContexts/Entities/LabelAssignmentLog.cs new file mode 100644 index 000000000..4349a07d7 --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.Domain/DialogEndUserContexts/Entities/LabelAssignmentLog.cs @@ -0,0 +1,23 @@ +using Digdir.Domain.Dialogporten.Domain.Actors; +using Digdir.Library.Entity.Abstractions.Features.Immutable; + +namespace Digdir.Domain.Dialogporten.Domain.DialogEndUserContexts.Entities; + +public sealed class LabelAssignmentLog : IImmutableEntity +{ + public Guid Id { get; set; } + public DateTimeOffset CreatedAt { get; set; } + public string Name { get; set; } = null!; + public string Action { get; set; } = null!; + + public Guid ContextId { get; set; } + public DialogEndUserContext Context { get; set; } = null!; + + public LabelAssignmentLogActor PerformedBy { get; set; } = null!; +} + +public sealed class LabelAssignmentLogActor : Actor, IImmutableEntity +{ + public Guid LabelAssignmentLogId { get; set; } + public LabelAssignmentLog LabelAssignmentLog { get; set; } = null!; +} diff --git a/src/Digdir.Domain.Dialogporten.Domain/DialogEndUserContexts/Entities/SystemLabel.cs b/src/Digdir.Domain.Dialogporten.Domain/DialogEndUserContexts/Entities/SystemLabel.cs new file mode 100644 index 000000000..6ab48c99e --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.Domain/DialogEndUserContexts/Entities/SystemLabel.cs @@ -0,0 +1,31 @@ +using System.ComponentModel; +using Digdir.Library.Entity.Abstractions.Features.Lookup; + +namespace Digdir.Domain.Dialogporten.Domain.DialogEndUserContexts.Entities; + +public sealed class SystemLabel : AbstractLookupEntity +{ + public const string Prefix = "systemlabel"; + public const string PrefixWithSeparator = Prefix + ":"; + + public enum Values + { + Default = 1, + Bin = 2, + Archive = 3 + } + + public SystemLabel(Values id) : base(id) { } + public override SystemLabel MapValue(Values id) => new(id); +} + +public static class SystemLabelExtensions +{ + public static string ToNamespacedName(this SystemLabel.Values label) => label switch + { + SystemLabel.Values.Default => SystemLabel.PrefixWithSeparator + label, + SystemLabel.Values.Bin => SystemLabel.PrefixWithSeparator + label, + SystemLabel.Values.Archive => SystemLabel.PrefixWithSeparator + label, + _ => throw new InvalidEnumArgumentException(nameof(label), (int)label, typeof(SystemLabel.Values)) + }; +} diff --git a/src/Digdir.Domain.Dialogporten.Domain/Dialogs/Entities/DialogEntity.cs b/src/Digdir.Domain.Dialogporten.Domain/Dialogs/Entities/DialogEntity.cs index 634bf121a..27e255e51 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.Actors; using Digdir.Domain.Dialogporten.Domain.Attachments; +using Digdir.Domain.Dialogporten.Domain.DialogEndUserContexts.Entities; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actions; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Contents; @@ -41,6 +42,7 @@ public sealed class DialogEntity : public string? PrecedingProcess { get; set; } + // === Dependent relationships === public DialogStatus.Values StatusId { get; set; } public DialogStatus Status { get; set; } = null!; @@ -71,6 +73,8 @@ public sealed class DialogEntity : [AggregateChild] public List SeenLog { get; set; } = []; + public DialogEndUserContext DialogEndUserContext { get; set; } = null!; + public void OnCreate(AggregateNode self, DateTimeOffset utcNow) => _domainEvents.Add(new DialogCreatedDomainEvent(Id, ServiceResource, Party, Process, PrecedingProcess)); @@ -83,10 +87,10 @@ public void OnDelete(AggregateNode self, DateTimeOffset utcNow) public void UpdateSeenAt(string endUserId, DialogUserType.Values userTypeId, string? endUserName) { var lastSeenAt = SeenLog - .Where(x => x.SeenBy.ActorId == endUserId) - .MaxBy(x => x.CreatedAt) - ?.CreatedAt - ?? DateTimeOffset.MinValue; + .Where(x => x.SeenBy.ActorId == endUserId) + .MaxBy(x => x.CreatedAt) + ?.CreatedAt + ?? DateTimeOffset.MinValue; if (lastSeenAt >= UpdatedAt) { diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/MutationTypes/MappingProfile.cs b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/MutationTypes/MappingProfile.cs new file mode 100644 index 000000000..057f14b12 --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/MutationTypes/MappingProfile.cs @@ -0,0 +1,13 @@ +using AutoMapper; +using Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.DialogSystemLabels.Commands.Set; + +namespace Digdir.Domain.Dialogporten.GraphQL.EndUser.MutationTypes; + +public sealed class MappingProfile : Profile +{ + public MappingProfile() + { + CreateMap() + .ForMember(dest => dest.Label, opt => opt.MapFrom(src => src.Label)); + } +} diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/MutationTypes/Mutations.cs b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/MutationTypes/Mutations.cs new file mode 100644 index 000000000..2fd7d27a0 --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/MutationTypes/Mutations.cs @@ -0,0 +1,42 @@ +using AutoMapper; +using Digdir.Domain.Dialogporten.Application.Common.ReturnTypes; +using Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.DialogSystemLabels.Commands.Set; +using MediatR; + +namespace Digdir.Domain.Dialogporten.GraphQL.EndUser.MutationTypes; + +public sealed class Mutations +{ + public async Task SetSystemLabel( + [Service] ISender mediator, + [Service] IMapper mapper, + SetSystemLabelInput input) + { + var command = mapper.Map(input); + var result = await mediator.Send(command); + + return result.Match( + success => new SetSystemLabelPayload { Success = true }, + entityNotFound => new SetSystemLabelPayload + { + Errors = [new SetSystemLabelEntityNotFound { Message = entityNotFound.Message }] + }, + entityDeleted => new SetSystemLabelPayload + { + Errors = [new SetSystemLabelEntityDeleted { Message = entityDeleted.Message }] + }, + validationError => new SetSystemLabelPayload + { + Errors = validationError.Errors.Select(x => new SetSystemLabelValidationError + { + Message = x.ErrorMessage + }).Cast().ToList() + }, + domainError => new SetSystemLabelPayload + { + Errors = domainError.Errors.Select(x => new SetSystemLabelDomainError { Message = x.ErrorMessage }) + .Cast().ToList() + }, + concurrencyError => new SetSystemLabelPayload { Errors = [] }); + } +} diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/MutationTypes/ObjectTypes.cs b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/MutationTypes/ObjectTypes.cs new file mode 100644 index 000000000..c6b00b7be --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/MutationTypes/ObjectTypes.cs @@ -0,0 +1,56 @@ +namespace Digdir.Domain.Dialogporten.GraphQL.EndUser.MutationTypes; + +public sealed class SetSystemLabelPayload +{ + public bool Success { get; set; } + public List Errors { get; set; } = []; +} + +public sealed class SetSystemLabelInput +{ + public Guid DialogId { get; set; } + public SystemLabel Label { get; set; } +} + +public enum SystemLabel +{ + Default = 1, + Bin = 2, + Archive = 3 +} + +[InterfaceType("SetSystemLabelError")] +public interface ISetSystemLabelError +{ + public string Message { get; set; } +} + +public sealed class SetSystemLabelEntityNotFound : ISetSystemLabelError +{ + public string Message { get; set; } = null!; +} + +public sealed class SetSystemLabelForbidden : ISetSystemLabelError +{ + public string Message { get; set; } = null!; +} + +public sealed class SetSystemLabelDomainError : ISetSystemLabelError +{ + public string Message { get; set; } = null!; +} + +public sealed class SetSystemLabelConcurrencyError : ISetSystemLabelError +{ + public string Message { get; set; } = null!; +} + +public sealed class SetSystemLabelEntityDeleted : ISetSystemLabelError +{ + public string Message { get; set; } = null!; +} + +public sealed class SetSystemLabelValidationError : ISetSystemLabelError +{ + public string Message { get; set; } = null!; +} diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/ServiceCollectionExtensions.cs b/src/Digdir.Domain.Dialogporten.GraphQL/ServiceCollectionExtensions.cs index 3340e3071..14b44b80f 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/ServiceCollectionExtensions.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/ServiceCollectionExtensions.cs @@ -1,5 +1,6 @@ using Digdir.Domain.Dialogporten.GraphQL.EndUser; using Digdir.Domain.Dialogporten.GraphQL.EndUser.DialogById; +using Digdir.Domain.Dialogporten.GraphQL.EndUser.MutationTypes; using Digdir.Domain.Dialogporten.GraphQL.EndUser.SearchDialogs; using Digdir.Domain.Dialogporten.Infrastructure.Persistence; @@ -17,11 +18,13 @@ public static IServiceCollection AddDialogportenGraphQl(this IServiceCollection .RegisterDbContext() .AddDiagnosticEventListener() .AddQueryType() + .AddMutationType() .AddType() .AddType() .AddType() .AddType() .AddType() + .AddType() .AddInstrumentation() .InitializeOnStartup() .Services; diff --git a/src/Digdir.Domain.Dialogporten.Infrastructure/Persistence/Configurations/DialogEndUserContexts/DialogEndUserContextConfiguration.cs b/src/Digdir.Domain.Dialogporten.Infrastructure/Persistence/Configurations/DialogEndUserContexts/DialogEndUserContextConfiguration.cs new file mode 100644 index 000000000..49c6a5903 --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.Infrastructure/Persistence/Configurations/DialogEndUserContexts/DialogEndUserContextConfiguration.cs @@ -0,0 +1,16 @@ +using Digdir.Domain.Dialogporten.Domain.DialogEndUserContexts.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Digdir.Domain.Dialogporten.Infrastructure.Persistence.Configurations.DialogEndUserContexts; + +internal sealed class DialogEndUserContextConfiguration : IEntityTypeConfiguration +{ + + public void Configure(EntityTypeBuilder builder) + { + builder.HasOne(d => d.Dialog) + .WithOne(d => d.DialogEndUserContext) + .OnDelete(DeleteBehavior.SetNull); + } +} diff --git a/src/Digdir.Domain.Dialogporten.Infrastructure/Persistence/DialogDbContext.cs b/src/Digdir.Domain.Dialogporten.Infrastructure/Persistence/DialogDbContext.cs index 9bebc5737..567df3804 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.Storage.ValueConversion; using System.Linq.Expressions; using Digdir.Domain.Dialogporten.Domain.Actors; +using Digdir.Domain.Dialogporten.Domain.DialogEndUserContexts.Entities; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Contents; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Transmissions; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Transmissions.Contents; @@ -43,10 +44,11 @@ public DialogDbContext(DbContextOptions options) : base(options public DbSet OutboxMessages => Set(); public DbSet OutboxMessageConsumers => Set(); public DbSet SubjectResources => Set(); + public DbSet DialogEndUserContexts => Set(); + public DbSet LabelAssignmentLogs => Set(); //protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) => // optionsBuilder.LogTo(Console.WriteLine); - internal bool TrySetOriginalRevision( TEntity? entity, Guid? revision) diff --git a/src/Digdir.Domain.Dialogporten.Infrastructure/Persistence/Migrations/20241002134750_AddSystemLabel.Designer.cs b/src/Digdir.Domain.Dialogporten.Infrastructure/Persistence/Migrations/20241002134750_AddSystemLabel.Designer.cs new file mode 100644 index 000000000..f4acfbda1 --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.Infrastructure/Persistence/Migrations/20241002134750_AddSystemLabel.Designer.cs @@ -0,0 +1,1998 @@ +// +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("20241002134750_AddSystemLabel")] + partial class AddSystemLabel + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Actors.Actor", 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.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.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("current_timestamp at time zone 'utc'"); + + b.HasKey("Id"); + + b.HasIndex("ActorTypeId"); + + b.ToTable("Actor"); + + b.HasDiscriminator().HasValue("Actor"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Actors.ActorType", b => + { + b.Property("Id") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.ToTable("ActorType"); + + b.HasData( + new + { + Id = 1, + Name = "PartyRepresentative" + }, + new + { + Id = 2, + Name = "ServiceOwner" + }); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Attachments.Attachment", 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.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("current_timestamp at time zone 'utc'"); + + b.HasKey("Id"); + + b.ToTable("Attachment"); + + b.HasDiscriminator().HasValue("Attachment"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Attachments.AttachmentUrl", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasDefaultValueSql("gen_random_uuid()"); + + b.Property("AttachmentId") + .HasColumnType("uuid"); + + b.Property("ConsumerTypeId") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("current_timestamp at time zone 'utc'"); + + 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("AttachmentId"); + + b.HasIndex("ConsumerTypeId"); + + b.ToTable("AttachmentUrl"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Attachments.AttachmentUrlConsumerType", b => + { + b.Property("Id") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.ToTable("AttachmentUrlConsumerType"); + + b.HasData( + new + { + Id = 1, + Name = "Gui" + }, + new + { + Id = 2, + Name = "Api" + }); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.DialogEndUserContexts.Entities.DialogEndUserContext", 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("Revision") + .IsConcurrencyToken() + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasDefaultValueSql("gen_random_uuid()"); + + b.Property("SystemLabelId") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("current_timestamp at time zone 'utc'"); + + b.HasKey("Id"); + + b.HasIndex("DialogId") + .IsUnique(); + + b.HasIndex("SystemLabelId"); + + b.ToTable("DialogEndUserContext"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.DialogEndUserContexts.Entities.LabelAssignmentLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasDefaultValueSql("gen_random_uuid()"); + + b.Property("Action") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ContextId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("current_timestamp at time zone 'utc'"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("ContextId"); + + b.ToTable("LabelAssignmentLog"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.DialogEndUserContexts.Entities.SystemLabel", b => + { + b.Property("Id") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.ToTable("SystemLabel"); + + b.HasData( + new + { + Id = 1, + Name = "Default" + }, + new + { + Id = 2, + Name = "Bin" + }, + new + { + Id = 3, + Name = "Archive" + }); + }); + + 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("RelatedActivityId") + .HasColumnType("uuid"); + + b.Property("TransmissionId") + .HasColumnType("uuid"); + + b.Property("TypeId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("DialogId"); + + b.HasIndex("RelatedActivityId"); + + b.HasIndex("TransmissionId"); + + 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 = "DialogCreated" + }, + new + { + Id = 2, + Name = "DialogClosed" + }, + new + { + Id = 3, + Name = "Information" + }, + new + { + Id = 4, + Name = "TransmissionOpened" + }, + new + { + Id = 5, + Name = "PaymentMade" + }, + new + { + Id = 6, + Name = "SignatureProvided" + }, + new + { + Id = 7, + Name = "DialogOpened" + }); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Contents.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") + .IsRequired() + .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.Contents.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/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("PrecedingProcess") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Process") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + 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("Process"); + + 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.HasKey("Id"); + + b.HasIndex("DialogId"); + + b.HasIndex("EndUserTypeId"); + + 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 = "Draft" + }, + new + { + Id = 4, + Name = "Sent" + }, + new + { + Id = 5, + Name = "RequiresAttention" + }, + 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 = "SystemUser" + }, + new + { + Id = 3, + Name = "ServiceOwner" + }, + new + { + Id = 4, + Name = "ServiceOwnerOnBehalfOfPerson" + }); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Transmissions.Contents.DialogTransmissionContent", 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("MediaType") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("TransmissionId") + .HasColumnType("uuid"); + + 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("TransmissionId", "TypeId") + .IsUnique(); + + b.ToTable("DialogTransmissionContent"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Transmissions.Contents.DialogTransmissionContentType", 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("Required") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("DialogTransmissionContentType"); + + b.HasData( + new + { + Id = 1, + AllowedMediaTypes = new[] { "text/plain" }, + MaxLength = 255, + Name = "Title", + Required = true + }, + new + { + Id = 2, + AllowedMediaTypes = new[] { "text/plain" }, + MaxLength = 255, + Name = "Summary", + Required = true + }); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Transmissions.DialogTransmission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasDefaultValueSql("gen_random_uuid()"); + + 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("ExtendedType") + .HasMaxLength(1023) + .HasColumnType("character varying(1023)"); + + b.Property("RelatedTransmissionId") + .HasColumnType("uuid"); + + b.Property("TypeId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("DialogId"); + + b.HasIndex("RelatedTransmissionId"); + + b.HasIndex("TypeId"); + + b.ToTable("DialogTransmission"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Transmissions.DialogTransmissionType", b => + { + b.Property("Id") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.ToTable("DialogTransmissionType"); + + b.HasData( + new + { + Id = 1, + Name = "Information" + }, + new + { + Id = 2, + Name = "Acceptance" + }, + new + { + Id = 3, + Name = "Rejection" + }, + new + { + Id = 4, + Name = "Request" + }, + new + { + Id = 5, + Name = "Alert" + }, + new + { + Id = 6, + Name = "Decision" + }, + new + { + Id = 7, + Name = "Submission" + }, + new + { + Id = 8, + Name = "Correction" + }); + }); + + 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.SubjectResources.SubjectResource", 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("Resource") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Subject") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("current_timestamp at time zone 'utc'"); + + b.HasKey("Id"); + + b.HasIndex("Resource", "Subject") + .IsUnique(); + + b.ToTable("SubjectResource"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.DialogEndUserContexts.Entities.LabelAssignmentLogActor", b => + { + b.HasBaseType("Digdir.Domain.Dialogporten.Domain.Actors.Actor"); + + b.Property("LabelAssignmentLogId") + .HasColumnType("uuid"); + + b.HasIndex("LabelAssignmentLogId") + .IsUnique(); + + b.HasDiscriminator().HasValue("LabelAssignmentLogActor"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities.DialogActivityPerformedByActor", b => + { + b.HasBaseType("Digdir.Domain.Dialogporten.Domain.Actors.Actor"); + + b.Property("ActivityId") + .HasColumnType("uuid"); + + b.HasIndex("ActivityId") + .IsUnique(); + + b.HasDiscriminator().HasValue("DialogActivityPerformedByActor"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.DialogSeenLogSeenByActor", b => + { + b.HasBaseType("Digdir.Domain.Dialogporten.Domain.Actors.Actor"); + + b.Property("DialogSeenLogId") + .HasColumnType("uuid"); + + b.HasIndex("DialogSeenLogId") + .IsUnique(); + + b.HasDiscriminator().HasValue("DialogSeenLogSeenByActor"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Transmissions.DialogTransmissionSenderActor", b => + { + b.HasBaseType("Digdir.Domain.Dialogporten.Domain.Actors.Actor"); + + b.Property("TransmissionId") + .HasColumnType("uuid"); + + b.HasIndex("TransmissionId") + .IsUnique(); + + b.HasDiscriminator().HasValue("DialogTransmissionSenderActor"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.DialogAttachment", b => + { + b.HasBaseType("Digdir.Domain.Dialogporten.Domain.Attachments.Attachment"); + + b.Property("DialogId") + .HasColumnType("uuid"); + + b.HasIndex("DialogId"); + + b.HasDiscriminator().HasValue("DialogAttachment"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Transmissions.DialogTransmissionAttachment", b => + { + b.HasBaseType("Digdir.Domain.Dialogporten.Domain.Attachments.Attachment"); + + b.Property("TransmissionId") + .HasColumnType("uuid"); + + b.HasIndex("TransmissionId"); + + b.HasDiscriminator().HasValue("DialogTransmissionAttachment"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.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.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.Contents.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.Transmissions.Contents.DialogTransmissionContentValue", b => + { + b.HasBaseType("Digdir.Domain.Dialogporten.Domain.Localizations.LocalizationSet"); + + b.Property("TransmissionContentId") + .HasColumnType("uuid"); + + b.HasIndex("TransmissionContentId") + .IsUnique(); + + b.HasDiscriminator().HasValue("DialogTransmissionContentValue"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Actors.Actor", b => + { + b.HasOne("Digdir.Domain.Dialogporten.Domain.Actors.ActorType", "ActorType") + .WithMany() + .HasForeignKey("ActorTypeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("ActorType"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Attachments.AttachmentUrl", b => + { + b.HasOne("Digdir.Domain.Dialogporten.Domain.Attachments.Attachment", "Attachment") + .WithMany("Urls") + .HasForeignKey("AttachmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Digdir.Domain.Dialogporten.Domain.Attachments.AttachmentUrlConsumerType", "ConsumerType") + .WithMany() + .HasForeignKey("ConsumerTypeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Attachment"); + + b.Navigation("ConsumerType"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.DialogEndUserContexts.Entities.DialogEndUserContext", b => + { + b.HasOne("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.DialogEntity", "Dialog") + .WithOne("DialogEndUserContext") + .HasForeignKey("Digdir.Domain.Dialogporten.Domain.DialogEndUserContexts.Entities.DialogEndUserContext", "DialogId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Digdir.Domain.Dialogporten.Domain.DialogEndUserContexts.Entities.SystemLabel", "SystemLabel") + .WithMany() + .HasForeignKey("SystemLabelId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Dialog"); + + b.Navigation("SystemLabel"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.DialogEndUserContexts.Entities.LabelAssignmentLog", b => + { + b.HasOne("Digdir.Domain.Dialogporten.Domain.DialogEndUserContexts.Entities.DialogEndUserContext", "Context") + .WithMany("LabelAssignmentLogs") + .HasForeignKey("ContextId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Context"); + }); + + 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.Activities.DialogActivity", "RelatedActivity") + .WithMany("RelatedActivities") + .HasForeignKey("RelatedActivityId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Transmissions.DialogTransmission", "Transmission") + .WithMany("Activities") + .HasForeignKey("TransmissionId") + .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("RelatedActivity"); + + b.Navigation("Transmission"); + + b.Navigation("Type"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Contents.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.Contents.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.Navigation("Dialog"); + + b.Navigation("EndUserType"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Transmissions.Contents.DialogTransmissionContent", b => + { + b.HasOne("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Transmissions.DialogTransmission", "Transmission") + .WithMany("Content") + .HasForeignKey("TransmissionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Transmissions.Contents.DialogTransmissionContentType", "Type") + .WithMany() + .HasForeignKey("TypeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Transmission"); + + b.Navigation("Type"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Transmissions.DialogTransmission", b => + { + b.HasOne("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.DialogEntity", "Dialog") + .WithMany("Transmissions") + .HasForeignKey("DialogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Transmissions.DialogTransmission", "RelatedTransmission") + .WithMany("RelatedTransmissions") + .HasForeignKey("RelatedTransmissionId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Transmissions.DialogTransmissionType", "Type") + .WithMany() + .HasForeignKey("TypeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Dialog"); + + b.Navigation("RelatedTransmission"); + + b.Navigation("Type"); + }); + + 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.DialogEndUserContexts.Entities.LabelAssignmentLogActor", b => + { + b.HasOne("Digdir.Domain.Dialogporten.Domain.DialogEndUserContexts.Entities.LabelAssignmentLog", "LabelAssignmentLog") + .WithOne("PerformedBy") + .HasForeignKey("Digdir.Domain.Dialogporten.Domain.DialogEndUserContexts.Entities.LabelAssignmentLogActor", "LabelAssignmentLogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("LabelAssignmentLog"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities.DialogActivityPerformedByActor", b => + { + b.HasOne("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities.DialogActivity", "Activity") + .WithOne("PerformedBy") + .HasForeignKey("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities.DialogActivityPerformedByActor", "ActivityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Activity"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.DialogSeenLogSeenByActor", b => + { + b.HasOne("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.DialogSeenLog", "DialogSeenLog") + .WithOne("SeenBy") + .HasForeignKey("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.DialogSeenLogSeenByActor", "DialogSeenLogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DialogSeenLog"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Transmissions.DialogTransmissionSenderActor", b => + { + b.HasOne("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Transmissions.DialogTransmission", "Transmission") + .WithOne("Sender") + .HasForeignKey("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Transmissions.DialogTransmissionSenderActor", "TransmissionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Transmission"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.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.Transmissions.DialogTransmissionAttachment", b => + { + b.HasOne("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Transmissions.DialogTransmission", "Transmission") + .WithMany("Attachments") + .HasForeignKey("TransmissionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Transmission"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Attachments.AttachmentDisplayName", b => + { + b.HasOne("Digdir.Domain.Dialogporten.Domain.Attachments.Attachment", "Attachment") + .WithOne("DisplayName") + .HasForeignKey("Digdir.Domain.Dialogporten.Domain.Attachments.AttachmentDisplayName", "AttachmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Attachment"); + }); + + 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.Contents.DialogContentValue", b => + { + b.HasOne("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Contents.DialogContent", "DialogContent") + .WithOne("Value") + .HasForeignKey("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Contents.DialogContentValue", "DialogContentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DialogContent"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Transmissions.Contents.DialogTransmissionContentValue", b => + { + b.HasOne("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Transmissions.Contents.DialogTransmissionContent", "TransmissionContent") + .WithOne("Value") + .HasForeignKey("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Transmissions.Contents.DialogTransmissionContentValue", "TransmissionContentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("TransmissionContent"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Attachments.Attachment", b => + { + b.Navigation("DisplayName"); + + b.Navigation("Urls"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.DialogEndUserContexts.Entities.DialogEndUserContext", b => + { + b.Navigation("LabelAssignmentLogs"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.DialogEndUserContexts.Entities.LabelAssignmentLog", b => + { + b.Navigation("PerformedBy") + .IsRequired(); + }); + + 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("PerformedBy") + .IsRequired(); + + b.Navigation("RelatedActivities"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Contents.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("DialogEndUserContext") + .IsRequired(); + + b.Navigation("GuiActions"); + + b.Navigation("SearchTags"); + + b.Navigation("SeenLog"); + + b.Navigation("Transmissions"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.DialogSeenLog", b => + { + b.Navigation("SeenBy") + .IsRequired(); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Transmissions.Contents.DialogTransmissionContent", b => + { + b.Navigation("Value") + .IsRequired(); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Transmissions.DialogTransmission", b => + { + b.Navigation("Activities"); + + b.Navigation("Attachments"); + + b.Navigation("Content"); + + b.Navigation("RelatedTransmissions"); + + b.Navigation("Sender") + .IsRequired(); + }); + + 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/20241002134750_AddSystemLabel.cs b/src/Digdir.Domain.Dialogporten.Infrastructure/Persistence/Migrations/20241002134750_AddSystemLabel.cs new file mode 100644 index 000000000..5a28964ed --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.Infrastructure/Persistence/Migrations/20241002134750_AddSystemLabel.cs @@ -0,0 +1,160 @@ +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 AddSystemLabel : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "LabelAssignmentLogId", + table: "Actor", + type: "uuid", + nullable: true); + + migrationBuilder.CreateTable( + name: "SystemLabel", + 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_SystemLabel", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "DialogEndUserContext", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "current_timestamp at time zone 'utc'"), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "current_timestamp at time zone 'utc'"), + Revision = table.Column(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"), + DialogId = table.Column(type: "uuid", nullable: true), + SystemLabelId = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_DialogEndUserContext", x => x.Id); + table.ForeignKey( + name: "FK_DialogEndUserContext_Dialog_DialogId", + column: x => x.DialogId, + principalTable: "Dialog", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + table.ForeignKey( + name: "FK_DialogEndUserContext_SystemLabel_SystemLabelId", + column: x => x.SystemLabelId, + principalTable: "SystemLabel", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "LabelAssignmentLog", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "current_timestamp at time zone 'utc'"), + Name = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), + Action = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), + ContextId = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_LabelAssignmentLog", x => x.Id); + table.ForeignKey( + name: "FK_LabelAssignmentLog_DialogEndUserContext_ContextId", + column: x => x.ContextId, + principalTable: "DialogEndUserContext", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.InsertData( + table: "SystemLabel", + columns: new[] { "Id", "Name" }, + values: new object[,] + { + { 1, "Default" }, + { 2, "Bin" }, + { 3, "Archive" } + }); + + migrationBuilder.CreateIndex( + name: "IX_Actor_LabelAssignmentLogId", + table: "Actor", + column: "LabelAssignmentLogId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_DialogEndUserContext_DialogId", + table: "DialogEndUserContext", + column: "DialogId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_DialogEndUserContext_SystemLabelId", + table: "DialogEndUserContext", + column: "SystemLabelId"); + + migrationBuilder.CreateIndex( + name: "IX_LabelAssignmentLog_ContextId", + table: "LabelAssignmentLog", + column: "ContextId"); + + migrationBuilder.AddForeignKey( + name: "FK_Actor_LabelAssignmentLog_LabelAssignmentLogId", + table: "Actor", + column: "LabelAssignmentLogId", + principalTable: "LabelAssignmentLog", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.Sql(""" + INSERT INTO "DialogEndUserContext" ("Id", "DialogId", "SystemLabelId") + SELECT d."Id" -- Just borrow the DialogId to get uuid7 + ,d."Id" + ,1 -- Default System Label + FROM "Dialog" d + LEFT JOIN "DialogEndUserContext" c + ON d."Id" = c."DialogId" + WHERE c."Id" is null; + """); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Actor_LabelAssignmentLog_LabelAssignmentLogId", + table: "Actor"); + + migrationBuilder.DropTable( + name: "LabelAssignmentLog"); + + migrationBuilder.DropTable( + name: "DialogEndUserContext"); + + migrationBuilder.DropTable( + name: "SystemLabel"); + + migrationBuilder.DropIndex( + name: "IX_Actor_LabelAssignmentLogId", + table: "Actor"); + + migrationBuilder.DropColumn( + name: "LabelAssignmentLogId", + table: "Actor"); + } + } +} diff --git a/src/Digdir.Domain.Dialogporten.Infrastructure/Persistence/Migrations/DialogDbContextModelSnapshot.cs b/src/Digdir.Domain.Dialogporten.Infrastructure/Persistence/Migrations/DialogDbContextModelSnapshot.cs index 016ccda91..2361232e1 100644 --- a/src/Digdir.Domain.Dialogporten.Infrastructure/Persistence/Migrations/DialogDbContextModelSnapshot.cs +++ b/src/Digdir.Domain.Dialogporten.Infrastructure/Persistence/Migrations/DialogDbContextModelSnapshot.cs @@ -192,6 +192,109 @@ protected override void BuildModel(ModelBuilder modelBuilder) }); }); + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.DialogEndUserContexts.Entities.DialogEndUserContext", 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("Revision") + .IsConcurrencyToken() + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasDefaultValueSql("gen_random_uuid()"); + + b.Property("SystemLabelId") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("current_timestamp at time zone 'utc'"); + + b.HasKey("Id"); + + b.HasIndex("DialogId") + .IsUnique(); + + b.HasIndex("SystemLabelId"); + + b.ToTable("DialogEndUserContext"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.DialogEndUserContexts.Entities.LabelAssignmentLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasDefaultValueSql("gen_random_uuid()"); + + b.Property("Action") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ContextId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("current_timestamp at time zone 'utc'"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("ContextId"); + + b.ToTable("LabelAssignmentLog"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.DialogEndUserContexts.Entities.SystemLabel", b => + { + b.Property("Id") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.ToTable("SystemLabel"); + + b.HasData( + new + { + Id = 1, + Name = "Default" + }, + new + { + Id = 2, + Name = "Bin" + }, + new + { + Id = 3, + Name = "Archive" + }); + }); + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actions.DialogApiAction", b => { b.Property("Id") @@ -1215,6 +1318,19 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("SubjectResource"); }); + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.DialogEndUserContexts.Entities.LabelAssignmentLogActor", b => + { + b.HasBaseType("Digdir.Domain.Dialogporten.Domain.Actors.Actor"); + + b.Property("LabelAssignmentLogId") + .HasColumnType("uuid"); + + b.HasIndex("LabelAssignmentLogId") + .IsUnique(); + + b.HasDiscriminator().HasValue("LabelAssignmentLogActor"); + }); + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities.DialogActivityPerformedByActor", b => { b.HasBaseType("Digdir.Domain.Dialogporten.Domain.Actors.Actor"); @@ -1392,6 +1508,35 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("ConsumerType"); }); + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.DialogEndUserContexts.Entities.DialogEndUserContext", b => + { + b.HasOne("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.DialogEntity", "Dialog") + .WithOne("DialogEndUserContext") + .HasForeignKey("Digdir.Domain.Dialogporten.Domain.DialogEndUserContexts.Entities.DialogEndUserContext", "DialogId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Digdir.Domain.Dialogporten.Domain.DialogEndUserContexts.Entities.SystemLabel", "SystemLabel") + .WithMany() + .HasForeignKey("SystemLabelId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Dialog"); + + b.Navigation("SystemLabel"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.DialogEndUserContexts.Entities.LabelAssignmentLog", b => + { + b.HasOne("Digdir.Domain.Dialogporten.Domain.DialogEndUserContexts.Entities.DialogEndUserContext", "Context") + .WithMany("LabelAssignmentLogs") + .HasForeignKey("ContextId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Context"); + }); + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actions.DialogApiAction", b => { b.HasOne("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.DialogEntity", "Dialog") @@ -1609,6 +1754,17 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("OutboxMessage"); }); + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.DialogEndUserContexts.Entities.LabelAssignmentLogActor", b => + { + b.HasOne("Digdir.Domain.Dialogporten.Domain.DialogEndUserContexts.Entities.LabelAssignmentLog", "LabelAssignmentLog") + .WithOne("PerformedBy") + .HasForeignKey("Digdir.Domain.Dialogporten.Domain.DialogEndUserContexts.Entities.LabelAssignmentLogActor", "LabelAssignmentLogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("LabelAssignmentLog"); + }); + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities.DialogActivityPerformedByActor", b => { b.HasOne("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities.DialogActivity", "Activity") @@ -1737,6 +1893,17 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Urls"); }); + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.DialogEndUserContexts.Entities.DialogEndUserContext", b => + { + b.Navigation("LabelAssignmentLogs"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.DialogEndUserContexts.Entities.LabelAssignmentLog", b => + { + b.Navigation("PerformedBy") + .IsRequired(); + }); + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actions.DialogApiAction", b => { b.Navigation("Endpoints"); @@ -1775,6 +1942,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Content"); + b.Navigation("DialogEndUserContext") + .IsRequired(); + b.Navigation("GuiActions"); b.Navigation("SearchTags"); diff --git a/src/Digdir.Domain.Dialogporten.WebApi/Endpoints/V1/EndUser/DialogLabelAssignmentLogs/Search/SearchDialogLabelAssignmentLogEndpoint.cs b/src/Digdir.Domain.Dialogporten.WebApi/Endpoints/V1/EndUser/DialogLabelAssignmentLogs/Search/SearchDialogLabelAssignmentLogEndpoint.cs new file mode 100644 index 000000000..f87b992bf --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.WebApi/Endpoints/V1/EndUser/DialogLabelAssignmentLogs/Search/SearchDialogLabelAssignmentLogEndpoint.cs @@ -0,0 +1,33 @@ +using Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.DialogLabelAssignmentLog.Queries.Search; +using Digdir.Domain.Dialogporten.WebApi.Common.Authorization; +using Digdir.Domain.Dialogporten.WebApi.Common.Extensions; +using FastEndpoints; +using MediatR; + +namespace Digdir.Domain.Dialogporten.WebApi.Endpoints.V1.EndUser.DialogLabelAssignmentLogs.Search; + +public sealed class SearchDialogLabelAssignmentLogEndpoint : Endpoint> +{ + private readonly ISender _sender; + + public SearchDialogLabelAssignmentLogEndpoint(ISender sender) + { + _sender = sender ?? throw new ArgumentNullException(nameof(sender)); + } + public override void Configure() + { + Get("dialogs/{dialogId}/labellog"); + Policies(AuthorizationPolicy.EndUser); + Group(); + + Description(d => SearchDialogLabelAssignmentSwaggerConfig.SetDescription(d)); + } + public override async Task HandleAsync(SearchDialogLabelAssignmentLogQuery req, CancellationToken ct) + { + var result = await _sender.Send(req, ct); + await result.Match( + dto => SendOkAsync(dto, ct), + notFound => this.NotFoundAsync(notFound, ct), + deleted => this.GoneAsync(deleted, ct)); + } +} diff --git a/src/Digdir.Domain.Dialogporten.WebApi/Endpoints/V1/EndUser/DialogLabelAssignmentLogs/Search/SearchDialogLabelAssignmentSwaggerConfig.cs b/src/Digdir.Domain.Dialogporten.WebApi/Endpoints/V1/EndUser/DialogLabelAssignmentLogs/Search/SearchDialogLabelAssignmentSwaggerConfig.cs new file mode 100644 index 000000000..a506acf98 --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.WebApi/Endpoints/V1/EndUser/DialogLabelAssignmentLogs/Search/SearchDialogLabelAssignmentSwaggerConfig.cs @@ -0,0 +1,17 @@ +using Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.DialogLabelAssignmentLog.Queries.Search; +using Digdir.Domain.Dialogporten.WebApi.Common.Swagger; +using Digdir.Domain.Dialogporten.WebApi.Endpoints.V1.Common.Extensions; + +namespace Digdir.Domain.Dialogporten.WebApi.Endpoints.V1.EndUser.DialogLabelAssignmentLogs.Search; + +public sealed class SearchDialogLabelAssignmentSwaggerConfig : ISwaggerConfig +{ + + public static string OperationId => "SearchDialogLabelAssignmentLog"; + public static RouteHandlerBuilder SetDescription(RouteHandlerBuilder builder) + => builder.OperationId(OperationId).ProducesOneOf>( + StatusCodes.Status200OK, + StatusCodes.Status404NotFound, + StatusCodes.Status410Gone); + public static object GetExample() => throw new NotImplementedException(); +} diff --git a/src/Digdir.Domain.Dialogporten.WebApi/Endpoints/V1/EndUser/DialogSystemLabels/Set/SetDialogSystemLabelEndpoint.cs b/src/Digdir.Domain.Dialogporten.WebApi/Endpoints/V1/EndUser/DialogSystemLabels/Set/SetDialogSystemLabelEndpoint.cs new file mode 100644 index 000000000..4a3afe3f8 --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.WebApi/Endpoints/V1/EndUser/DialogSystemLabels/Set/SetDialogSystemLabelEndpoint.cs @@ -0,0 +1,32 @@ +using Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.DialogSystemLabels.Commands.Set; +using Digdir.Domain.Dialogporten.WebApi.Common.Authorization; +using Digdir.Domain.Dialogporten.WebApi.Common.Extensions; +using FastEndpoints; +using MediatR; + +namespace Digdir.Domain.Dialogporten.WebApi.Endpoints.V1.EndUser.DialogSystemLabels.Set; + +public sealed class SetDialogSystemLabelEndpoint(ISender sender) : Endpoint +{ + private readonly ISender _sender = sender ?? throw new ArgumentNullException(nameof(sender)); + + public override void Configure() + { + Put("dialogs/{dialogId}/systemlabels"); + Policies(AuthorizationPolicy.EndUser); + Group(); + + Description(b => SetDialogSystemLabelSwaggerConfig.SetDescription(b)); + } + public override async Task HandleAsync(SetDialogSystemLabelCommand req, CancellationToken ct) + { + var result = await _sender.Send(req, ct); + await result.Match( + _ => SendNoContentAsync(ct), + notFound => this.NotFoundAsync(notFound, ct), + deleted => this.GoneAsync(deleted, ct), + domainError => this.UnprocessableEntityAsync(domainError, ct), + validationError => this.BadRequestAsync(validationError, ct), + concurrencyError => this.PreconditionFailed(ct)); + } +} diff --git a/src/Digdir.Domain.Dialogporten.WebApi/Endpoints/V1/EndUser/DialogSystemLabels/Set/SetDialogSystemLabelSwaggerConfig.cs b/src/Digdir.Domain.Dialogporten.WebApi/Endpoints/V1/EndUser/DialogSystemLabels/Set/SetDialogSystemLabelSwaggerConfig.cs new file mode 100644 index 000000000..b7c923514 --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.WebApi/Endpoints/V1/EndUser/DialogSystemLabels/Set/SetDialogSystemLabelSwaggerConfig.cs @@ -0,0 +1,16 @@ +using Digdir.Domain.Dialogporten.WebApi.Endpoints.V1.Common.Extensions; + +namespace Digdir.Domain.Dialogporten.WebApi.Endpoints.V1.EndUser.DialogSystemLabels.Set; + +public sealed class SetDialogSystemLabelSwaggerConfig +{ + public static string OperationId => "SetDialogLabel"; + public static RouteHandlerBuilder SetDescription(RouteHandlerBuilder builder) => builder.OperationId(OperationId).ProducesOneOf( + StatusCodes.Status204NoContent, + StatusCodes.Status400BadRequest, + StatusCodes.Status403Forbidden, + StatusCodes.Status404NotFound, + StatusCodes.Status410Gone, + StatusCodes.Status412PreconditionFailed, + StatusCodes.Status422UnprocessableEntity); +} diff --git a/tests/Digdir.Domain.Dialogporten.Application.Integration.Tests/Features/V1/ServiceOwner/Dialogs/Queries/GetDialogTests.cs b/tests/Digdir.Domain.Dialogporten.Application.Integration.Tests/Features/V1/ServiceOwner/Dialogs/Queries/GetDialogTests.cs index 89bfb3111..5f5cb4e91 100644 --- a/tests/Digdir.Domain.Dialogporten.Application.Integration.Tests/Features/V1/ServiceOwner/Dialogs/Queries/GetDialogTests.cs +++ b/tests/Digdir.Domain.Dialogporten.Application.Integration.Tests/Features/V1/ServiceOwner/Dialogs/Queries/GetDialogTests.cs @@ -29,7 +29,8 @@ public async Task Get_ReturnsSimpleDialog_WhenDialogExists() result.Should().BeEquivalentTo(createDialogCommand, options => options .ExcludingMissingMembers() .Excluding(x => x.UpdatedAt) - .Excluding(x => x.CreatedAt)); + .Excluding(x => x.CreatedAt) + .Excluding(x => x.SystemLabel)); } [Fact] @@ -49,7 +50,8 @@ public async Task Get_ReturnsDialog_WhenDialogExists() result.Should().BeEquivalentTo(createCommand, options => options .ExcludingMissingMembers() .Excluding(x => x.UpdatedAt) - .Excluding(x => x.CreatedAt)); + .Excluding(x => x.CreatedAt) + .Excluding(x => x.SystemLabel)); } // TODO: Add tests } 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 13477addc..12c539b55 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 @@ -3,6 +3,7 @@ using Digdir.Domain.Dialogporten.Application.Common.Authorization; using Digdir.Domain.Dialogporten.Application.Common.ReturnTypes; using Digdir.Domain.Dialogporten.Application.Externals; +using Digdir.Domain.Dialogporten.Application.Externals.Presentation; using Digdir.Domain.Dialogporten.Application.Features.V1.ServiceOwner.Dialogs.Commands.Create; using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities; using Digdir.Tool.Dialogporten.GenerateFakeData; @@ -28,6 +29,7 @@ public async Task CreateDialogCommand_Should_Return_Forbidden_When_Scope_Is_Miss var userResourceRegistrySub = Substitute.For(); var userOrganizationRegistrySub = Substitute.For(); var serviceAuthorizationSub = Substitute.For(); + var userSub = Substitute.For(); var createCommand = DialogGenerator.GenerateSimpleFakeDialog(); @@ -39,7 +41,7 @@ public async Task CreateDialogCommand_Should_Return_Forbidden_When_Scope_Is_Miss .CurrentUserIsOwner(createCommand.ServiceResource, Arg.Any()) .Returns(true); - var commandHandler = new CreateDialogCommandHandler(dialogDbContextSub, + var commandHandler = new CreateDialogCommandHandler(userSub, dialogDbContextSub, mapper, unitOfWorkSub, domainContextSub, userOrganizationRegistrySub, serviceAuthorizationSub); @@ -66,7 +68,7 @@ public async Task CreateDialogCommand_Should_Return_Forbidden_When_User_Is_Not_O var userResourceRegistrySub = Substitute.For(); var userOrganizationRegistrySub = Substitute.For(); var serviceAuthorizationSub = Substitute.For(); - + var userSub = Substitute.For(); var createCommand = DialogGenerator.GenerateSimpleFakeDialog(); serviceAuthorizationSub @@ -77,7 +79,7 @@ public async Task CreateDialogCommand_Should_Return_Forbidden_When_User_Is_Not_O .CurrentUserIsOwner(createCommand.ServiceResource, Arg.Any()) .Returns(false); - var commandHandler = new CreateDialogCommandHandler(dialogDbContextSub, + var commandHandler = new CreateDialogCommandHandler(userSub, dialogDbContextSub, mapper, unitOfWorkSub, domainContextSub, userOrganizationRegistrySub, serviceAuthorizationSub); diff --git a/tests/k6/common/dialog.js b/tests/k6/common/dialog.js index 2edb4be2f..d2687495b 100644 --- a/tests/k6/common/dialog.js +++ b/tests/k6/common/dialog.js @@ -89,6 +89,7 @@ export function setProcess(dialog, process) { } dialog.process = process; } + export function setDueAt(dialog, dueAt) { if (dueAt == null) { delete dialog.dueAt; @@ -139,7 +140,13 @@ export function setVisibleFrom(dialog, visibleFrom) { dialog.visibleFrom = visibleFrom; } - +export function setSystemLabel(dialog, systemLabel) { + let validLabels = ['Default', 'Bin', 'Archive'] + if (!validLabels.includes(systemLabel)) { + throw new Error("Invalid systemLabel provided."); + } + dialog.systemLabel = systemLabel; +} export function setActivities(dialog, activities) { if (activities == null) { delete dialog.activities; diff --git a/tests/k6/common/testimports.js b/tests/k6/common/testimports.js index 544cc3bcf..14f1e919f 100644 --- a/tests/k6/common/testimports.js +++ b/tests/k6/common/testimports.js @@ -5,6 +5,8 @@ export { customConsole } from './console.js'; export { getServiceOwnerTokenFromGenerator, getEnduserTokenFromGenerator } from './token.js'; export { getEU, + postEU, + putEU, getSO, postSO, postSOAsync, @@ -28,5 +30,6 @@ export { setProcess, setVisibleFrom, setActivities, - addActivity + addActivity, + setSystemLabel } from './dialog.js'; diff --git a/tests/k6/tests/enduser/all-tests.js b/tests/k6/tests/enduser/all-tests.js index 4c96ee596..308bd1f4e 100644 --- a/tests/k6/tests/enduser/all-tests.js +++ b/tests/k6/tests/enduser/all-tests.js @@ -1,10 +1,12 @@ // This file is generated, see "scripts" directory import { default as dialogDetails } from './dialogDetails.js'; import { default as dialogSearch } from './dialogSearch.js'; +import { default as dialogSystemLabelLog } from './dialogSystemLabelLog.js'; import { default as parties } from './parties.js'; export default function() { dialogDetails(); dialogSearch(); + dialogSystemLabelLog(); parties(); } diff --git a/tests/k6/tests/enduser/dialogSearch.js b/tests/k6/tests/enduser/dialogSearch.js index 3d45d27a9..06ce3191c 100644 --- a/tests/k6/tests/enduser/dialogSearch.js +++ b/tests/k6/tests/enduser/dialogSearch.js @@ -15,6 +15,7 @@ import { setExpiresAt, setVisibleFrom, setProcess, + setSystemLabel, postSO, putSO, purgeSO @@ -29,7 +30,8 @@ export default function () { let dialogIds = []; let titleToSearchFor = uuidv4(); - let processToSearchFor = "urn:test:process:1" + let processToSearchFor = "urn:test:process:1"; + let systemLabelToSearchFor = 'Bin'; let additionalInfoToSearchFor = uuidv4(); let searchTagsToSearchFor = [uuidv4(), uuidv4()]; let extendedStatusToSearchFor = "status:" + uuidv4(); @@ -75,6 +77,7 @@ export default function () { setTitle(dialogs[++d], titleForExpiresAtItem); setExpiresAt(dialogs[d], new Date("2034-03-07T10:13:00Z")); + setSystemLabel(dialogs[d], 'Bin') dialogs[++d].id = idForCustomOrg; @@ -211,6 +214,21 @@ export default function () { expect(r.json(), 'response json').to.have.property("items").with.lengthOf(1); expect(r.json().items[0], 'process').to.have.property("process").that.equals(processToSearchFor); }) + + describe('List with systemLabel filter', () => { + let response = getEU('dialogs/' + defaultFilter + '&SystemLabel=' + systemLabelToSearchFor); + expectStatusFor(response).to.equal(200); + expect(response, 'response').to.have.validJsonBody(); + expect(response.json(), 'response json').to.have.property("items").with.lengthOf(1); + expect(response.json().items[0], 'system label').to.have.property('systemLabel').that.equals(systemLabelToSearchFor); + }) + + describe('List with invalid systemLabel filter', () => { + let response = getEU('dialogs/' + defaultFilter + '&SystemLabel=' + 'firepit'); + expectStatusFor(response).to.equal(400); + expect(response, 'response').to.have.validJsonBody(); + expect(response.json(), 'response json').to.have.property("errors"); + }) /* Disabled for now. Dialogporten doesn't have proper TTD handling as of yet. diff --git a/tests/k6/tests/enduser/dialogSystemLabelLog.js b/tests/k6/tests/enduser/dialogSystemLabelLog.js new file mode 100644 index 000000000..0abfaef0f --- /dev/null +++ b/tests/k6/tests/enduser/dialogSystemLabelLog.js @@ -0,0 +1,60 @@ +import { + describe, expect, expectStatusFor, getEU, putEU, postSO, putSO, purgeSO +} from '../../common/testimports.js' + +import {default as dialogToInsert} from '../serviceowner/testdata/01-create-dialog.js'; + +export default function () { + let dialogId; + let dialog; + describe('Arrange: Create a dialog to test against', () => { + let d = dialogToInsert(); + let r = postSO("dialogs", d); + expectStatusFor(r).to.equal(201); + dialogId = r.json(); + dialog = d; + }); + + describe('Update label as enduser', () => { + let body = { + 'label': 'Bin' + } + let response = putEU('dialogs/' + dialogId + '/systemlabels', body); + expectStatusFor(response).to.equal(204); + response = getEU('dialogs/' + dialogId + '/labellog'); + expectStatusFor(response).to.equal(200); + expect(response, 'response').to.have.validJsonBody(); + console.log(response.json()); + expect(response.json(), 'response body').to.have.lengthOf(1); + }); + + describe('Changing labels trigger 2 logs', () => { + + let body = { + 'label': 'archive' + } + let response = putEU('dialogs/' + dialogId + '/systemlabels', body); + expectStatusFor(response).to.equal(204); + response = getEU('dialogs/' + dialogId + '/labellog'); + expectStatusFor(response).to.equal(200); + expect(response, 'response').to.have.validJsonBody(); + console.log(response.json()); + expect(response.json(), 'response body').to.have.lengthOf(3); + }) + + describe('Dialog update set system label to default', () => { + dialog.progress = "60"; + let response = putSO('dialogs/' + dialogId, dialog); + expectStatusFor(response).to.equal(204); + response = getEU('dialogs/' + dialogId + '/labellog'); + expectStatusFor(response).to.equal(200); + expect(response, 'response').to.have.validJsonBody(); + console.log(response.json()); + expect(response.json(), 'response body').to.have.lengthOf(4); + }) + + describe('Cleanup', () => { + let response = purgeSO("dialogs/" + dialogId); + expectStatusFor(response).to.equal(204); + }) +}