diff --git a/docs/swagger/V1/swagger.verified.json b/docs/swagger/V1/swagger.verified.json index 882c5df4d..6244e3ca9 100644 --- a/docs/swagger/V1/swagger.verified.json +++ b/docs/swagger/V1/swagger.verified.json @@ -1486,6 +1486,126 @@ ] } }, + "/api/v1/enduser/dialogs/{dialogId}/seenlog": { + "get": { + "tags": [ + "Enduser" + ], + "summary": "Gets a single dialog seen log record", + "description": "Gets a single dialog seen log record. For more information see the documentation (link TBD).", + "operationId": "SearchDialogSeenLog", + "parameters": [ + { + "name": "dialogId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "guid" + } + } + ], + "responses": { + "401": { + "description": "Missing or invalid authentication token. Requires a Maskinporten-token with the scope \"digdir:dialogporten\"." + }, + "403": { + "description": "Forbidden" + }, + "200": { + "description": "Successfully returned the dialog seen log record.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SearchDialogSeenLogDto" + } + } + } + } + }, + "404": { + "description": "The given dialog ID or dialog element ID was not found or was already deleted.", + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + }, + "security": [ + { + "JWTBearerAuth": [] + } + ] + } + }, + "/api/v1/enduser/dialogs/{dialogId}/seenlog/{seenLogId}": { + "get": { + "tags": [ + "Enduser" + ], + "summary": "Gets a single dialog seen log record", + "description": "Gets a single dialog seen log record. For more information see the documentation (link TBD).", + "operationId": "GetDialogSeenLog", + "parameters": [ + { + "name": "dialogId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "guid" + } + }, + { + "name": "seenLogId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "guid" + } + } + ], + "responses": { + "401": { + "description": "Missing or invalid authentication token. Requires a Maskinporten-token with the scope \"digdir:dialogporten\"." + }, + "403": { + "description": "Forbidden" + }, + "200": { + "description": "Successfully returned the dialog seen log record.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetDialogSeenLogDto" + } + } + } + }, + "404": { + "description": "The given dialog ID or dialog element ID was not found or was already deleted.", + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + }, + "security": [ + { + "JWTBearerAuth": [] + } + ] + } + }, "/api/v1/enduser/dialogs": { "get": { "tags": [ @@ -2606,7 +2726,7 @@ "seenSinceLastUpdate": { "type": "array", "items": { - "$ref": "#/components/schemas/SearchDialogDialogSeenRecordDtoSO" + "$ref": "#/components/schemas/SearchDialogDialogSeenLogDtoSO" } } } @@ -2626,7 +2746,7 @@ } } }, - "SearchDialogDialogSeenRecordDtoSO": { + "SearchDialogDialogSeenLogDtoSO": { "type": "object", "additionalProperties": false, "properties": { @@ -2756,7 +2876,7 @@ "seenSinceLastUpdate": { "type": "array", "items": { - "$ref": "#/components/schemas/GetDialogDialogSeenRecordDtoSO" + "$ref": "#/components/schemas/GetDialogDialogSeenLogDtoSO" } } } @@ -2998,7 +3118,7 @@ } } }, - "GetDialogDialogSeenRecordDtoSO": { + "GetDialogDialogSeenLogDtoSO": { "type": "object", "additionalProperties": false, "properties": { @@ -3450,6 +3570,54 @@ } } }, + "SearchDialogSeenLogDto": { + "type": "object", + "additionalProperties": false, + "properties": { + "id": { + "type": "string", + "format": "guid" + }, + "seenAt": { + "type": "string", + "format": "date-time" + }, + "endUserIdHash": { + "type": "string" + }, + "endUserName": { + "type": "string", + "nullable": true + }, + "isCurrentEndUser": { + "type": "boolean" + } + } + }, + "GetDialogSeenLogDto": { + "type": "object", + "additionalProperties": false, + "properties": { + "id": { + "type": "string", + "format": "guid" + }, + "seenAt": { + "type": "string", + "format": "date-time" + }, + "endUserIdHash": { + "type": "string" + }, + "endUserName": { + "type": "string", + "nullable": true + }, + "isCurrentEndUser": { + "type": "boolean" + } + } + }, "PaginatedListOfSearchDialogDto": { "type": "object", "additionalProperties": false, @@ -3536,7 +3704,7 @@ "seenSinceLastUpdate": { "type": "array", "items": { - "$ref": "#/components/schemas/SearchDialogDialogSeenRecordDto" + "$ref": "#/components/schemas/SearchDialogDialogSeenLogDto" } } } @@ -3602,7 +3770,7 @@ } } }, - "SearchDialogDialogSeenRecordDto": { + "SearchDialogDialogSeenLogDto": { "type": "object", "additionalProperties": false, "properties": { @@ -3723,7 +3891,7 @@ "seenSinceLastUpdate": { "type": "array", "items": { - "$ref": "#/components/schemas/GetDialogDialogSeenRecordDto" + "$ref": "#/components/schemas/GetDialogDialogSeenLogDto" } } } @@ -3967,7 +4135,7 @@ } } }, - "GetDialogDialogSeenRecordDto": { + "GetDialogDialogSeenLogDto": { "type": "object", "additionalProperties": false, "properties": { diff --git a/src/Digdir.Domain.Dialogporten.Application/ApplicationExtensions.cs b/src/Digdir.Domain.Dialogporten.Application/ApplicationExtensions.cs index 8b248668d..b962d8198 100644 --- a/src/Digdir.Domain.Dialogporten.Application/ApplicationExtensions.cs +++ b/src/Digdir.Domain.Dialogporten.Application/ApplicationExtensions.cs @@ -39,7 +39,7 @@ public static IServiceCollection AddApplication(this IServiceCollection services .AddScoped() // Transient - .AddTransient() + .AddTransient() .AddTransient() .AddTransient() .AddTransient() diff --git a/src/Digdir.Domain.Dialogporten.Application/Common/MappingUtils.cs b/src/Digdir.Domain.Dialogporten.Application/Common/IStringHasher.cs similarity index 94% rename from src/Digdir.Domain.Dialogporten.Application/Common/MappingUtils.cs rename to src/Digdir.Domain.Dialogporten.Application/Common/IStringHasher.cs index 4a3ca8f22..55cdc2d84 100644 --- a/src/Digdir.Domain.Dialogporten.Application/Common/MappingUtils.cs +++ b/src/Digdir.Domain.Dialogporten.Application/Common/IStringHasher.cs @@ -10,7 +10,7 @@ internal interface IStringHasher string? Hash(string? personIdentifier); } -internal class RandomSaltStringHasher : IStringHasher +internal class PersistentRandomSaltStringHasher : IStringHasher { private const int SaltSize = 16; private readonly Lazy _lazySalt = new(() => RandomNumberGenerator.GetBytes(SaltSize)); diff --git a/src/Digdir.Domain.Dialogporten.Application/Common/IUserNameRegistry.cs b/src/Digdir.Domain.Dialogporten.Application/Common/IUserNameRegistry.cs index f0542a6a7..9e8b06c98 100644 --- a/src/Digdir.Domain.Dialogporten.Application/Common/IUserNameRegistry.cs +++ b/src/Digdir.Domain.Dialogporten.Application/Common/IUserNameRegistry.cs @@ -1,3 +1,4 @@ +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using Digdir.Domain.Dialogporten.Application.Common.Extensions; using Digdir.Domain.Dialogporten.Application.Externals; @@ -8,9 +9,11 @@ namespace Digdir.Domain.Dialogporten.Application.Common; public interface IUserNameRegistry { bool TryGetCurrentUserPid([NotNullWhen(true)] out string? userPid); - Task GetCurrentUserName(string personalIdentificationNumber, CancellationToken cancellationToken); + Task GetUserInformation(CancellationToken cancellationToken); } +public record UserInformation(string UserPid, string? UserName); + public class UserNameRegistry : IUserNameRegistry { private readonly IUser _user; @@ -24,12 +27,22 @@ public UserNameRegistry(IUser user, INameRegistry nameRegistry) public bool TryGetCurrentUserPid([NotNullWhen(true)] out string? userPid) => _user.TryGetPid(out userPid); - public async Task GetCurrentUserName(string personalIdentificationNumber, CancellationToken cancellationToken) => - await _nameRegistry.GetName(personalIdentificationNumber, cancellationToken); + public async Task GetUserInformation(CancellationToken cancellationToken) + { + if (!TryGetCurrentUserPid(out var userPid)) + { + return null; + } + + var userName = await _nameRegistry.GetName(userPid, cancellationToken); + return new(userPid, userName); + } } internal sealed class LocalDevelopmentUserNameRegistryDecorator : IUserNameRegistry { + private const string LocalDevelopmentUserPid = "Local Development User"; + private readonly IUserNameRegistry _userNameRegistry; public LocalDevelopmentUserNameRegistryDecorator(IUserNameRegistry userNameRegistry) @@ -40,6 +53,8 @@ public LocalDevelopmentUserNameRegistryDecorator(IUserNameRegistry userNameRegis public bool TryGetCurrentUserPid([NotNullWhen(true)] out string? userPid) => _userNameRegistry.TryGetCurrentUserPid(out userPid); - public async Task GetCurrentUserName(string personalIdentificationNumber, CancellationToken cancellationToken) - => await Task.FromResult("Local Development User"); + public Task GetUserInformation(CancellationToken cancellationToken) + => _userNameRegistry.TryGetCurrentUserPid(out var userPid) + ? Task.FromResult(new UserInformation(userPid!, LocalDevelopmentUserPid)) + : throw new UnreachableException(); } diff --git a/src/Digdir.Domain.Dialogporten.Application/Externals/IDialogDbContext.cs b/src/Digdir.Domain.Dialogporten.Application/Externals/IDialogDbContext.cs index daa937047..120a6ca4d 100644 --- a/src/Digdir.Domain.Dialogporten.Application/Externals/IDialogDbContext.cs +++ b/src/Digdir.Domain.Dialogporten.Application/Externals/IDialogDbContext.cs @@ -24,7 +24,7 @@ public interface IDialogDbContext DbSet OutboxMessages { get; } DbSet OutboxMessageConsumers { get; } - DbSet DialogSeenLog { get; } + DbSet DialogSeenLog { get; } /// /// Validate a property on the using a lambda diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogSeenLogs/Queries/Get/GetDialogSeenLogDto.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogSeenLogs/Queries/Get/GetDialogSeenLogDto.cs new file mode 100644 index 000000000..8d5def578 --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogSeenLogs/Queries/Get/GetDialogSeenLogDto.cs @@ -0,0 +1,13 @@ +namespace Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.DialogSeenLogs.Queries.Get; + +public class GetDialogSeenLogDto +{ + public Guid Id { get; set; } + public DateTimeOffset SeenAt { get; set; } + + public string EndUserIdHash { get; set; } = null!; + + public string? EndUserName { get; set; } + + public bool IsCurrentEndUser { get; set; } +} diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogSeenLogs/Queries/Get/GetDialogSeenLogQuery.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogSeenLogs/Queries/Get/GetDialogSeenLogQuery.cs new file mode 100644 index 000000000..8ecbe1f18 --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogSeenLogs/Queries/Get/GetDialogSeenLogQuery.cs @@ -0,0 +1,91 @@ +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.DialogSeenLogs.Queries.Get; + +public sealed class GetDialogSeenLogQuery : IRequest +{ + public Guid DialogId { get; set; } + public Guid SeenLogId { get; set; } +} + +[GenerateOneOf] +public partial class GetDialogSeenLogResult : OneOfBase; + +internal sealed class GetDialogSeenLogQueryHandler : IRequestHandler +{ + private readonly IMapper _mapper; + private readonly IDialogDbContext _dbContext; + private readonly IAltinnAuthorization _altinnAuthorization; + private readonly IStringHasher _stringHasher; + private readonly IUserNameRegistry _userNameRegistry; + + public GetDialogSeenLogQueryHandler( + IMapper mapper, + IDialogDbContext dbContext, + IAltinnAuthorization altinnAuthorization, + IStringHasher stringHasher, + IUserNameRegistry userNameRegistry) + { + _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper)); + _dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext)); + _altinnAuthorization = altinnAuthorization ?? throw new ArgumentNullException(nameof(altinnAuthorization)); + _stringHasher = stringHasher ?? throw new ArgumentNullException(nameof(stringHasher)); + _userNameRegistry = userNameRegistry ?? throw new ArgumentNullException(nameof(userNameRegistry)); + } + + public async Task Handle(GetDialogSeenLogQuery request, + CancellationToken cancellationToken) + { + if (!_userNameRegistry.TryGetCurrentUserPid(out var userPid)) + { + return new Forbidden("No valid user pid found."); + } + + var dialog = await _dbContext.Dialogs + .AsNoTracking() + .Include(x => x.SeenLog.Where(x => x.Id == request.SeenLogId)) + .IgnoreQueryFilters() + .FirstOrDefaultAsync(x => x.Id == request.DialogId, + cancellationToken: cancellationToken); + + if (dialog is null) + { + return new EntityNotFound(request.DialogId); + } + + var authorizationResult = await _altinnAuthorization.GetDialogDetailsAuthorization( + dialog, + cancellationToken); + + // If we cannot read the dialog at all, we don't allow access to the seen log + if (!authorizationResult.HasReadAccessToMainResource()) + { + return new EntityNotFound(request.DialogId); + } + + if (dialog.Deleted) + { + return new EntityDeleted(request.DialogId); + } + + var seenLog = dialog.SeenLog.FirstOrDefault(); + if (seenLog is null) + { + return new EntityNotFound(request.SeenLogId); + } + + var dto = _mapper.Map(seenLog); + dto.IsCurrentEndUser = userPid == seenLog.EndUserId; + dto.EndUserIdHash = _stringHasher.Hash(seenLog.EndUserId); + + return dto; + } +} diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogSeenLogs/Queries/Get/MappingProfile.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogSeenLogs/Queries/Get/MappingProfile.cs new file mode 100644 index 000000000..1bfcae98f --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogSeenLogs/Queries/Get/MappingProfile.cs @@ -0,0 +1,13 @@ +using AutoMapper; +using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities; + +namespace Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.DialogSeenLogs.Queries.Get; + +public class MappingProfile : Profile +{ + public MappingProfile() + { + CreateMap() + .ForMember(dest => dest.SeenAt, opt => opt.MapFrom(src => src.CreatedAt)); + } +} diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogSeenLogs/Queries/Search/MappingProfile.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogSeenLogs/Queries/Search/MappingProfile.cs new file mode 100644 index 000000000..f57b3f427 --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogSeenLogs/Queries/Search/MappingProfile.cs @@ -0,0 +1,13 @@ +using AutoMapper; +using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities; + +namespace Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.DialogSeenLogs.Queries.Search; + +public class MappingProfile : Profile +{ + public MappingProfile() + { + CreateMap() + .ForMember(dest => dest.SeenAt, opt => opt.MapFrom(src => src.CreatedAt)); + } +} diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogSeenLogs/Queries/Search/SearchDialogSeenLogDto.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogSeenLogs/Queries/Search/SearchDialogSeenLogDto.cs new file mode 100644 index 000000000..a09d24123 --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogSeenLogs/Queries/Search/SearchDialogSeenLogDto.cs @@ -0,0 +1,13 @@ +namespace Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.DialogSeenLogs.Queries.Search; + +public class SearchDialogSeenLogDto +{ + public Guid Id { get; set; } + public DateTimeOffset SeenAt { get; set; } + + public string EndUserIdHash { get; set; } = null!; + + public string? EndUserName { get; set; } + + public bool IsCurrentEndUser { get; set; } +} diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogSeenLogs/Queries/Search/SearchDialogSeenLogQuery.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogSeenLogs/Queries/Search/SearchDialogSeenLogQuery.cs new file mode 100644 index 000000000..ebeba3c4f --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogSeenLogs/Queries/Search/SearchDialogSeenLogQuery.cs @@ -0,0 +1,88 @@ +using AutoMapper; +using Digdir.Domain.Dialogporten.Application.Common.ReturnTypes; +using Digdir.Domain.Dialogporten.Application.Externals.AltinnAuthorization; +using Digdir.Domain.Dialogporten.Application.Externals; +using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities; +using MediatR; +using OneOf; +using Microsoft.EntityFrameworkCore; +using Digdir.Domain.Dialogporten.Application.Common; + +namespace Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.DialogSeenLogs.Queries.Search; + +public sealed class SearchDialogSeenLogQuery : IRequest +{ + public Guid DialogId { get; set; } +} + +[GenerateOneOf] +public partial class SearchDialogSeenLogResult : OneOfBase, EntityNotFound, EntityDeleted, Forbidden>; + +internal sealed class SearchDialogSeenLogQueryHandler : IRequestHandler +{ + private readonly IDialogDbContext _db; + private readonly IMapper _mapper; + private readonly IAltinnAuthorization _altinnAuthorization; + private readonly IUserNameRegistry _userNameRegistry; + private readonly IStringHasher _stringHasher; + + public SearchDialogSeenLogQueryHandler( + IDialogDbContext db, + IMapper mapper, + IAltinnAuthorization altinnAuthorization, + IUserNameRegistry userNameRegistry, + IStringHasher stringHasher) + { + _db = db ?? throw new ArgumentNullException(nameof(db)); + _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper)); + _altinnAuthorization = altinnAuthorization ?? throw new ArgumentNullException(nameof(altinnAuthorization)); + _userNameRegistry = userNameRegistry ?? throw new ArgumentNullException(nameof(userNameRegistry)); + _stringHasher = stringHasher ?? throw new ArgumentNullException(nameof(stringHasher)); + } + + public async Task Handle(SearchDialogSeenLogQuery request, CancellationToken cancellationToken) + { + if (!_userNameRegistry.TryGetCurrentUserPid(out var userPid)) + { + return new Forbidden("No valid user pid found."); + } + + var dialog = await _db.Dialogs + .AsNoTracking() + .Include(x => x.SeenLog) + .ThenInclude(x => x.Via!.Localizations) + .IgnoreQueryFilters() + .FirstOrDefaultAsync(x => x.Id == request.DialogId, + cancellationToken: cancellationToken); + + if (dialog is null) + { + return new EntityNotFound(request.DialogId); + } + + var authorizationResult = await _altinnAuthorization.GetDialogDetailsAuthorization( + dialog, + cancellationToken); + + // If we cannot read the dialog at all, we don't allow access to the seen log + if (!authorizationResult.HasReadAccessToMainResource()) + { + return new EntityNotFound(request.DialogId); + } + + if (dialog.Deleted) + { + return new EntityDeleted(request.DialogId); + } + + return dialog.SeenLog + .Select(x => + { + var dto = _mapper.Map(x); + dto.IsCurrentEndUser = x.EndUserId == userPid; + dto.EndUserIdHash = _stringHasher.Hash(x.EndUserId); + return dto; + }) + .ToList(); + } +} diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/Get/GetDialogDto.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/Get/GetDialogDto.cs index e4056533f..6f1d94525 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 @@ -33,10 +33,11 @@ public sealed class GetDialogDto public List GuiActions { get; set; } = []; public List ApiActions { get; set; } = []; public List Activities { get; set; } = []; - public List SeenSinceLastUpdate { get; set; } = []; + public List SeenSinceLastUpdate { get; set; } = []; + } -public class GetDialogDialogSeenRecordDto +public class GetDialogDialogSeenLogDto { public Guid Id { get; set; } public DateTimeOffset CreatedAt { get; set; } diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/Get/GetDialogQuery.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/Get/GetDialogQuery.cs index b5ade7164..1ac23bad1 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 @@ -53,11 +53,15 @@ public GetDialogQueryHandler( public async Task Handle(GetDialogQuery request, CancellationToken cancellationToken) { - if (!_userNameRegistry.TryGetCurrentUserPid(out var userPid)) + var userInformation = await _userNameRegistry.GetUserInformation(cancellationToken); + + if (userInformation is null) { return new Forbidden("No valid user pid found."); } + var (userPid, userName) = userInformation; + // This query could be written without all the includes as ProjectTo will do the job for us. // However, we need to guarantee an order for sub resources of the dialog aggregate. // This is to ensure that the get is consistent, and that PATCH in the API presentation @@ -101,7 +105,6 @@ public async Task Handle(GetDialogQuery request, CancellationTo return new EntityDeleted(request.DialogId); } - var userName = await _userNameRegistry.GetCurrentUserName(userPid, cancellationToken); // TODO: What if name lookup fails // https://github.com/digdir/dialogporten/issues/387 dialog.UpdateSeenAt(userPid, userName); @@ -120,7 +123,7 @@ public async Task Handle(GetDialogQuery request, CancellationTo dialogDto.SeenSinceLastUpdate = dialog.SeenLog .Select(log => { - var logDto = _mapper.Map(log); + var logDto = _mapper.Map(log); logDto.IsCurrentEndUser = log.EndUserId == userPid; logDto.EndUserIdHash = _stringHasher.Hash(log.EndUserId); return logDto; 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 44528f860..810354e63 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 @@ -16,7 +16,7 @@ public MappingProfile() .ForMember(dest => dest.Status, opt => opt.MapFrom(src => src.StatusId)) .ForMember(dest => dest.SeenSinceLastUpdate, opt => opt.Ignore()); - CreateMap(); + CreateMap(); CreateMap() .ForMember(dest => dest.Type, opt => opt.MapFrom(src => src.TypeId)); 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 8987b0c99..d32ede1c4 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 @@ -29,7 +29,7 @@ public MappingProfile() CreateMap() .ForMember(dest => dest.Type, opt => opt.MapFrom(src => src.TypeId)); - CreateMap() + CreateMap() .ForMember(dest => dest.EndUserIdHash, opt => opt.MapFrom(src => src.EndUserId)); CreateMap() diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/Search/SearchDialogDto.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/Search/SearchDialogDto.cs index fafb9fff0..9f335989c 100644 --- a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/Search/SearchDialogDto.cs +++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/Search/SearchDialogDto.cs @@ -23,10 +23,10 @@ public sealed class SearchDialogDto public SearchDialogDialogActivityDto? LatestActivity { get; set; } public List Content { get; set; } = []; - public List SeenSinceLastUpdate { get; set; } = []; + public List SeenSinceLastUpdate { get; set; } = []; } -public class SearchDialogDialogSeenRecordDto +public class SearchDialogDialogSeenLogDto { public Guid Id { get; set; } public DateTimeOffset CreatedAt { get; set; } diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Queries/Get/GetDialogDto.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Queries/Get/GetDialogDto.cs index caf584dfd..2fef589c8 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 @@ -35,10 +35,10 @@ public sealed class GetDialogDto public List GuiActions { get; set; } = []; public List ApiActions { get; set; } = []; public List Activities { get; set; } = []; - public List SeenSinceLastUpdate { get; set; } = []; + public List SeenSinceLastUpdate { get; set; } = []; } -public class GetDialogDialogSeenRecordDto +public class GetDialogDialogSeenLogDto { public Guid Id { get; set; } public DateTimeOffset CreatedAt { get; set; } diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Queries/Get/GetDialogQuery.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Queries/Get/GetDialogQuery.cs index a946a0a19..3cb2d6b1e 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 @@ -79,7 +79,7 @@ public async Task Handle(GetDialogQuery request, CancellationTo dialogDto.SeenSinceLastUpdate = dialog.SeenLog .Select(log => { - var logDto = _mapper.Map(log); + var logDto = _mapper.Map(log); // TODO: Set when #386 is implemented // logDto.IsAuthenticatedUser = log.EndUserId == userPid; logDto.EndUserIdHash = _stringHasher.Hash(log.EndUserId); 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 1500a9f9b..7ed4ae3c7 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,7 @@ public MappingProfile() .ForMember(dest => dest.Status, opt => opt.MapFrom(src => src.StatusId)) .ForMember(dest => dest.SeenSinceLastUpdate, opt => opt.Ignore()); - CreateMap(); + CreateMap(); CreateMap() .ForMember(dest => dest.Type, opt => opt.MapFrom(src => src.TypeId)); 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 09f3a90dc..580cf9552 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 @@ -16,7 +16,7 @@ public MappingProfile() .ForMember(dest => dest.Content, opt => opt.MapFrom(src => src.Content.Where(x => x.Type.OutputInList))) .ForMember(dest => dest.Status, opt => opt.MapFrom(src => src.StatusId)); - CreateMap() + CreateMap() .ForMember(dest => dest.EndUserIdHash, opt => opt.MapFrom(src => src.EndUserId)); CreateMap(); diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Queries/Search/SearchDialogDto.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Queries/Search/SearchDialogDto.cs index 2e4135f04..b3c632c7d 100644 --- a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Queries/Search/SearchDialogDto.cs +++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Queries/Search/SearchDialogDto.cs @@ -21,10 +21,10 @@ public sealed class SearchDialogDto public DialogStatus.Values Status { get; set; } public List Content { get; set; } = []; - public List SeenSinceLastUpdate { get; set; } = []; + public List SeenSinceLastUpdate { get; set; } = []; } -public class SearchDialogDialogSeenRecordDto +public class SearchDialogDialogSeenLogDto { public Guid Id { get; set; } public DateTimeOffset CreatedAt { get; set; } diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Queries/Search/SearchDialogQuery.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Queries/Search/SearchDialogQuery.cs index 724c504d4..9e438105f 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 @@ -59,7 +59,6 @@ public sealed class SearchDialogQuery : SortablePaginationParameter public DateTimeOffset? CreatedBefore { get; init; } - /// /// Only return dialogs updated after this date /// diff --git a/src/Digdir.Domain.Dialogporten.Domain/Dialogs/Entities/DialogEntity.cs b/src/Digdir.Domain.Dialogporten.Domain/Dialogs/Entities/DialogEntity.cs index 4888be2ca..a4ad71d6c 100644 --- a/src/Digdir.Domain.Dialogporten.Domain/Dialogs/Entities/DialogEntity.cs +++ b/src/Digdir.Domain.Dialogporten.Domain/Dialogs/Entities/DialogEntity.cs @@ -57,7 +57,7 @@ public class DialogEntity : public List Activities { get; set; } = []; [AggregateChild] - public List SeenLog { get; set; } = []; + public List SeenLog { get; set; } = []; public void SoftDelete() { diff --git a/src/Digdir.Domain.Dialogporten.Domain/Dialogs/Entities/DialogSeenRecord.cs b/src/Digdir.Domain.Dialogporten.Domain/Dialogs/Entities/DialogSeenLog.cs similarity index 86% rename from src/Digdir.Domain.Dialogporten.Domain/Dialogs/Entities/DialogSeenRecord.cs rename to src/Digdir.Domain.Dialogporten.Domain/Dialogs/Entities/DialogSeenLog.cs index a6e751589..547881e6c 100644 --- a/src/Digdir.Domain.Dialogporten.Domain/Dialogs/Entities/DialogSeenRecord.cs +++ b/src/Digdir.Domain.Dialogporten.Domain/Dialogs/Entities/DialogSeenLog.cs @@ -4,7 +4,7 @@ namespace Digdir.Domain.Dialogporten.Domain.Dialogs.Entities; -public class DialogSeenRecord : IImmutableEntity +public class DialogSeenLog : IImmutableEntity { public Guid Id { get; set; } public DateTimeOffset CreatedAt { get; set; } @@ -23,6 +23,6 @@ public class DialogSeenRecord : IImmutableEntity public class DialogSeenLogVia : LocalizationSet { - public DialogSeenRecord DialogSeenLog { get; set; } = null!; + public DialogSeenLog DialogSeenLog { get; set; } = null!; public Guid DialogSeenLogId { get; set; } } diff --git a/src/Digdir.Domain.Dialogporten.Infrastructure/Persistence/DialogDbContext.cs b/src/Digdir.Domain.Dialogporten.Infrastructure/Persistence/DialogDbContext.cs index c7f3057ef..73129fd99 100644 --- a/src/Digdir.Domain.Dialogporten.Infrastructure/Persistence/DialogDbContext.cs +++ b/src/Digdir.Domain.Dialogporten.Infrastructure/Persistence/DialogDbContext.cs @@ -30,7 +30,7 @@ public DialogDbContext(DbContextOptions options) : base(options public DbSet DialogElementUrls => Set(); public DbSet DialogGuiActionTypes => Set(); public DbSet DialogActivityTypes => Set(); - public DbSet DialogSeenLog => Set(); + public DbSet DialogSeenLog => Set(); public DbSet DialogContentTypes => Set(); public DbSet DialogContent => Set(); diff --git a/src/Digdir.Domain.Dialogporten.Infrastructure/Persistence/Migrations/20240408113451_RenameFromSeenRecordToSeenLog.Designer.cs b/src/Digdir.Domain.Dialogporten.Infrastructure/Persistence/Migrations/20240408113451_RenameFromSeenRecordToSeenLog.Designer.cs new file mode 100644 index 000000000..4b56e2604 --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.Infrastructure/Persistence/Migrations/20240408113451_RenameFromSeenRecordToSeenLog.Designer.cs @@ -0,0 +1,1287 @@ +// +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("20240408113451_RenameFromSeenRecordToSeenLog")] + partial class RenameFromSeenRecordToSeenLog + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.3") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actions.DialogApiAction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasDefaultValueSql("gen_random_uuid()"); + + b.Property("Action") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("AuthorizationAttribute") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("current_timestamp at time zone 'utc'"); + + b.Property("DialogElementId") + .HasColumnType("uuid"); + + 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("DialogElementId"); + + 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("IsBackChannel") + .HasColumnType("boolean"); + + b.Property("IsDeleteAction") + .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("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("DialogElementId") + .HasColumnType("uuid"); + + b.Property("DialogId") + .HasColumnType("uuid"); + + b.Property("ExtendedType") + .HasMaxLength(1023) + .HasColumnType("character varying(1023)"); + + b.Property("RelatedActivityId") + .HasColumnType("uuid"); + + b.Property("TypeId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("DialogElementId"); + + b.HasIndex("DialogId"); + + b.HasIndex("RelatedActivityId"); + + b.HasIndex("TypeId"); + + b.ToTable("DialogActivity"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities.DialogActivityType", b => + { + b.Property("Id") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.ToTable("DialogActivityType"); + + b.HasData( + new + { + Id = 1, + Name = "Submission" + }, + new + { + Id = 2, + Name = "Feedback" + }, + new + { + Id = 3, + Name = "Information" + }, + new + { + Id = 4, + Name = "Error" + }, + new + { + Id = 5, + Name = "Closed" + }, + new + { + Id = 7, + Name = "Forwarded" + }); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Content.DialogContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasDefaultValueSql("gen_random_uuid()"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("current_timestamp at time zone 'utc'"); + + b.Property("DialogId") + .HasColumnType("uuid"); + + b.Property("TypeId") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("current_timestamp at time zone 'utc'"); + + b.HasKey("Id"); + + b.HasIndex("TypeId"); + + b.HasIndex("DialogId", "TypeId") + .IsUnique(); + + b.ToTable("DialogContent"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Content.DialogContentType", b => + { + b.Property("Id") + .HasColumnType("integer"); + + b.Property("MaxLength") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("OutputInList") + .HasColumnType("boolean"); + + b.Property("RenderAsHtml") + .HasColumnType("boolean"); + + b.Property("Required") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("DialogContentType"); + + b.HasData( + new + { + Id = 1, + MaxLength = 255, + Name = "Title", + OutputInList = true, + RenderAsHtml = false, + Required = true + }, + new + { + Id = 2, + MaxLength = 255, + Name = "SenderName", + OutputInList = true, + RenderAsHtml = false, + Required = false + }, + new + { + Id = 3, + MaxLength = 255, + Name = "Summary", + OutputInList = true, + RenderAsHtml = false, + Required = true + }, + new + { + Id = 4, + MaxLength = 1023, + Name = "AdditionalInfo", + OutputInList = false, + RenderAsHtml = true, + Required = false + }, + new + { + Id = 5, + MaxLength = 20, + Name = "ExtendedStatus", + OutputInList = true, + RenderAsHtml = false, + Required = false + }); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.DialogEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasDefaultValueSql("gen_random_uuid()"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("current_timestamp at time zone 'utc'"); + + b.Property("Deleted") + .HasColumnType("boolean"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DueAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ExpiresAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ExtendedStatus") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ExternalReference") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Org") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)") + .UseCollation("C"); + + b.Property("Party") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)") + .UseCollation("C"); + + b.Property("Progress") + .HasColumnType("integer"); + + b.Property("Revision") + .IsConcurrencyToken() + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasDefaultValueSql("gen_random_uuid()"); + + b.Property("ServiceResource") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)") + .UseCollation("C"); + + b.Property("StatusId") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("current_timestamp at time zone 'utc'"); + + b.Property("VisibleFrom") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("DueAt"); + + b.HasIndex("Org"); + + b.HasIndex("Party"); + + b.HasIndex("ServiceResource"); + + b.HasIndex("StatusId"); + + b.HasIndex("UpdatedAt"); + + b.ToTable("Dialog", (string)null); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.DialogSearchTag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasDefaultValueSql("gen_random_uuid()"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("current_timestamp at time zone 'utc'"); + + b.Property("DialogId") + .HasColumnType("uuid"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.HasKey("Id"); + + b.HasIndex("DialogId", "Value") + .IsUnique(); + + b.ToTable("DialogSearchTag"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.DialogSeenLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasDefaultValueSql("gen_random_uuid()"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("current_timestamp at time zone 'utc'"); + + b.Property("DialogId") + .HasColumnType("uuid"); + + b.Property("EndUserId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("EndUserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("DialogId"); + + b.ToTable("DialogSeenLog"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.DialogStatus", b => + { + b.Property("Id") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.ToTable("DialogStatus"); + + b.HasData( + new + { + Id = 1, + Name = "New" + }, + new + { + Id = 2, + Name = "InProgress" + }, + new + { + Id = 3, + Name = "Waiting" + }, + new + { + Id = 4, + Name = "Signing" + }, + new + { + Id = 5, + Name = "Cancelled" + }, + new + { + Id = 6, + Name = "Completed" + }); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Elements.DialogElement", 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("ExternalReference") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("RelatedDialogElementId") + .HasColumnType("uuid"); + + b.Property("Type") + .HasMaxLength(1023) + .HasColumnType("character varying(1023)"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("current_timestamp at time zone 'utc'"); + + b.HasKey("Id"); + + b.HasIndex("DialogId"); + + b.HasIndex("RelatedDialogElementId"); + + b.ToTable("DialogElement"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Elements.DialogElementUrl", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasDefaultValueSql("gen_random_uuid()"); + + b.Property("ConsumerTypeId") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("current_timestamp at time zone 'utc'"); + + b.Property("DialogElementId") + .HasColumnType("uuid"); + + b.Property("MimeType") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("current_timestamp at time zone 'utc'"); + + b.Property("Url") + .IsRequired() + .HasMaxLength(1023) + .HasColumnType("character varying(1023)"); + + b.HasKey("Id"); + + b.HasIndex("ConsumerTypeId"); + + b.HasIndex("DialogElementId"); + + b.ToTable("DialogElementUrl"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Elements.DialogElementUrlConsumerType", b => + { + b.Property("Id") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.ToTable("DialogElementUrlConsumerType"); + + b.HasData( + new + { + Id = 1, + Name = "Gui" + }, + new + { + Id = 2, + Name = "Api" + }); + }); + + 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("CultureCode") + .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", "CultureCode"); + + 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("Discriminator").HasValue("LocalizationSet"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Outboxes.OutboxMessage", b => + { + b.Property("EventId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("EventPayload") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("EventType") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("EventId"); + + b.ToTable("OutboxMessage"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Outboxes.OutboxMessageConsumer", b => + { + b.Property("EventId") + .HasColumnType("uuid"); + + b.Property("ConsumerName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("EventId", "ConsumerName"); + + b.ToTable("OutboxMessageConsumer"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actions.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.ToTable("LocalizationSet", t => + { + t.Property("ActivityId") + .HasColumnName("DialogActivityDescription_ActivityId"); + }); + + b.HasDiscriminator().HasValue("DialogActivityDescription"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities.DialogActivityPerformedBy", b => + { + b.HasBaseType("Digdir.Domain.Dialogporten.Domain.Localizations.LocalizationSet"); + + b.Property("ActivityId") + .HasColumnType("uuid"); + + b.HasIndex("ActivityId") + .IsUnique(); + + b.HasDiscriminator().HasValue("DialogActivityPerformedBy"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Content.DialogContentValue", b => + { + b.HasBaseType("Digdir.Domain.Dialogporten.Domain.Localizations.LocalizationSet"); + + b.Property("DialogContentId") + .HasColumnType("uuid"); + + b.HasIndex("DialogContentId") + .IsUnique(); + + b.HasDiscriminator().HasValue("DialogContentValue"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.DialogSeenLogVia", b => + { + b.HasBaseType("Digdir.Domain.Dialogporten.Domain.Localizations.LocalizationSet"); + + b.Property("DialogSeenLogId") + .HasColumnType("uuid"); + + b.HasIndex("DialogSeenLogId") + .IsUnique(); + + b.HasDiscriminator().HasValue("DialogSeenLogVia"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Elements.DialogElementDisplayName", b => + { + b.HasBaseType("Digdir.Domain.Dialogporten.Domain.Localizations.LocalizationSet"); + + b.Property("ElementId") + .HasColumnType("uuid"); + + b.HasIndex("ElementId") + .IsUnique(); + + b.HasDiscriminator().HasValue("DialogElementDisplayName"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actions.DialogApiAction", b => + { + b.HasOne("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Elements.DialogElement", "DialogElement") + .WithMany("ApiActions") + .HasForeignKey("DialogElementId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.DialogEntity", "Dialog") + .WithMany("ApiActions") + .HasForeignKey("DialogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Dialog"); + + b.Navigation("DialogElement"); + }); + + 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.Dialogs.Entities.Actions.DialogGuiActionPriority", "Priority") + .WithMany() + .HasForeignKey("PriorityId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Dialog"); + + b.Navigation("Priority"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities.DialogActivity", b => + { + b.HasOne("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Elements.DialogElement", "DialogElement") + .WithMany("Activities") + .HasForeignKey("DialogElementId") + .OnDelete(DeleteBehavior.SetNull); + + 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.Activities.DialogActivityType", "Type") + .WithMany() + .HasForeignKey("TypeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Dialog"); + + b.Navigation("DialogElement"); + + b.Navigation("RelatedActivity"); + + b.Navigation("Type"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Content.DialogContent", b => + { + b.HasOne("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.DialogEntity", "Dialog") + .WithMany("Content") + .HasForeignKey("DialogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Content.DialogContentType", "Type") + .WithMany() + .HasForeignKey("TypeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Dialog"); + + b.Navigation("Type"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.DialogEntity", b => + { + b.HasOne("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.DialogStatus", "Status") + .WithMany() + .HasForeignKey("StatusId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Status"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.DialogSearchTag", b => + { + b.HasOne("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.DialogEntity", "Dialog") + .WithMany("SearchTags") + .HasForeignKey("DialogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Dialog"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.DialogSeenLog", b => + { + b.HasOne("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.DialogEntity", "Dialog") + .WithMany("SeenLog") + .HasForeignKey("DialogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Dialog"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Elements.DialogElement", b => + { + b.HasOne("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.DialogEntity", "Dialog") + .WithMany("Elements") + .HasForeignKey("DialogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Elements.DialogElement", "RelatedDialogElement") + .WithMany("RelatedDialogElements") + .HasForeignKey("RelatedDialogElementId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Dialog"); + + b.Navigation("RelatedDialogElement"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Elements.DialogElementUrl", b => + { + b.HasOne("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Elements.DialogElementUrlConsumerType", "ConsumerType") + .WithMany() + .HasForeignKey("ConsumerTypeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Elements.DialogElement", "DialogElement") + .WithMany("Urls") + .HasForeignKey("DialogElementId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ConsumerType"); + + b.Navigation("DialogElement"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Localizations.Localization", b => + { + b.HasOne("Digdir.Domain.Dialogporten.Domain.Localizations.LocalizationSet", "LocalizationSet") + .WithMany("Localizations") + .HasForeignKey("LocalizationSetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("LocalizationSet"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Outboxes.OutboxMessageConsumer", b => + { + b.HasOne("Digdir.Domain.Dialogporten.Domain.Outboxes.OutboxMessage", "OutboxMessage") + .WithMany("OutboxMessageConsumers") + .HasForeignKey("EventId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("OutboxMessage"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actions.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.Activities.DialogActivityPerformedBy", b => + { + b.HasOne("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities.DialogActivity", "Activity") + .WithOne("PerformedBy") + .HasForeignKey("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities.DialogActivityPerformedBy", "ActivityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Activity"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Content.DialogContentValue", b => + { + b.HasOne("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Content.DialogContent", "DialogContent") + .WithOne("Value") + .HasForeignKey("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Content.DialogContentValue", "DialogContentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DialogContent"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.DialogSeenLogVia", b => + { + b.HasOne("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.DialogSeenLog", "DialogSeenLog") + .WithOne("Via") + .HasForeignKey("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.DialogSeenLogVia", "DialogSeenLogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DialogSeenLog"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Elements.DialogElementDisplayName", b => + { + b.HasOne("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Elements.DialogElement", "Element") + .WithOne("DisplayName") + .HasForeignKey("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Elements.DialogElementDisplayName", "ElementId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Element"); + }); + + 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("Title"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities.DialogActivity", b => + { + b.Navigation("Description"); + + b.Navigation("PerformedBy"); + + b.Navigation("RelatedActivities"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Content.DialogContent", b => + { + b.Navigation("Value") + .IsRequired(); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.DialogEntity", b => + { + b.Navigation("Activities"); + + b.Navigation("ApiActions"); + + b.Navigation("Content"); + + b.Navigation("Elements"); + + b.Navigation("GuiActions"); + + b.Navigation("SearchTags"); + + b.Navigation("SeenLog"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.DialogSeenLog", b => + { + b.Navigation("Via"); + }); + + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Elements.DialogElement", b => + { + b.Navigation("Activities"); + + b.Navigation("ApiActions"); + + b.Navigation("DisplayName"); + + b.Navigation("RelatedDialogElements"); + + b.Navigation("Urls"); + }); + + 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/20240408113451_RenameFromSeenRecordToSeenLog.cs b/src/Digdir.Domain.Dialogporten.Infrastructure/Persistence/Migrations/20240408113451_RenameFromSeenRecordToSeenLog.cs new file mode 100644 index 000000000..7d417e416 --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.Infrastructure/Persistence/Migrations/20240408113451_RenameFromSeenRecordToSeenLog.cs @@ -0,0 +1,101 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Digdir.Domain.Dialogporten.Infrastructure.Persistence.Migrations +{ + /// + public partial class RenameFromSeenRecordToSeenLog : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_LocalizationSet_DialogSeenRecord_DialogSeenLogId", + table: "LocalizationSet"); + + migrationBuilder.DropTable( + name: "DialogSeenRecord"); + + migrationBuilder.CreateTable( + name: "DialogSeenLog", + 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'"), + EndUserId = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), + EndUserName = table.Column(type: "character varying(255)", maxLength: 255, nullable: true), + DialogId = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_DialogSeenLog", x => x.Id); + table.ForeignKey( + name: "FK_DialogSeenLog_Dialog_DialogId", + column: x => x.DialogId, + principalTable: "Dialog", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_DialogSeenLog_DialogId", + table: "DialogSeenLog", + column: "DialogId"); + + migrationBuilder.AddForeignKey( + name: "FK_LocalizationSet_DialogSeenLog_DialogSeenLogId", + table: "LocalizationSet", + column: "DialogSeenLogId", + principalTable: "DialogSeenLog", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_LocalizationSet_DialogSeenLog_DialogSeenLogId", + table: "LocalizationSet"); + + migrationBuilder.DropTable( + name: "DialogSeenLog"); + + migrationBuilder.CreateTable( + name: "DialogSeenRecord", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"), + DialogId = table.Column(type: "uuid", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "current_timestamp at time zone 'utc'"), + EndUserId = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), + EndUserName = table.Column(type: "character varying(255)", maxLength: 255, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_DialogSeenRecord", x => x.Id); + table.ForeignKey( + name: "FK_DialogSeenRecord_Dialog_DialogId", + column: x => x.DialogId, + principalTable: "Dialog", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_DialogSeenRecord_DialogId", + table: "DialogSeenRecord", + column: "DialogId"); + + migrationBuilder.AddForeignKey( + name: "FK_LocalizationSet_DialogSeenRecord_DialogSeenLogId", + table: "LocalizationSet", + column: "DialogSeenLogId", + principalTable: "DialogSeenRecord", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + } +} diff --git a/src/Digdir.Domain.Dialogporten.Infrastructure/Persistence/Migrations/DialogDbContextModelSnapshot.cs b/src/Digdir.Domain.Dialogporten.Infrastructure/Persistence/Migrations/DialogDbContextModelSnapshot.cs index 589455e6f..e49fa840e 100644 --- a/src/Digdir.Domain.Dialogporten.Infrastructure/Persistence/Migrations/DialogDbContextModelSnapshot.cs +++ b/src/Digdir.Domain.Dialogporten.Infrastructure/Persistence/Migrations/DialogDbContextModelSnapshot.cs @@ -60,7 +60,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("DialogId"); - b.ToTable("DialogApiAction", (string)null); + b.ToTable("DialogApiAction"); }); modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actions.DialogApiActionEndpoint", b => @@ -119,7 +119,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("HttpMethodId"); - b.ToTable("DialogApiActionEndpoint", (string)null); + b.ToTable("DialogApiActionEndpoint"); }); modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actions.DialogGuiAction", b => @@ -171,7 +171,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("PriorityId"); - b.ToTable("DialogGuiAction", (string)null); + b.ToTable("DialogGuiAction"); }); modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actions.DialogGuiActionPriority", b => @@ -186,7 +186,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("Id"); - b.ToTable("DialogGuiActionPriority", (string)null); + b.ToTable("DialogGuiActionPriority"); b.HasData( new @@ -244,7 +244,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("TypeId"); - b.ToTable("DialogActivity", (string)null); + b.ToTable("DialogActivity"); }); modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities.DialogActivityType", b => @@ -259,7 +259,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("Id"); - b.ToTable("DialogActivityType", (string)null); + b.ToTable("DialogActivityType"); b.HasData( new @@ -324,7 +324,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("DialogId", "TypeId") .IsUnique(); - b.ToTable("DialogContent", (string)null); + b.ToTable("DialogContent"); }); modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Content.DialogContentType", b => @@ -351,7 +351,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("Id"); - b.ToTable("DialogContentType", (string)null); + b.ToTable("DialogContentType"); b.HasData( new @@ -515,10 +515,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("DialogId", "Value") .IsUnique(); - b.ToTable("DialogSearchTag", (string)null); + b.ToTable("DialogSearchTag"); }); - modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.DialogSeenRecord", b => + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.DialogSeenLog", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -546,7 +546,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("DialogId"); - b.ToTable("DialogSeenRecord", (string)null); + b.ToTable("DialogSeenLog"); }); modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.DialogStatus", b => @@ -561,7 +561,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("Id"); - b.ToTable("DialogStatus", (string)null); + b.ToTable("DialogStatus"); b.HasData( new @@ -637,7 +637,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("RelatedDialogElementId"); - b.ToTable("DialogElement", (string)null); + b.ToTable("DialogElement"); }); modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Elements.DialogElementUrl", b => @@ -678,7 +678,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("DialogElementId"); - b.ToTable("DialogElementUrl", (string)null); + b.ToTable("DialogElementUrl"); }); modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Elements.DialogElementUrlConsumerType", b => @@ -693,7 +693,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("Id"); - b.ToTable("DialogElementUrlConsumerType", (string)null); + b.ToTable("DialogElementUrlConsumerType"); b.HasData( new @@ -720,7 +720,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("Id"); - b.ToTable("HttpVerb", (string)null); + b.ToTable("HttpVerb"); b.HasData( new @@ -796,7 +796,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("LocalizationSetId", "CultureCode"); - b.ToTable("Localization", (string)null); + b.ToTable("Localization"); }); modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Localizations.LocalizationSet", b => @@ -818,7 +818,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("Id"); - b.ToTable("LocalizationSet", (string)null); + b.ToTable("LocalizationSet"); b.HasDiscriminator("Discriminator").HasValue("LocalizationSet"); @@ -842,7 +842,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("EventId"); - b.ToTable("OutboxMessage", (string)null); + b.ToTable("OutboxMessage"); }); modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Outboxes.OutboxMessageConsumer", b => @@ -856,7 +856,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("EventId", "ConsumerName"); - b.ToTable("OutboxMessageConsumer", (string)null); + b.ToTable("OutboxMessageConsumer"); }); modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actions.DialogGuiActionTitle", b => @@ -882,7 +882,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("ActivityId") .IsUnique(); - b.ToTable("LocalizationSet", null, t => + b.ToTable("LocalizationSet", t => { t.Property("ActivityId") .HasColumnName("DialogActivityDescription_ActivityId"); @@ -1073,7 +1073,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Dialog"); }); - modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.DialogSeenRecord", b => + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.DialogSeenLog", b => { b.HasOne("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.DialogEntity", "Dialog") .WithMany("SeenLog") @@ -1189,7 +1189,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.DialogSeenLogVia", b => { - b.HasOne("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.DialogSeenRecord", "DialogSeenLog") + b.HasOne("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.DialogSeenLog", "DialogSeenLog") .WithOne("Via") .HasForeignKey("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.DialogSeenLogVia", "DialogSeenLogId") .OnDelete(DeleteBehavior.Cascade) @@ -1251,7 +1251,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("SeenLog"); }); - modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.DialogSeenRecord", b => + modelBuilder.Entity("Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.DialogSeenLog", b => { b.Navigation("Via"); }); diff --git a/src/Digdir.Domain.Dialogporten.WebApi/Digdir.Domain.Dialogporten.WebApi.csproj b/src/Digdir.Domain.Dialogporten.WebApi/Digdir.Domain.Dialogporten.WebApi.csproj index b3e018233..911eb7f22 100644 --- a/src/Digdir.Domain.Dialogporten.WebApi/Digdir.Domain.Dialogporten.WebApi.csproj +++ b/src/Digdir.Domain.Dialogporten.WebApi/Digdir.Domain.Dialogporten.WebApi.csproj @@ -12,21 +12,21 @@ - - - - - - - - - + + + + + + + + + - - - + + + diff --git a/src/Digdir.Domain.Dialogporten.WebApi/Endpoints/V1/EndUser/DialogSeenLogs/Get/GetDialogSeenLogEndpoint.cs b/src/Digdir.Domain.Dialogporten.WebApi/Endpoints/V1/EndUser/DialogSeenLogs/Get/GetDialogSeenLogEndpoint.cs new file mode 100644 index 000000000..6018d7a87 --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.WebApi/Endpoints/V1/EndUser/DialogSeenLogs/Get/GetDialogSeenLogEndpoint.cs @@ -0,0 +1,36 @@ +using Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.DialogSeenLogs.Queries.Get; +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.DialogSeenLogs.Get; + +public class GetDialogSeenLogEndpoint : Endpoint +{ + private readonly ISender _sender; + + public GetDialogSeenLogEndpoint(ISender sender) + { + _sender = sender ?? throw new ArgumentNullException(nameof(sender)); + } + + public override void Configure() + { + Get("dialogs/{dialogId}/seenlog/{seenLogId}"); + Policies(AuthorizationPolicy.EndUser); + Group(); + + Description(d => GetDialogSeenLogSwaggerConfig.SetDescription(d)); + } + + public override async Task HandleAsync(GetDialogSeenLogQuery 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), + forbiden => this.ForbiddenAsync(forbiden, ct)); + } +} diff --git a/src/Digdir.Domain.Dialogporten.WebApi/Endpoints/V1/EndUser/DialogSeenLogs/Get/GetDialogSeenLogSwaggerConfig.cs b/src/Digdir.Domain.Dialogporten.WebApi/Endpoints/V1/EndUser/DialogSeenLogs/Get/GetDialogSeenLogSwaggerConfig.cs new file mode 100644 index 000000000..c2c15d96d --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.WebApi/Endpoints/V1/EndUser/DialogSeenLogs/Get/GetDialogSeenLogSwaggerConfig.cs @@ -0,0 +1,35 @@ +using Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.DialogSeenLogs.Queries.Get; +using Digdir.Domain.Dialogporten.WebApi.Common; +using Digdir.Domain.Dialogporten.WebApi.Common.Extensions; +using Digdir.Domain.Dialogporten.WebApi.Common.Swagger; +using Digdir.Domain.Dialogporten.WebApi.Endpoints.V1.Common.Extensions; +using FastEndpoints; + +namespace Digdir.Domain.Dialogporten.WebApi.Endpoints.V1.EndUser.DialogSeenLogs.Get; + +public class GetDialogSeenLogSwaggerConfig : ISwaggerConfig +{ + public static string OperationId => "GetDialogSeenLog"; + public static RouteHandlerBuilder SetDescription(RouteHandlerBuilder builder) + => builder.OperationId(OperationId) + .ProducesOneOf( + StatusCodes.Status200OK, + StatusCodes.Status404NotFound); + + public static object GetExample() => throw new NotImplementedException(); +} + +public sealed class GetDialogSeenLogEndpointSummary : Summary +{ + public GetDialogSeenLogEndpointSummary() + { + Summary = "Gets a single dialog seen log record"; + Description = """ + Gets a single dialog seen log record. For more information see the documentation (link TBD). + """; + + Responses[StatusCodes.Status200OK] = Constants.SwaggerSummary.ReturnedResult.FormatInvariant("seen log record"); + Responses[StatusCodes.Status401Unauthorized] = Constants.SwaggerSummary.EndUserAuthenticationFailure; + Responses[StatusCodes.Status404NotFound] = Constants.SwaggerSummary.DialogElementNotFound; + } +} diff --git a/src/Digdir.Domain.Dialogporten.WebApi/Endpoints/V1/EndUser/DialogSeenLogs/Search/SearchDialogSeenLogEndpoint.cs b/src/Digdir.Domain.Dialogporten.WebApi/Endpoints/V1/EndUser/DialogSeenLogs/Search/SearchDialogSeenLogEndpoint.cs new file mode 100644 index 000000000..63a24379c --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.WebApi/Endpoints/V1/EndUser/DialogSeenLogs/Search/SearchDialogSeenLogEndpoint.cs @@ -0,0 +1,36 @@ +using Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.DialogSeenLogs.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.DialogSeenLogs.Search; + +public class SearchDialogSeenLogEndpoint : Endpoint> +{ + private readonly ISender _sender; + + public SearchDialogSeenLogEndpoint(ISender sender) + { + _sender = sender ?? throw new ArgumentNullException(nameof(sender)); + } + + public override void Configure() + { + Get("dialogs/{dialogId}/seenlog"); + Policies(AuthorizationPolicy.EndUser); + Group(); + + Description(d => SearchDialogSeenLogSwaggerConfig.SetDescription(d)); + } + + public override async Task HandleAsync(SearchDialogSeenLogQuery 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), + forbiden => this.ForbiddenAsync(forbiden, ct)); + } +} diff --git a/src/Digdir.Domain.Dialogporten.WebApi/Endpoints/V1/EndUser/DialogSeenLogs/Search/SearchDialogSeenLogSwaggerConfig.cs b/src/Digdir.Domain.Dialogporten.WebApi/Endpoints/V1/EndUser/DialogSeenLogs/Search/SearchDialogSeenLogSwaggerConfig.cs new file mode 100644 index 000000000..7132f8ec4 --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.WebApi/Endpoints/V1/EndUser/DialogSeenLogs/Search/SearchDialogSeenLogSwaggerConfig.cs @@ -0,0 +1,36 @@ +using Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.DialogSeenLogs.Queries.Search; +using Digdir.Domain.Dialogporten.WebApi.Common; +using Digdir.Domain.Dialogporten.WebApi.Common.Extensions; +using Digdir.Domain.Dialogporten.WebApi.Common.Swagger; +using Digdir.Domain.Dialogporten.WebApi.Endpoints.V1.Common.Extensions; +using FastEndpoints; + +namespace Digdir.Domain.Dialogporten.WebApi.Endpoints.V1.EndUser.DialogSeenLogs.Search; + +public class SearchDialogSeenLogSwaggerConfig : ISwaggerConfig +{ + public static string OperationId => "SearchDialogSeenLog"; + + public static RouteHandlerBuilder SetDescription(RouteHandlerBuilder builder) + => builder.OperationId(OperationId) + .ProducesOneOf>( + StatusCodes.Status200OK, + StatusCodes.Status404NotFound); + + public static object GetExample() => throw new NotImplementedException(); +} + +public sealed class SearchDialogSeenLogEndpointSummary : Summary +{ + public SearchDialogSeenLogEndpointSummary() + { + Summary = "Gets a single dialog seen log record"; + Description = """ + Gets a single dialog seen log record. For more information see the documentation (link TBD). + """; + + Responses[StatusCodes.Status200OK] = Constants.SwaggerSummary.ReturnedResult.FormatInvariant("seen log record"); + Responses[StatusCodes.Status401Unauthorized] = Constants.SwaggerSummary.EndUserAuthenticationFailure; + Responses[StatusCodes.Status404NotFound] = Constants.SwaggerSummary.DialogElementNotFound; + } +}