From b577f8176e525cb5a1b225ae17fb5f549447a68c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Skogstad?= Date: Thu, 21 Mar 2024 12:42:11 +0100 Subject: [PATCH 01/23] --wip-- [skip ci] --- Digdir.Domain.Dialogporten.sln | 7 + .../DialogQueries.cs | 152 ++++++++++++++++++ .../Digdir.Domain.Dialogporten.GraphQL.csproj | 22 +++ .../LocalDevelopmentUser.cs | 44 +++++ .../Program.cs | 57 +++++++ .../Properties/launchSettings.json | 41 +++++ .../appsettings.Development.json | 76 +++++++++ .../appsettings.json | 16 ++ 8 files changed, 415 insertions(+) create mode 100644 src/Digdir.Domain.Dialogporten.GraphQL/DialogQueries.cs create mode 100644 src/Digdir.Domain.Dialogporten.GraphQL/Digdir.Domain.Dialogporten.GraphQL.csproj create mode 100644 src/Digdir.Domain.Dialogporten.GraphQL/LocalDevelopmentUser.cs create mode 100644 src/Digdir.Domain.Dialogporten.GraphQL/Program.cs create mode 100644 src/Digdir.Domain.Dialogporten.GraphQL/Properties/launchSettings.json create mode 100644 src/Digdir.Domain.Dialogporten.GraphQL/appsettings.Development.json create mode 100644 src/Digdir.Domain.Dialogporten.GraphQL/appsettings.json diff --git a/Digdir.Domain.Dialogporten.sln b/Digdir.Domain.Dialogporten.sln index f513035db..9d6ebfded 100644 --- a/Digdir.Domain.Dialogporten.sln +++ b/Digdir.Domain.Dialogporten.sln @@ -53,6 +53,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Digdir.Tool.Dialogporten.Ed EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Digdir.Domain.Dialogporten.WebApi.Integration.Tests", "tests\Digdir.Domain.Dialogporten.WebApi.Integration.Tests\Digdir.Domain.Dialogporten.WebApi.Integration.Tests.csproj", "{42004236-D45C-4A1F-9FF9-CF12B7388389}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Digdir.Domain.Dialogporten.GraphQL", "src\Digdir.Domain.Dialogporten.GraphQL\Digdir.Domain.Dialogporten.GraphQL.csproj", "{234FE24D-1047-4E29-A625-1EB406C37A2D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -123,6 +125,10 @@ Global {42004236-D45C-4A1F-9FF9-CF12B7388389}.Debug|Any CPU.Build.0 = Debug|Any CPU {42004236-D45C-4A1F-9FF9-CF12B7388389}.Release|Any CPU.ActiveCfg = Release|Any CPU {42004236-D45C-4A1F-9FF9-CF12B7388389}.Release|Any CPU.Build.0 = Release|Any CPU + {234FE24D-1047-4E29-A625-1EB406C37A2D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {234FE24D-1047-4E29-A625-1EB406C37A2D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {234FE24D-1047-4E29-A625-1EB406C37A2D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {234FE24D-1047-4E29-A625-1EB406C37A2D}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -147,6 +153,7 @@ Global {B6FE45A3-FB14-4528-9957-295AB2A00A46} = {CADB8189-4AA1-4732-844A-C41DBF3EC8B7} {030909AA-5B61-46B4-9B74-0D2D779478FF} = {3C2C775D-F2D1-42A2-B53F-CC6D5FF59633} {42004236-D45C-4A1F-9FF9-CF12B7388389} = {CADB8189-4AA1-4732-844A-C41DBF3EC8B7} + {234FE24D-1047-4E29-A625-1EB406C37A2D} = {320B47A0-5EB8-4B6E-8C84-90633A1849CA} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {B2FE67FF-7622-4AFB-AD8E-961B6A39D888} diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/DialogQueries.cs b/src/Digdir.Domain.Dialogporten.GraphQL/DialogQueries.cs new file mode 100644 index 000000000..17e99e96e --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.GraphQL/DialogQueries.cs @@ -0,0 +1,152 @@ +using AutoMapper; +using Digdir.Domain.Dialogporten.Application.Features.V1.Common.Localizations; +using Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.Dialogs.Queries.Get; +using Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.Dialogs.Queries.Search; +using MediatR; + +namespace Digdir.Domain.Dialogporten.GraphQL; + +public class DialogQueries +{ + [GraphQLName("dialogById")] + public async Task GetDialogAsync( + [Service] IMediator mediator, + [Service] IMapper mapper, + [Argument] Guid dialogId) + { + var query = new GetDialogQuery + { + DialogId = dialogId + }; + + var result = await mediator.Send(query); + + var foo = result.Match( + dto => dto, + notFound => throw new ArgumentException(), + deleted => throw new ArgumentException(), + forbidden => throw new ArgumentException()); + + var mapped = mapper.Map(foo); + + return mapped; + } + + [GraphQLName("dialogs")] + public async Task SearchDialogsAsync( + [Service] IMediator mediator, + [Service] IMapper mapper, + SearchDialogInput input) + { + // var query = new GetDialogQuery + // { + // DialogId = dialogId + // }; + + var query = new SearchDialogQuery + { + + }; + var result = await mediator.Send(query); + + var foo = result.Match( + dto => dto, + validationError => throw new ArgumentException(), + forbidden => throw new ArgumentException()); + + var mapped = mapper.Map(foo); + + return mapped; + } +} + +public class MappingProfile : Profile +{ + public MappingProfile() + { + CreateMap() + .ForMember(dest => dest.Status, opt => opt.MapFrom(src => src.Status)); + + CreateMap() + .ForMember(dest => dest.Type, opt => opt.MapFrom(src => src.Type)); + + CreateMap(); + } +} + +public enum DialogStatus +{ + New = 1, + + /// + /// Under arbeid. Generell status som brukes for dialogtjenester der ytterligere bruker-input er + /// forventet. + /// + InProgress = 2, + + /// + /// Venter pÃ¥ tilbakemelding fra tjenesteeier + /// + Waiting = 3, + + /// + /// Dialogen er i en tilstand hvor den venter pÃ¥ signering. Typisk siste steg etter at all + /// utfylling er gjennomført og validert. + /// + Signing = 4, + + /// + /// Dialogen ble avbrutt. Dette gjør at dialogen typisk fjernes fra normale GUI-visninger. + /// + Cancelled = 5, + + /// + /// Dialigen ble fullført. Dette gjør at dialogen typisk flyttes til et GUI-arkiv eller lignende. + /// + Completed = 6 +} + +public class Dialog +{ + public Guid Id { get; set; } + public Guid Revision { get; set; } + public string Org { get; set; } = null!; + public string ServiceResource { get; set; } = null!; + public string Party { get; set; } = null!; + public int? Progress { get; set; } + public string? ExtendedStatus { get; set; } + public string? ExternalReference { get; set; } + public DateTimeOffset? VisibleFrom { get; set; } + public DateTimeOffset? DueAt { get; set; } + public DateTimeOffset? ExpiresAt { get; set; } + public DateTimeOffset CreatedAt { get; set; } + public DateTimeOffset UpdatedAt { get; set; } + + public DialogStatus Status { get; set; } + + public List Content { get; set; } = []; + // public List Elements { get; set; } = []; + // public List GuiActions { get; set; } = []; + // public List ApiActions { get; set; } = []; + // public List Activities { get; set; } = []; +} + +public enum ContentType +{ + Title = 1, + SenderName = 2, + Summary = 3, + AdditionalInfo = 4 +} + +public sealed class Content +{ + public ContentType Type { get; set; } + public List Value { get; set; } = []; +} + +public sealed class Localization +{ + public string Value { get; set; } = null!; + public string CultureCode { get; set; } = null!; +} diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/Digdir.Domain.Dialogporten.GraphQL.csproj b/src/Digdir.Domain.Dialogporten.GraphQL/Digdir.Domain.Dialogporten.GraphQL.csproj new file mode 100644 index 000000000..d0c809f43 --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.GraphQL/Digdir.Domain.Dialogporten.GraphQL.csproj @@ -0,0 +1,22 @@ + + + + net8.0 + enable + enable + 750256a4-f332-4783-8802-8a7d9566f9ca + + + + + + + + + + + + + + + diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/LocalDevelopmentUser.cs b/src/Digdir.Domain.Dialogporten.GraphQL/LocalDevelopmentUser.cs new file mode 100644 index 000000000..775f0ef34 --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.GraphQL/LocalDevelopmentUser.cs @@ -0,0 +1,44 @@ +using System.Collections.ObjectModel; +using System.Security.Claims; +using Digdir.Domain.Dialogporten.Application.Externals.Presentation; + +namespace Digdir.Domain.Dialogporten.GraphQL; + +internal sealed class LocalDevelopmentUser : IUser +{ + private readonly ClaimsPrincipal _principal = new(new ClaimsIdentity(new[] + { + new Claim(ClaimTypes.Name, "Local Development User"), + new Claim(ClaimTypes.NameIdentifier, "local-development-user"), + new Claim("pid", "03886595947"), + new Claim("scope", string.Join(" ", AuthorizationScope.AllScopes.Value)), + new Claim("consumer", + """ + { + "authority": "iso6523-actorid-upis", + "ID": "0192:991825827" + } + """) + })); + + public ClaimsPrincipal GetPrincipal() => _principal; +} + + +internal static class AuthorizationScope +{ + public const string EndUser = "digdir:dialogporten"; + public const string ServiceProvider = "digdir:dialogporten.serviceprovider"; + public const string ServiceProviderSearch = "digdir:dialogporten.serviceprovider.search"; + public const string Testing = "digdir:dialogporten.developer.test"; + + internal static readonly Lazy> AllScopes = new(GetAll); + + private static ReadOnlyCollection GetAll() => + typeof(AuthorizationScope) + .GetFields() + .Where(x => x.IsLiteral && !x.IsInitOnly && x.DeclaringType == typeof(string)) + .Select(x => (string)x.GetRawConstantValue()!) + .ToList() + .AsReadOnly(); +} diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/Program.cs b/src/Digdir.Domain.Dialogporten.GraphQL/Program.cs new file mode 100644 index 000000000..e51ecc372 --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.GraphQL/Program.cs @@ -0,0 +1,57 @@ +using System.Globalization; +using System.Reflection; +using Digdir.Domain.Dialogporten.Application; +using Digdir.Domain.Dialogporten.Application.Externals.Presentation; +using Digdir.Domain.Dialogporten.GraphQL; +using Digdir.Domain.Dialogporten.Infrastructure; +using Microsoft.ApplicationInsights.Extensibility; +using Serilog; + +Log.Logger = new LoggerConfiguration() + .MinimumLevel.Warning() + .Enrich.FromLogContext() + .WriteTo.Console(formatProvider: CultureInfo.InvariantCulture) + .WriteTo.ApplicationInsights( + TelemetryConfiguration.CreateDefault(), + TelemetryConverter.Traces) + .CreateBootstrapLogger(); + +try +{ + BuildAndRun(args); +} +catch (Exception ex) when (ex is not OperationCanceledException) +{ + Log.Fatal(ex, "Application terminated unexpectedly"); + throw; +} +finally +{ + Log.CloseAndFlush(); +} + +static void BuildAndRun(string[] args) +{ + var builder = WebApplication.CreateBuilder(args); + + // builder.Host.UseSerilog((context, services, configuration) => configuration + // .MinimumLevel.Warning() + // .MinimumLevel.Override("Microsoft.EntityFrameworkCore", Serilog.Events.LogEventLevel.Fatal) + // .ReadFrom.Configuration(context.Configuration) + // .ReadFrom.Services(services) + // .Enrich.FromLogContext() + // .WriteTo(Console.WriteLine())); + + builder.Services + .AddApplication(builder.Configuration, builder.Environment) + .AddInfrastructure(builder.Configuration, builder.Environment) + .AddAutoMapper(Assembly.GetExecutingAssembly()) + // .AddApplicationInsightsTelemetry() + .AddScoped() + .AddGraphQLServer() + .AddQueryType(); + + var app = builder.Build(); + app.MapGraphQL(); + app.Run(); +} diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/Properties/launchSettings.json b/src/Digdir.Domain.Dialogporten.GraphQL/Properties/launchSettings.json new file mode 100644 index 000000000..778d377bf --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.GraphQL/Properties/launchSettings.json @@ -0,0 +1,41 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:53970", + "sslPort": 44306 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5181", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "launchUrl": "graphql", + "applicationUrl": "http://localhost:5181", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/appsettings.Development.json b/src/Digdir.Domain.Dialogporten.GraphQL/appsettings.Development.json new file mode 100644 index 000000000..b07097573 --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.GraphQL/appsettings.Development.json @@ -0,0 +1,76 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "Infrastructure": { + "Redis": { + "Enabled": true, + "ConnectionString": "localhost:6379" + }, + "DialogDbConnectionString": "TODO: Add to local secrets", + // Settings from appsettings.json, environment variables or other configuration providers. + // The first three are always mandatory for all client definitions types + "Maskinporten": { + // 1. Valid values are test and prod + "Environment": "test", + + // 2. Client Id/integration as configured in Maskinporten + "ClientId": "TODO: Add to local secrets", + + // 3. Scope(s) requested, space seperated. Must be provisioned on supplied client id. + "Scope": "altinn:events.publish altinn:events.publish.admin altinn:register/partylookup.admin altinn:authorization:pdp", + + // -------------------------- + // Any additional settings are specific for the selected client definition type. + // See below for examples using other types. + "EncodedJwk": "TODO: Add to local secrets" + }, + "Altinn": { + "BaseUri": "https://platform.tt02.altinn.no/", + "SubscriptionKey": "TODO: Add to local secrets" + }, + "AltinnCdn": { + "BaseUri": "https://altinncdn.no/" + } + }, + "Application": { + "Dialogporten": { + "BaseUri": "https://localhost:7214" + } + }, + "WebApi": { + "Authentication": { + "JwtBearerTokenSchemas": [ + { + "Name": "Maskinporten", + "WellKnown": "https://test.maskinporten.no/.well-known/oauth-authorization-server/" + }, + { + "Name": "MaskinportenAuxiliary", + "WellKnown": "https://ver2.maskinporten.no/.well-known/oauth-authorization-server/" + }, + { + "Name": "Altinn", + "WellKnown": "https://platform.tt02.altinn.no/authentication/api/v1/openid/.well-known/openid-configuration" + }, + { + "Name": "Idporten", + "WellKnown": "https://test.idporten.no/.well-known/openid-configuration" + } + ] + } + }, + "LocalDevelopment": { + "UseLocalDevelopmentUser": true, + "UseLocalDevelopmentResourceRegister": true, + "UseLocalDevelopmentOrganizationRegister": true, + "UseLocalDevelopmentNameRegister": true, + "UseLocalDevelopmentAltinnAuthorization": true, + "UseLocalDevelopmentCloudEventBus": true, + "DisableShortCircuitOutboxDispatcher": true, + "DisableAuth": true + } +} diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/appsettings.json b/src/Digdir.Domain.Dialogporten.GraphQL/appsettings.json new file mode 100644 index 000000000..d51dc7502 --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.GraphQL/appsettings.json @@ -0,0 +1,16 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning", + "Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware": "None", + "System.Net.Http.HttpClient": "Information" + } + }, + "Infrastructure": { + "AltinnCdn": { + "BaseUri": "https://altinncdn.no/" + } + }, + "AllowedHosts": "*" +} From dc97e52dd71c445c856a231330c1ab8ae6981dd1 Mon Sep 17 00:00:00 2001 From: Magnus Sandgren <5285192+MagnusSandgren@users.noreply.github.com> Date: Tue, 26 Mar 2024 15:46:42 +0100 Subject: [PATCH 02/23] Fiddle with graphql --- .../Queries/GetQueryable/GetDialogQuery.cs | 135 ++++++++++ .../GetQueryable/GetQueryableDialogDto.cs | 120 +++++++++ .../Queries/GetQueryable/MappingProfile.cs | 39 +++ .../DialogQueries.cs | 244 ++++++++---------- .../Digdir.Domain.Dialogporten.GraphQL.csproj | 9 +- .../Program.cs | 15 +- .../Persistence/DialogDbContext.cs | 2 +- 7 files changed, 418 insertions(+), 146 deletions(-) create mode 100644 src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/GetQueryable/GetDialogQuery.cs create mode 100644 src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/GetQueryable/GetQueryableDialogDto.cs create mode 100644 src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/GetQueryable/MappingProfile.cs diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/GetQueryable/GetDialogQuery.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/GetQueryable/GetDialogQuery.cs new file mode 100644 index 000000000..1b0e327f4 --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/GetQueryable/GetDialogQuery.cs @@ -0,0 +1,135 @@ +using System.Diagnostics; +using AutoMapper; +using AutoMapper.QueryableExtensions; +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.Pagination; +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.Dialogs.Queries.GetQueryable; + +public sealed class GetDialogQuery : IRequest> +{ + public string? Search { get; set; } + + public string? SearchCultureCode { get; set; } + + ///// + ///// Filter by one or more service resources + ///// + //public List? ServiceResource { get; init; } + + ///// + ///// Filter by one or more owning parties + ///// + //public List? Party { get; init; } +} + +internal sealed class GetDialogQueryableHandler : IRequestHandler> +{ + private readonly IDialogDbContext _db; + private readonly IMapper _mapper; + private readonly IUnitOfWork _unitOfWork; + private readonly IClock _clock; + private readonly IUserNameRegistry _userNameRegistry; + private readonly IAltinnAuthorization _altinnAuthorization; + + public GetDialogQueryableHandler( + IDialogDbContext db, + IMapper mapper, + IUnitOfWork unitOfWork, + IClock clock, + IUserNameRegistry userNameRegistry, + IAltinnAuthorization altinnAuthorization) + { + _db = db ?? throw new ArgumentNullException(nameof(db)); + _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper)); + _unitOfWork = unitOfWork ?? throw new ArgumentNullException(nameof(unitOfWork)); + _clock = clock ?? throw new ArgumentNullException(nameof(clock)); + _userNameRegistry = userNameRegistry ?? throw new ArgumentNullException(nameof(userNameRegistry)); + _altinnAuthorization = altinnAuthorization ?? throw new ArgumentNullException(nameof(altinnAuthorization)); + } + + public Task> Handle(GetDialogQuery request, CancellationToken cancellationToken) + { + var searchExpression = Expressions.LocalizedSearchExpression(request.Search, request.SearchCultureCode); + + var queryable = _db.Dialogs + .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!)) + ) + .Where(x => !x.VisibleFrom.HasValue || _clock.UtcNowOffset > x.VisibleFrom) + .Where(x => !x.ExpiresAt.HasValue || x.ExpiresAt > _clock.UtcNowOffset) + .ProjectTo(_mapper.ConfigurationProvider); + + return Task.FromResult(queryable); + //var userName = await _userNameRegistry.GetCurrentUserName(userPid, cancellationToken); + //// TODO: What if name lookup fails + //// https://github.com/digdir/dialogporten/issues/387 + //dialog.UpdateSeenAt(userPid, userName); + + //var saveResult = await _unitOfWork + // .WithoutAuditableSideEffects() + // .SaveChangesAsync(cancellationToken); + + //saveResult.Switch( + // success => { }, + // domainError => throw new UnreachableException("Should not get domain error when updating SeenAt."), + // concurrencyError => throw new UnreachableException("Should not get concurrencyError when updating SeenAt.")); + + // hash end user ids + //var salt = MappingUtils.GetHashSalt(); + //foreach (var activity in dialog.Activities) + //{ + // activity.SeenByEndUserId = MappingUtils.HashPid(activity.SeenByEndUserId, salt); + //} + + //var dto = _mapper.Map(dialog); + + //DecorateWithAuthorization(dto, authorizationResult); + + //return dto; + } + + //private static void DecorateWithAuthorization(GetDialogDto dto, DialogDetailsAuthorizationResult authorizationResult) + //{ + // foreach (var (action, resources) in authorizationResult.AuthorizedAltinnActions) + // { + // foreach (var apiAction in dto.ApiActions.Where(a => a.Action == action)) + // { + // if ((apiAction.AuthorizationAttribute is null && resources.Contains(Constants.MainResource)) + // || (apiAction.AuthorizationAttribute is not null && resources.Contains(apiAction.AuthorizationAttribute))) + // { + // apiAction.IsAuthorized = true; + // } + // } + + // foreach (var guiAction in dto.GuiActions.Where(a => a.Action == action)) + // { + // if ((guiAction.AuthorizationAttribute is null && resources.Contains(Constants.MainResource)) + // || (guiAction.AuthorizationAttribute is not null && resources.Contains(guiAction.AuthorizationAttribute))) + // { + // guiAction.IsAuthorized = true; + // } + // } + + // // Simple "read" on the main resource will give access to a dialog element, unless a authorization attribute is set, + // // in which case an "elementread" action is required + // foreach (var dialogElement in dto.Elements.Where( + // dialogElement => (dialogElement.AuthorizationAttribute is null) + // || (dialogElement.AuthorizationAttribute is not null + // && action == Constants.ElementReadAction))) + // { + // dialogElement.IsAuthorized = true; + // } + // } + //} +} diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/GetQueryable/GetQueryableDialogDto.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/GetQueryable/GetQueryableDialogDto.cs new file mode 100644 index 000000000..18b0546ea --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/GetQueryable/GetQueryableDialogDto.cs @@ -0,0 +1,120 @@ +using Digdir.Domain.Dialogporten.Application.Features.V1.Common.Localizations; +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.Content; +using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Elements; +using Digdir.Domain.Dialogporten.Domain.Http; + +namespace Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.Dialogs.Queries.GetQueryable; + +public sealed class GetQueryableDialogDto +{ + public Guid Id { get; set; } + public Guid Revision { get; set; } + public string Org { get; set; } = null!; + public string ServiceResource { get; set; } = null!; + public string Party { get; set; } = null!; + public int? Progress { get; set; } + public string? ExtendedStatus { get; set; } + public string? ExternalReference { get; set; } + public DateTimeOffset? VisibleFrom { get; set; } + public DateTimeOffset? DueAt { get; set; } + public DateTimeOffset? ExpiresAt { get; set; } + public DateTimeOffset CreatedAt { get; set; } + public DateTimeOffset UpdatedAt { get; set; } + + //public DialogStatus.Values Status { get; set; } + + public List Content { get; set; } = []; + + public List Elements { get; set; } = []; + public List GuiActions { get; set; } = []; + public List ApiActions { get; set; } = []; + public List Activities { get; set; } = []; +} + +public sealed class GetDialogContentDto +{ + //public DialogContentType.Values Type { get; set; } + public List Value { get; set; } = []; +} + +public sealed class GetDialogDialogActivityDto +{ + public Guid Id { get; set; } + public DateTimeOffset? CreatedAt { get; set; } + public Uri? ExtendedType { get; set; } + public string? SeenByEndUserIdHash { get; init; } + + //public DialogActivityType.Values Type { get; set; } + + public Guid? RelatedActivityId { get; set; } + public Guid? DialogElementId { get; set; } + + public List? PerformedBy { get; set; } = []; + public List Description { get; set; } = []; +} + +public sealed class GetDialogDialogApiActionDto +{ + public Guid Id { get; set; } + public string Action { get; set; } = null!; + public string? AuthorizationAttribute { get; set; } + public bool IsAuthorized { get; set; } + + public Guid? DialogElementId { get; set; } + + public List Endpoints { get; set; } = []; +} + +public sealed class GetDialogDialogApiActionEndpointDto +{ + public Guid Id { get; set; } + public string? Version { get; set; } + public Uri Url { get; set; } = null!; + //public HttpVerb.Values HttpMethod { get; set; } + public Uri? DocumentationUrl { get; set; } + public Uri? RequestSchema { get; set; } + public Uri? ResponseSchema { get; set; } + public bool Deprecated { get; set; } + public DateTimeOffset? SunsetAt { get; set; } +} + +public sealed class GetDialogDialogGuiActionDto +{ + public Guid Id { get; set; } + public string Action { get; set; } = null!; + public Uri Url { get; set; } = null!; + public string? AuthorizationAttribute { get; set; } + public bool IsAuthorized { get; set; } + public bool IsBackChannel { get; set; } + public bool IsDeleteAction { get; set; } + + //public DialogGuiActionPriority.Values Priority { get; set; } + + public List Title { get; set; } = []; +} + +public sealed class GetDialogDialogElementDto +{ + public Guid Id { get; set; } + public Uri? Type { get; set; } + public string? ExternalReference { get; set; } + public string? AuthorizationAttribute { get; set; } + public bool IsAuthorized { get; set; } + + public Guid? RelatedDialogElementId { get; set; } + + public List DisplayName { get; set; } = []; + public List Urls { get; set; } = []; +} + +public sealed class GetDialogDialogElementUrlDto +{ + public Guid Id { get; set; } + public Uri Url { get; set; } = null!; + public string? MimeType { get; set; } + + //public DialogElementUrlConsumerType.Values ConsumerType { get; set; } +} diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/GetQueryable/MappingProfile.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/GetQueryable/MappingProfile.cs new file mode 100644 index 000000000..31170773e --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/GetQueryable/MappingProfile.cs @@ -0,0 +1,39 @@ +using AutoMapper; +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.Content; +using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Elements; + +namespace Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.Dialogs.Queries.GetQueryable; + +internal sealed class MappingProfile : Profile +{ + public MappingProfile() + { + CreateMap() + .ForMember(dest => dest.Revision, opt => opt.MapFrom(src => src.Revision)); + //.ForMember(dest => dest.Status, opt => opt.MapFrom(src => src.StatusId)); + + CreateMap() + .ForMember(dest => dest.SeenByEndUserIdHash, opt => opt.MapFrom(src => src.SeenByEndUserId)); + //.ForMember(dest => dest.Type, opt => opt.MapFrom(src => src.TypeId)); + + CreateMap(); + + CreateMap(); + //.ForMember(dest => dest.HttpMethod, opt => opt.MapFrom(src => src.HttpMethodId)); + + CreateMap(); + //.ForMember(dest => dest.Priority, opt => opt.MapFrom(src => src.PriorityId)); + + CreateMap(); + + CreateMap(); + //.ForMember(dest => dest.ConsumerType, opt => opt.MapFrom(src => src.ConsumerTypeId)); + + CreateMap(); + //.ForMember(dest => dest.Type, opt => opt.MapFrom(src => src.TypeId)); + } + +} diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/DialogQueries.cs b/src/Digdir.Domain.Dialogporten.GraphQL/DialogQueries.cs index 17e99e96e..493932a5d 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/DialogQueries.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/DialogQueries.cs @@ -1,152 +1,116 @@ +using System.Threading; using AutoMapper; +using Digdir.Domain.Dialogporten.Application.Common.Pagination; +using Digdir.Domain.Dialogporten.Application.Externals.AltinnAuthorization; +using Digdir.Domain.Dialogporten.Application.Externals; using Digdir.Domain.Dialogporten.Application.Features.V1.Common.Localizations; -using Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.Dialogs.Queries.Get; -using Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.Dialogs.Queries.Search; using MediatR; +using Yarp.ReverseProxy.Utilities; +using Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.Dialogs.Queries.GetQueryable; namespace Digdir.Domain.Dialogporten.GraphQL; public class DialogQueries { - [GraphQLName("dialogById")] - public async Task GetDialogAsync( - [Service] IMediator mediator, - [Service] IMapper mapper, - [Argument] Guid dialogId) + //[UsePaging] + [UseProjection] + [UseFiltering] + [UseSorting] + public async Task> GetDialogs( + [Service] ISender mediator, + GetDialogQuery? query) { - var query = new GetDialogQuery - { - DialogId = dialogId - }; - - var result = await mediator.Send(query); - - var foo = result.Match( - dto => dto, - notFound => throw new ArgumentException(), - deleted => throw new ArgumentException(), - forbidden => throw new ArgumentException()); - - var mapped = mapper.Map(foo); - - return mapped; - } - - [GraphQLName("dialogs")] - public async Task SearchDialogsAsync( - [Service] IMediator mediator, - [Service] IMapper mapper, - SearchDialogInput input) - { - // var query = new GetDialogQuery - // { - // DialogId = dialogId - // }; - - var query = new SearchDialogQuery - { - - }; - var result = await mediator.Send(query); - - var foo = result.Match( - dto => dto, - validationError => throw new ArgumentException(), - forbidden => throw new ArgumentException()); - - var mapped = mapper.Map(foo); - - return mapped; + return await mediator.Send(query ?? new()); } } -public class MappingProfile : Profile -{ - public MappingProfile() - { - CreateMap() - .ForMember(dest => dest.Status, opt => opt.MapFrom(src => src.Status)); - - CreateMap() - .ForMember(dest => dest.Type, opt => opt.MapFrom(src => src.Type)); - - CreateMap(); - } -} - -public enum DialogStatus -{ - New = 1, - - /// - /// Under arbeid. Generell status som brukes for dialogtjenester der ytterligere bruker-input er - /// forventet. - /// - InProgress = 2, - - /// - /// Venter pÃ¥ tilbakemelding fra tjenesteeier - /// - Waiting = 3, - - /// - /// Dialogen er i en tilstand hvor den venter pÃ¥ signering. Typisk siste steg etter at all - /// utfylling er gjennomført og validert. - /// - Signing = 4, - - /// - /// Dialogen ble avbrutt. Dette gjør at dialogen typisk fjernes fra normale GUI-visninger. - /// - Cancelled = 5, - - /// - /// Dialigen ble fullført. Dette gjør at dialogen typisk flyttes til et GUI-arkiv eller lignende. - /// - Completed = 6 -} - -public class Dialog -{ - public Guid Id { get; set; } - public Guid Revision { get; set; } - public string Org { get; set; } = null!; - public string ServiceResource { get; set; } = null!; - public string Party { get; set; } = null!; - public int? Progress { get; set; } - public string? ExtendedStatus { get; set; } - public string? ExternalReference { get; set; } - public DateTimeOffset? VisibleFrom { get; set; } - public DateTimeOffset? DueAt { get; set; } - public DateTimeOffset? ExpiresAt { get; set; } - public DateTimeOffset CreatedAt { get; set; } - public DateTimeOffset UpdatedAt { get; set; } - - public DialogStatus Status { get; set; } - - public List Content { get; set; } = []; - // public List Elements { get; set; } = []; - // public List GuiActions { get; set; } = []; - // public List ApiActions { get; set; } = []; - // public List Activities { get; set; } = []; -} - -public enum ContentType -{ - Title = 1, - SenderName = 2, - Summary = 3, - AdditionalInfo = 4 -} - -public sealed class Content -{ - public ContentType Type { get; set; } - public List Value { get; set; } = []; -} - -public sealed class Localization -{ - public string Value { get; set; } = null!; - public string CultureCode { get; set; } = null!; -} +//public class MappingProfile : Profile +//{ +// public MappingProfile() +// { +// CreateMap() +// .ForMember(dest => dest.Status, opt => opt.MapFrom(src => src.Status)); + +// CreateMap() +// .ForMember(dest => dest.Type, opt => opt.MapFrom(src => src.Type)); + +// CreateMap(); +// } +//} + +//public enum DialogStatus +//{ +// New = 1, + +// /// +// /// Under arbeid. Generell status som brukes for dialogtjenester der ytterligere bruker-input er +// /// forventet. +// /// +// InProgress = 2, + +// /// +// /// Venter pÃ¥ tilbakemelding fra tjenesteeier +// /// +// Waiting = 3, + +// /// +// /// Dialogen er i en tilstand hvor den venter pÃ¥ signering. Typisk siste steg etter at all +// /// utfylling er gjennomført og validert. +// /// +// Signing = 4, + +// /// +// /// Dialogen ble avbrutt. Dette gjør at dialogen typisk fjernes fra normale GUI-visninger. +// /// +// Cancelled = 5, + +// /// +// /// Dialigen ble fullført. Dette gjør at dialogen typisk flyttes til et GUI-arkiv eller lignende. +// /// +// Completed = 6 +//} + +//public class Dialog +//{ +// public Guid Id { get; set; } +// public Guid Revision { get; set; } +// public string Org { get; set; } = null!; +// public string ServiceResource { get; set; } = null!; +// public string Party { get; set; } = null!; +// public int? Progress { get; set; } +// public string? ExtendedStatus { get; set; } +// public string? ExternalReference { get; set; } +// public DateTimeOffset? VisibleFrom { get; set; } +// public DateTimeOffset? DueAt { get; set; } +// public DateTimeOffset? ExpiresAt { get; set; } +// public DateTimeOffset CreatedAt { get; set; } +// public DateTimeOffset UpdatedAt { get; set; } + +// public DialogStatus Status { get; set; } + +// public List Content { get; set; } = []; +// // public List Elements { get; set; } = []; +// // public List GuiActions { get; set; } = []; +// // public List ApiActions { get; set; } = []; +// // public List Activities { get; set; } = []; +//} + +//public enum ContentType +//{ +// Title = 1, +// SenderName = 2, +// Summary = 3, +// AdditionalInfo = 4 +//} + +//public sealed class Content +//{ +// public ContentType Type { get; set; } +// public List Value { get; set; } = []; +//} + +//public sealed class Localization +//{ +// public string Value { get; set; } = null!; +// public string CultureCode { get; set; } = null!; +//} diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/Digdir.Domain.Dialogporten.GraphQL.csproj b/src/Digdir.Domain.Dialogporten.GraphQL/Digdir.Domain.Dialogporten.GraphQL.csproj index d0c809f43..eb6bda1bf 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/Digdir.Domain.Dialogporten.GraphQL.csproj +++ b/src/Digdir.Domain.Dialogporten.GraphQL/Digdir.Domain.Dialogporten.GraphQL.csproj @@ -1,4 +1,4 @@ - + net8.0 @@ -8,10 +8,11 @@ - + + - - + + diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/Program.cs b/src/Digdir.Domain.Dialogporten.GraphQL/Program.cs index e51ecc372..9cb009a0c 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/Program.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/Program.cs @@ -4,6 +4,7 @@ using Digdir.Domain.Dialogporten.Application.Externals.Presentation; using Digdir.Domain.Dialogporten.GraphQL; using Digdir.Domain.Dialogporten.Infrastructure; +using Digdir.Domain.Dialogporten.Infrastructure.Persistence; using Microsoft.ApplicationInsights.Extensibility; using Serilog; @@ -42,6 +43,14 @@ static void BuildAndRun(string[] args) // .Enrich.FromLogContext() // .WriteTo(Console.WriteLine())); + /* TODOS: + * - Gjør DialogDbContext til internal. Kan måtte gjøre dette prosjektet til et "friend assembly" av infrastructure (internalsVisibleTo) + * + * + * + */ + + builder.Services .AddApplication(builder.Configuration, builder.Environment) .AddInfrastructure(builder.Configuration, builder.Environment) @@ -49,7 +58,11 @@ static void BuildAndRun(string[] args) // .AddApplicationInsightsTelemetry() .AddScoped() .AddGraphQLServer() - .AddQueryType(); + .AddProjections() + .AddFiltering() + .AddSorting() + .RegisterDbContext() + .AddQueryType(); var app = builder.Build(); app.MapGraphQL(); diff --git a/src/Digdir.Domain.Dialogporten.Infrastructure/Persistence/DialogDbContext.cs b/src/Digdir.Domain.Dialogporten.Infrastructure/Persistence/DialogDbContext.cs index 73129fd99..66ba527dc 100644 --- a/src/Digdir.Domain.Dialogporten.Infrastructure/Persistence/DialogDbContext.cs +++ b/src/Digdir.Domain.Dialogporten.Infrastructure/Persistence/DialogDbContext.cs @@ -16,7 +16,7 @@ namespace Digdir.Domain.Dialogporten.Infrastructure.Persistence; -internal sealed class DialogDbContext : DbContext, IDialogDbContext +public sealed class DialogDbContext : DbContext, IDialogDbContext { public DialogDbContext(DbContextOptions options) : base(options) { } From 129a9af622e1bf4604ca1c6af6b72e7e77e456b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Skogstad?= Date: Tue, 2 Apr 2024 11:15:17 +0200 Subject: [PATCH 03/23] checkpoint --- src/Digdir.Domain.Dialogporten.GraphQL/Program.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/Program.cs b/src/Digdir.Domain.Dialogporten.GraphQL/Program.cs index 9cb009a0c..ea7b91dcf 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/Program.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/Program.cs @@ -44,10 +44,10 @@ static void BuildAndRun(string[] args) // .WriteTo(Console.WriteLine())); /* TODOS: - * - Gjør DialogDbContext til internal. Kan måtte gjøre dette prosjektet til et "friend assembly" av infrastructure (internalsVisibleTo) - * - * - * + * - Gjør DialogDbContext til internal. Kan mÃ¥tte gjøre dette prosjektet til et "friend assembly" av infrastructure (internalsVisibleTo) + * + * + * */ From 4d411463c7be01ae96e42566d2155f37bebc365a Mon Sep 17 00:00:00 2001 From: Magnus Sandgren <5285192+MagnusSandgren@users.noreply.github.com> Date: Mon, 8 Apr 2024 14:48:01 +0200 Subject: [PATCH 04/23] Draft add auth --- .../AuthenticationBuilderExtensions.cs | 63 ++++++++++++++++++ .../Authentication/AuthenticationOptions.cs | 12 ++++ .../AuthenticationOptionsValidator.cs | 25 ++++++++ .../JwtSchemeSelectorMiddleware.cs | 40 ++++++++++++ .../Common/Authentication/TokenIssuerCache.cs | 64 +++++++++++++++++++ .../Authorization/AllowAnonymousHandler.cs | 20 ++++++ .../AuthorizationOptionsSetup.cs | 45 +++++++++++++ .../Authorization/AuthorizationPolicy.cs | 29 +++++++++ .../AuthorizationPolicyBuilderExtensions.cs | 21 ++++++ .../Common/Constants.cs | 8 +++ .../Common/GraphQlSettings.cs | 21 ++++++ .../Digdir.Domain.Dialogporten.GraphQL.csproj | 2 + .../Program.cs | 42 +++++++++--- .../appsettings.Development.json | 2 +- 14 files changed, 384 insertions(+), 10 deletions(-) create mode 100644 src/Digdir.Domain.Dialogporten.GraphQL/Common/Authentication/AuthenticationBuilderExtensions.cs create mode 100644 src/Digdir.Domain.Dialogporten.GraphQL/Common/Authentication/AuthenticationOptions.cs create mode 100644 src/Digdir.Domain.Dialogporten.GraphQL/Common/Authentication/AuthenticationOptionsValidator.cs create mode 100644 src/Digdir.Domain.Dialogporten.GraphQL/Common/Authentication/JwtSchemeSelectorMiddleware.cs create mode 100644 src/Digdir.Domain.Dialogporten.GraphQL/Common/Authentication/TokenIssuerCache.cs create mode 100644 src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AllowAnonymousHandler.cs create mode 100644 src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationOptionsSetup.cs create mode 100644 src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationPolicy.cs create mode 100644 src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationPolicyBuilderExtensions.cs create mode 100644 src/Digdir.Domain.Dialogporten.GraphQL/Common/Constants.cs create mode 100644 src/Digdir.Domain.Dialogporten.GraphQL/Common/GraphQlSettings.cs diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authentication/AuthenticationBuilderExtensions.cs b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authentication/AuthenticationBuilderExtensions.cs new file mode 100644 index 000000000..5ddbc6c0f --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authentication/AuthenticationBuilderExtensions.cs @@ -0,0 +1,63 @@ +using Microsoft.IdentityModel.Tokens; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using System.Diagnostics; + +namespace Digdir.Domain.Dialogporten.GraphQL.Common.Authentication; + +internal static class AuthenticationBuilderExtensions +{ + public static IServiceCollection AddDialogportenAuthentication( + this IServiceCollection services, + IConfiguration configuration) + { + var jwtTokenSchemas = configuration + .GetSection(GraphQlSettings.SectionName) + .Get() + ?.Authentication + ?.JwtBearerTokenSchemas; + + if (jwtTokenSchemas is null || jwtTokenSchemas.Count == 0) + // Validation should have caught this. + throw new UnreachableException(); + + services.AddSingleton(); + + var authenticationBuilder = services.AddAuthentication(); + + foreach (var schema in jwtTokenSchemas) + { + authenticationBuilder.AddJwtBearer(schema.Name, options => + { + options.MetadataAddress = schema.WellKnown; + options.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuerSigningKey = true, + ValidateIssuer = false, + ValidateAudience = false, + RequireExpirationTime = true, + ValidateLifetime = true, + ClockSkew = TimeSpan.FromSeconds(2) + }; + + options.Events = new JwtBearerEvents + { + OnMessageReceived = async context => + { + var expectedIssuer = await context.HttpContext + .RequestServices + .GetRequiredService() + .GetIssuerForScheme(schema.Name); + + if (context.HttpContext.Items.TryGetValue(Constants.CurrentTokenIssuer, out var tokenIssuer) + && (string?)tokenIssuer != expectedIssuer) + { + context.NoResult(); + } + } + }; + }); + } + + return services; + } +} diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authentication/AuthenticationOptions.cs b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authentication/AuthenticationOptions.cs new file mode 100644 index 000000000..7c17dd258 --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authentication/AuthenticationOptions.cs @@ -0,0 +1,12 @@ +namespace Digdir.Domain.Dialogporten.GraphQL.Common.Authentication; + +public sealed class AuthenticationOptions +{ + public required List JwtBearerTokenSchemas { get; init; } +} + +public sealed class JwtBearerTokenSchemasOptions +{ + public required string Name { get; init; } + public required string WellKnown { get; init; } +} diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authentication/AuthenticationOptionsValidator.cs b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authentication/AuthenticationOptionsValidator.cs new file mode 100644 index 000000000..cb3b95620 --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authentication/AuthenticationOptionsValidator.cs @@ -0,0 +1,25 @@ +using FluentValidation; + +namespace Digdir.Domain.Dialogporten.GraphQL.Common.Authentication; + +internal sealed class AuthenticationOptionsValidator : AbstractValidator +{ + public AuthenticationOptionsValidator( + IValidator jwtTokenSchemaValidator) + { + RuleFor(x => x.JwtBearerTokenSchemas) + .NotEmpty() + .WithMessage("At least one JwtBearerTokenSchema must be configured"); + RuleForEach(x => x.JwtBearerTokenSchemas) + .SetValidator(jwtTokenSchemaValidator); + } +} + +internal sealed class JwtBearerTokenSchemasOptionsValidator : AbstractValidator +{ + public JwtBearerTokenSchemasOptionsValidator() + { + RuleFor(x => x.Name).NotEmpty(); + RuleFor(x => x.WellKnown).NotEmpty(); + } +} diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authentication/JwtSchemeSelectorMiddleware.cs b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authentication/JwtSchemeSelectorMiddleware.cs new file mode 100644 index 000000000..4c73edb27 --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authentication/JwtSchemeSelectorMiddleware.cs @@ -0,0 +1,40 @@ +using System.IdentityModel.Tokens.Jwt; + +namespace Digdir.Domain.Dialogporten.GraphQL.Common.Authentication; + +public class JwtSchemeSelectorMiddleware +{ + private readonly RequestDelegate _next; + + public JwtSchemeSelectorMiddleware(RequestDelegate next) + { + _next = next; + } + + public Task InvokeAsync(HttpContext context) + { + if (!context.Request.Headers.TryGetValue(Constants.Authorization, out var authorizationHeader)) + return _next(context); + + var token = authorizationHeader.ToString() + .Split(' ') + .LastOrDefault(); + + if (string.IsNullOrEmpty(token)) + return _next(context); + + var handler = new JwtSecurityTokenHandler(); + if (!handler.CanReadToken(token)) + return _next(context); + + var jwtToken = handler.ReadJwtToken(token); + context.Items[Constants.CurrentTokenIssuer] = jwtToken.Issuer; + return _next(context); + } +} + +public static class JwtSchemeSelectorMiddlewareExtensions +{ + public static IApplicationBuilder UseJwtSchemeSelector(this IApplicationBuilder app) + => app.UseMiddleware(); +} diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authentication/TokenIssuerCache.cs b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authentication/TokenIssuerCache.cs new file mode 100644 index 000000000..f2ffe53c5 --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authentication/TokenIssuerCache.cs @@ -0,0 +1,64 @@ +using Microsoft.IdentityModel.Protocols.OpenIdConnect; +using Microsoft.IdentityModel.Protocols; +using Microsoft.Extensions.Options; + +namespace Digdir.Domain.Dialogporten.GraphQL.Common.Authentication; + +public interface ITokenIssuerCache +{ + public Task GetIssuerForScheme(string schemeName); +} + +public sealed class TokenIssuerCache : ITokenIssuerCache, IDisposable +{ + private readonly Dictionary _issuerMappings = new(); + private readonly SemaphoreSlim _initializationSemaphore = new(1, 1); + private bool _initialized; + private readonly IReadOnlyCollection _jwtTokenSchemas; + + public TokenIssuerCache(IOptions apiSettings) + { + _jwtTokenSchemas = apiSettings + .Value + .Authentication + .JwtBearerTokenSchemas + ?? throw new ArgumentException("JwtBearerTokenSchemas is required."); + } + + public async Task GetIssuerForScheme(string schemeName) + { + await EnsureInitializedAsync(); + + return _issuerMappings.TryGetValue(schemeName, out var issuer) + ? issuer : null; + } + + private async Task EnsureInitializedAsync() + { + if (_initialized) return; + await _initializationSemaphore.WaitAsync(); + if (_initialized) return; + + try + { + foreach (var schema in _jwtTokenSchemas) + { + var configManager = new ConfigurationManager( + schema.WellKnown, new OpenIdConnectConfigurationRetriever()); + var config = await configManager.GetConfigurationAsync(); + _issuerMappings[schema.Name] = config.Issuer; + } + + _initialized = true; + } + finally + { + _initializationSemaphore.Release(); + } + } + + public void Dispose() + { + _initializationSemaphore.Dispose(); + } +} diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AllowAnonymousHandler.cs b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AllowAnonymousHandler.cs new file mode 100644 index 000000000..59478a730 --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AllowAnonymousHandler.cs @@ -0,0 +1,20 @@ +using Microsoft.AspNetCore.Authorization; + +namespace Digdir.Domain.Dialogporten.GraphQL.Common.Authorization; + +/// +/// This authorisation handler will bypass all requirements +/// +public class AllowAnonymousHandler : IAuthorizationHandler +{ + public Task HandleAsync(AuthorizationHandlerContext context) + { + foreach (var requirement in context.PendingRequirements) + { + //Simply pass all requirements + context.Succeed(requirement); + } + + return Task.CompletedTask; + } +} diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationOptionsSetup.cs b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationOptionsSetup.cs new file mode 100644 index 000000000..3af13c46e --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationOptionsSetup.cs @@ -0,0 +1,45 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.Options; + +namespace Digdir.Domain.Dialogporten.GraphQL.Common.Authorization; + +internal sealed class AuthorizationOptionsSetup : IConfigureOptions +{ + private readonly GraphQlSettings _options; + + public AuthorizationOptionsSetup(IOptions options) + { + _options = options.Value; + } + + public void Configure(AuthorizationOptions options) + { + var authenticationSchemas = _options + .Authentication + .JwtBearerTokenSchemas + .Select(x => x.Name) + .ToArray(); + + options.DefaultPolicy = new AuthorizationPolicyBuilder() + .RequireAuthenticatedUser() + .AddAuthenticationSchemes(authenticationSchemas) + .RequireValidConsumerClaim() + .Build(); + + options.AddPolicy(AuthorizationPolicy.EndUser, builder => builder + .Combine(options.DefaultPolicy) + .RequireScope(AuthorizationScope.EndUser)); + + options.AddPolicy(AuthorizationPolicy.ServiceProvider, builder => builder + .Combine(options.DefaultPolicy) + .RequireScope(AuthorizationScope.ServiceProvider)); + + options.AddPolicy(AuthorizationPolicy.ServiceProviderSearch, builder => builder + .Combine(options.DefaultPolicy) + .RequireScope(AuthorizationScope.ServiceProviderSearch)); + + options.AddPolicy(AuthorizationPolicy.Testing, builder => builder + .Combine(options.DefaultPolicy) + .RequireScope(AuthorizationScope.Testing)); + } +} diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationPolicy.cs b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationPolicy.cs new file mode 100644 index 000000000..9a227445a --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationPolicy.cs @@ -0,0 +1,29 @@ +using System.Collections.ObjectModel; + +namespace Digdir.Domain.Dialogporten.GraphQL.Common.Authorization; + +internal static class AuthorizationPolicy +{ + public const string EndUser = "enduser"; + public const string ServiceProvider = "serviceprovider"; + public const string ServiceProviderSearch = "serviceproviderSearch"; + public const string Testing = "testing"; +} + +internal static class AuthorizationScope +{ + public const string EndUser = "digdir:dialogporten"; + public const string ServiceProvider = "digdir:dialogporten.serviceprovider"; + public const string ServiceProviderSearch = "digdir:dialogporten.serviceprovider.search"; + public const string Testing = "digdir:dialogporten.developer.test"; + + internal static readonly Lazy> AllScopes = new(GetAll); + + private static ReadOnlyCollection GetAll() => + typeof(AuthorizationScope) + .GetFields() + .Where(x => x.IsLiteral && !x.IsInitOnly && x.DeclaringType == typeof(string)) + .Select(x => (string)x.GetRawConstantValue()!) + .ToList() + .AsReadOnly(); +} diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationPolicyBuilderExtensions.cs b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationPolicyBuilderExtensions.cs new file mode 100644 index 000000000..d1d2e2395 --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationPolicyBuilderExtensions.cs @@ -0,0 +1,21 @@ +using Digdir.Domain.Dialogporten.Application.Common.Extensions; +using Microsoft.AspNetCore.Authorization; + +namespace Digdir.Domain.Dialogporten.GraphQL.Common.Authorization; + +internal static class AuthorizationPolicyBuilderExtensions +{ + private const string ScopeClaim = "scope"; + private const char ScopeClaimSeparator = ' '; + + public static AuthorizationPolicyBuilder RequireScope(this AuthorizationPolicyBuilder builder, string scope) => + builder.RequireAssertion(ctx => ctx.User.Claims + .Where(x => x.Type == ScopeClaim) + .Select(x => x.Value) + .Any(scopeValue => scopeValue == scope || scopeValue + .Split(ScopeClaimSeparator, StringSplitOptions.RemoveEmptyEntries) + .Contains(scope))); + + public static AuthorizationPolicyBuilder RequireValidConsumerClaim(this AuthorizationPolicyBuilder builder) => + builder.RequireAssertion(ctx => ctx.User.TryGetOrgNumber(out _)); +} diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Constants.cs b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Constants.cs new file mode 100644 index 000000000..7d054cc36 --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Constants.cs @@ -0,0 +1,8 @@ +namespace Digdir.Domain.Dialogporten.GraphQL.Common; + +internal static class Constants +{ + internal const string Authorization = "Authorization"; + internal const string CurrentTokenIssuer = "CurrentIssuer"; +} + diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/Common/GraphQlSettings.cs b/src/Digdir.Domain.Dialogporten.GraphQL/Common/GraphQlSettings.cs new file mode 100644 index 000000000..756126a7c --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.GraphQL/Common/GraphQlSettings.cs @@ -0,0 +1,21 @@ +using Digdir.Domain.Dialogporten.GraphQL.Common.Authentication; +using FluentValidation; + +namespace Digdir.Domain.Dialogporten.GraphQL.Common; + +public sealed class GraphQlSettings +{ + public const string SectionName = "GraphQl"; + + public required AuthenticationOptions Authentication { get; init; } +} + +internal sealed class WebApiOptionsValidator : AbstractValidator +{ + public WebApiOptionsValidator( + IValidator authenticationOptionsValidator) + { + RuleFor(x => x.Authentication) + .SetValidator(authenticationOptionsValidator); + } +} diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/Digdir.Domain.Dialogporten.GraphQL.csproj b/src/Digdir.Domain.Dialogporten.GraphQL/Digdir.Domain.Dialogporten.GraphQL.csproj index eb6bda1bf..b22356814 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/Digdir.Domain.Dialogporten.GraphQL.csproj +++ b/src/Digdir.Domain.Dialogporten.GraphQL/Digdir.Domain.Dialogporten.GraphQL.csproj @@ -8,6 +8,8 @@ + + diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/Program.cs b/src/Digdir.Domain.Dialogporten.GraphQL/Program.cs index ea7b91dcf..b7c0241b9 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/Program.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/Program.cs @@ -3,6 +3,8 @@ using Digdir.Domain.Dialogporten.Application; using Digdir.Domain.Dialogporten.Application.Externals.Presentation; using Digdir.Domain.Dialogporten.GraphQL; +using Digdir.Domain.Dialogporten.GraphQL.Common.Authentication; +using Digdir.Domain.Dialogporten.GraphQL.Common.Authorization; using Digdir.Domain.Dialogporten.Infrastructure; using Digdir.Domain.Dialogporten.Infrastructure.Persistence; using Microsoft.ApplicationInsights.Extensibility; @@ -35,13 +37,15 @@ static void BuildAndRun(string[] args) { var builder = WebApplication.CreateBuilder(args); - // builder.Host.UseSerilog((context, services, configuration) => configuration - // .MinimumLevel.Warning() - // .MinimumLevel.Override("Microsoft.EntityFrameworkCore", Serilog.Events.LogEventLevel.Fatal) - // .ReadFrom.Configuration(context.Configuration) - // .ReadFrom.Services(services) - // .Enrich.FromLogContext() - // .WriteTo(Console.WriteLine())); + builder.Host.UseSerilog((context, services, configuration) => configuration + .MinimumLevel.Warning() + .MinimumLevel.Override("Microsoft.EntityFrameworkCore", Serilog.Events.LogEventLevel.Fatal) + .ReadFrom.Configuration(context.Configuration) + .ReadFrom.Services(services) + .Enrich.FromLogContext() + .WriteTo.ApplicationInsights( + services.GetRequiredService(), + TelemetryConverter.Traces)); /* TODOS: * - Gjør DialogDbContext til internal. Kan mÃ¥tte gjøre dette prosjektet til et "friend assembly" av infrastructure (internalsVisibleTo) @@ -52,19 +56,39 @@ static void BuildAndRun(string[] args) builder.Services + // Options setup + .ConfigureOptions() + + // Clean architecture projects .AddApplication(builder.Configuration, builder.Environment) .AddInfrastructure(builder.Configuration, builder.Environment) + + // Asp infra .AddAutoMapper(Assembly.GetExecutingAssembly()) + .AddApplicationInsightsTelemetry() // .AddApplicationInsightsTelemetry() .AddScoped() + + // Graph QL .AddGraphQLServer() + .AddAuthorization() .AddProjections() .AddFiltering() .AddSorting() .RegisterDbContext() - .AddQueryType(); + .AddQueryType() + .Services + + // Auth + .AddDialogportenAuthentication(builder.Configuration) + .AddAuthorization(); var app = builder.Build(); - app.MapGraphQL(); + + app.UseJwtSchemeSelector(); + app.UseAuthentication(); + app.UseAuthorization(); + + app.MapGraphQL().RequireAuthorization(); app.Run(); } diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/appsettings.Development.json b/src/Digdir.Domain.Dialogporten.GraphQL/appsettings.Development.json index b07097573..8521c47ec 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/appsettings.Development.json +++ b/src/Digdir.Domain.Dialogporten.GraphQL/appsettings.Development.json @@ -41,7 +41,7 @@ "BaseUri": "https://localhost:7214" } }, - "WebApi": { + "GraphQl": { "Authentication": { "JwtBearerTokenSchemas": [ { From 3b34c1a7188ec8498a9f6e45677be15aa524042b Mon Sep 17 00:00:00 2001 From: Magnus Sandgren <5285192+MagnusSandgren@users.noreply.github.com> Date: Mon, 8 Apr 2024 15:46:43 +0200 Subject: [PATCH 05/23] Add config and fluent validation of said config. --- src/Digdir.Domain.Dialogporten.GraphQL/Program.cs | 12 +++++++++++- .../Properties/launchSettings.json | 4 ++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/Program.cs b/src/Digdir.Domain.Dialogporten.GraphQL/Program.cs index b7c0241b9..7d959dc28 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/Program.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/Program.cs @@ -3,12 +3,15 @@ using Digdir.Domain.Dialogporten.Application; using Digdir.Domain.Dialogporten.Application.Externals.Presentation; using Digdir.Domain.Dialogporten.GraphQL; +using Digdir.Domain.Dialogporten.GraphQL.Common; using Digdir.Domain.Dialogporten.GraphQL.Common.Authentication; using Digdir.Domain.Dialogporten.GraphQL.Common.Authorization; using Digdir.Domain.Dialogporten.Infrastructure; using Digdir.Domain.Dialogporten.Infrastructure.Persistence; using Microsoft.ApplicationInsights.Extensibility; +using Digdir.Domain.Dialogporten.Application.Common.Extensions.OptionExtensions; using Serilog; +using FluentValidation; Log.Logger = new LoggerConfiguration() .MinimumLevel.Warning() @@ -47,6 +50,11 @@ static void BuildAndRun(string[] args) services.GetRequiredService(), TelemetryConverter.Traces)); + builder.Services + .AddOptions() + .Bind(builder.Configuration.GetSection(GraphQlSettings.SectionName)) + .ValidateFluently() + .ValidateOnStart(); /* TODOS: * - Gjør DialogDbContext til internal. Kan mÃ¥tte gjøre dette prosjektet til et "friend assembly" av infrastructure (internalsVisibleTo) * @@ -54,6 +62,7 @@ static void BuildAndRun(string[] args) * */ + var thisAssembly = Assembly.GetExecutingAssembly(); builder.Services // Options setup @@ -68,6 +77,7 @@ static void BuildAndRun(string[] args) .AddApplicationInsightsTelemetry() // .AddApplicationInsightsTelemetry() .AddScoped() + .AddValidatorsFromAssembly(thisAssembly, ServiceLifetime.Transient, includeInternalTypes: true) // Graph QL .AddGraphQLServer() @@ -89,6 +99,6 @@ static void BuildAndRun(string[] args) app.UseAuthentication(); app.UseAuthorization(); - app.MapGraphQL().RequireAuthorization(); + app.MapGraphQL(); app.Run(); } diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/Properties/launchSettings.json b/src/Digdir.Domain.Dialogporten.GraphQL/Properties/launchSettings.json index 778d377bf..2233da99c 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/Properties/launchSettings.json +++ b/src/Digdir.Domain.Dialogporten.GraphQL/Properties/launchSettings.json @@ -13,7 +13,7 @@ "commandName": "Project", "dotnetRunMessages": true, "launchBrowser": true, - "launchUrl": "swagger", + "launchUrl": "graphql", "applicationUrl": "http://localhost:5181", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" @@ -32,7 +32,7 @@ "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, - "launchUrl": "swagger", + "launchUrl": "graphql", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } From 3d9bd08875a6de5e054dd34b2a022993ee65c523 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Skogstad?= Date: Sun, 14 Apr 2024 22:27:57 +0200 Subject: [PATCH 06/23] Fix auth for banana cake pop? --- .../DialogQueries.cs | 2 ++ src/Digdir.Domain.Dialogporten.GraphQL/Program.cs | 10 +++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/DialogQueries.cs b/src/Digdir.Domain.Dialogporten.GraphQL/DialogQueries.cs index 493932a5d..7e6e161d7 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/DialogQueries.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/DialogQueries.cs @@ -7,9 +7,11 @@ using MediatR; using Yarp.ReverseProxy.Utilities; using Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.Dialogs.Queries.GetQueryable; +using HotChocolate.Authorization; namespace Digdir.Domain.Dialogporten.GraphQL; +[Authorize] public class DialogQueries { //[UsePaging] diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/Program.cs b/src/Digdir.Domain.Dialogporten.GraphQL/Program.cs index 7d959dc28..870817301 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/Program.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/Program.cs @@ -12,6 +12,7 @@ using Digdir.Domain.Dialogporten.Application.Common.Extensions.OptionExtensions; using Serilog; using FluentValidation; +using HotChocolate.AspNetCore; Log.Logger = new LoggerConfiguration() .MinimumLevel.Warning() @@ -99,6 +100,13 @@ static void BuildAndRun(string[] args) app.UseAuthentication(); app.UseAuthorization(); - app.MapGraphQL(); + app.MapGraphQL() + .AllowAnonymous() + .WithOptions(new GraphQLServerOptions + { + EnableSchemaRequests = builder.Environment.IsDevelopment(), + Tool = { Enable = builder.Environment.IsDevelopment() } + }); + app.Run(); } From c0e8db67032ac404a59b1dd91d642e3b4fbd4e0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Skogstad?= Date: Sun, 14 Apr 2024 23:30:57 +0200 Subject: [PATCH 07/23] c --- src/Digdir.Domain.Dialogporten.GraphQL/DialogQueries.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/DialogQueries.cs b/src/Digdir.Domain.Dialogporten.GraphQL/DialogQueries.cs index 7e6e161d7..438395125 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/DialogQueries.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/DialogQueries.cs @@ -7,11 +7,12 @@ using MediatR; using Yarp.ReverseProxy.Utilities; using Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.Dialogs.Queries.GetQueryable; +using Digdir.Domain.Dialogporten.GraphQL.Common.Authorization; using HotChocolate.Authorization; namespace Digdir.Domain.Dialogporten.GraphQL; -[Authorize] +[Authorize(Policy = AuthorizationPolicy.EndUser)] public class DialogQueries { //[UsePaging] From dddf4b3a8ba3d65052ca1a1ac696e8c96ffd8a64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Skogstad?= Date: Sun, 14 Apr 2024 23:32:17 +0200 Subject: [PATCH 08/23] --wip-- [skip ci] --- .../Program.cs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/Program.cs b/src/Digdir.Domain.Dialogporten.GraphQL/Program.cs index 870817301..d4689a144 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/Program.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/Program.cs @@ -100,12 +100,24 @@ static void BuildAndRun(string[] args) app.UseAuthentication(); app.UseAuthorization(); + // app.MapGraphQL() + // .AllowAnonymous() + // .WithOptions(new GraphQLServerOptions + // { + // EnableSchemaRequests = builder.Environment.IsDevelopment(), + // Tool = { Enable = builder.Environment.IsDevelopment() } + // }); + + app.MapBananaCakePop("/bcp"); app.MapGraphQL() - .AllowAnonymous() + .RequireAuthorization() .WithOptions(new GraphQLServerOptions { EnableSchemaRequests = builder.Environment.IsDevelopment(), - Tool = { Enable = builder.Environment.IsDevelopment() } + Tool = + { + Enable = builder.Environment.IsDevelopment(), + } }); app.Run(); From 818fd6227c7296a421fdea972b7e9511a69bf887 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Skogstad?= Date: Mon, 15 Apr 2024 17:04:22 +0200 Subject: [PATCH 09/23] --wip-- [skip ci] --- .../Queries/GetQueryable/GetDialogQuery.cs | 135 ---------- .../GetQueryable/GetQueryableDialogDto.cs | 120 --------- .../Queries/GetQueryable/MappingProfile.cs | 39 --- .../Dialogs/Entities/DialogStatus.cs | 10 +- .../Authentication}/LocalDevelopmentUser.cs | 2 +- .../DialogQueries.cs | 119 --------- .../EndUser/Common/ObjectTypes.cs | 7 + .../EndUser/DialogById/DialogByIdQuery.cs | 14 ++ .../EndUser/DialogById/MappingProfile.cs | 35 +++ .../EndUser/DialogById/ObjectTypes.cs | 229 +++++++++++++++++ .../EndUser/DialogQueries.cs | 53 ++++ .../EndUser/SearchDialog/MappingProfile.cs | 35 +++ .../EndUser/SearchDialog/ObjectTypes.cs | 235 ++++++++++++++++++ .../EndUser/SearchDialog/SearchDialogQuery.cs | 15 ++ .../Program.cs | 29 ++- 15 files changed, 647 insertions(+), 430 deletions(-) delete mode 100644 src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/GetQueryable/GetDialogQuery.cs delete mode 100644 src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/GetQueryable/GetQueryableDialogDto.cs delete mode 100644 src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/GetQueryable/MappingProfile.cs rename src/Digdir.Domain.Dialogporten.GraphQL/{ => Common/Authentication}/LocalDevelopmentUser.cs (95%) delete mode 100644 src/Digdir.Domain.Dialogporten.GraphQL/DialogQueries.cs create mode 100644 src/Digdir.Domain.Dialogporten.GraphQL/EndUser/Common/ObjectTypes.cs create mode 100644 src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogById/DialogByIdQuery.cs create mode 100644 src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogById/MappingProfile.cs create mode 100644 src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogById/ObjectTypes.cs create mode 100644 src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogQueries.cs create mode 100644 src/Digdir.Domain.Dialogporten.GraphQL/EndUser/SearchDialog/MappingProfile.cs create mode 100644 src/Digdir.Domain.Dialogporten.GraphQL/EndUser/SearchDialog/ObjectTypes.cs create mode 100644 src/Digdir.Domain.Dialogporten.GraphQL/EndUser/SearchDialog/SearchDialogQuery.cs diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/GetQueryable/GetDialogQuery.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/GetQueryable/GetDialogQuery.cs deleted file mode 100644 index 1b0e327f4..000000000 --- a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/GetQueryable/GetDialogQuery.cs +++ /dev/null @@ -1,135 +0,0 @@ -using System.Diagnostics; -using AutoMapper; -using AutoMapper.QueryableExtensions; -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.Pagination; -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.Dialogs.Queries.GetQueryable; - -public sealed class GetDialogQuery : IRequest> -{ - public string? Search { get; set; } - - public string? SearchCultureCode { get; set; } - - ///// - ///// Filter by one or more service resources - ///// - //public List? ServiceResource { get; init; } - - ///// - ///// Filter by one or more owning parties - ///// - //public List? Party { get; init; } -} - -internal sealed class GetDialogQueryableHandler : IRequestHandler> -{ - private readonly IDialogDbContext _db; - private readonly IMapper _mapper; - private readonly IUnitOfWork _unitOfWork; - private readonly IClock _clock; - private readonly IUserNameRegistry _userNameRegistry; - private readonly IAltinnAuthorization _altinnAuthorization; - - public GetDialogQueryableHandler( - IDialogDbContext db, - IMapper mapper, - IUnitOfWork unitOfWork, - IClock clock, - IUserNameRegistry userNameRegistry, - IAltinnAuthorization altinnAuthorization) - { - _db = db ?? throw new ArgumentNullException(nameof(db)); - _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper)); - _unitOfWork = unitOfWork ?? throw new ArgumentNullException(nameof(unitOfWork)); - _clock = clock ?? throw new ArgumentNullException(nameof(clock)); - _userNameRegistry = userNameRegistry ?? throw new ArgumentNullException(nameof(userNameRegistry)); - _altinnAuthorization = altinnAuthorization ?? throw new ArgumentNullException(nameof(altinnAuthorization)); - } - - public Task> Handle(GetDialogQuery request, CancellationToken cancellationToken) - { - var searchExpression = Expressions.LocalizedSearchExpression(request.Search, request.SearchCultureCode); - - var queryable = _db.Dialogs - .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!)) - ) - .Where(x => !x.VisibleFrom.HasValue || _clock.UtcNowOffset > x.VisibleFrom) - .Where(x => !x.ExpiresAt.HasValue || x.ExpiresAt > _clock.UtcNowOffset) - .ProjectTo(_mapper.ConfigurationProvider); - - return Task.FromResult(queryable); - //var userName = await _userNameRegistry.GetCurrentUserName(userPid, cancellationToken); - //// TODO: What if name lookup fails - //// https://github.com/digdir/dialogporten/issues/387 - //dialog.UpdateSeenAt(userPid, userName); - - //var saveResult = await _unitOfWork - // .WithoutAuditableSideEffects() - // .SaveChangesAsync(cancellationToken); - - //saveResult.Switch( - // success => { }, - // domainError => throw new UnreachableException("Should not get domain error when updating SeenAt."), - // concurrencyError => throw new UnreachableException("Should not get concurrencyError when updating SeenAt.")); - - // hash end user ids - //var salt = MappingUtils.GetHashSalt(); - //foreach (var activity in dialog.Activities) - //{ - // activity.SeenByEndUserId = MappingUtils.HashPid(activity.SeenByEndUserId, salt); - //} - - //var dto = _mapper.Map(dialog); - - //DecorateWithAuthorization(dto, authorizationResult); - - //return dto; - } - - //private static void DecorateWithAuthorization(GetDialogDto dto, DialogDetailsAuthorizationResult authorizationResult) - //{ - // foreach (var (action, resources) in authorizationResult.AuthorizedAltinnActions) - // { - // foreach (var apiAction in dto.ApiActions.Where(a => a.Action == action)) - // { - // if ((apiAction.AuthorizationAttribute is null && resources.Contains(Constants.MainResource)) - // || (apiAction.AuthorizationAttribute is not null && resources.Contains(apiAction.AuthorizationAttribute))) - // { - // apiAction.IsAuthorized = true; - // } - // } - - // foreach (var guiAction in dto.GuiActions.Where(a => a.Action == action)) - // { - // if ((guiAction.AuthorizationAttribute is null && resources.Contains(Constants.MainResource)) - // || (guiAction.AuthorizationAttribute is not null && resources.Contains(guiAction.AuthorizationAttribute))) - // { - // guiAction.IsAuthorized = true; - // } - // } - - // // Simple "read" on the main resource will give access to a dialog element, unless a authorization attribute is set, - // // in which case an "elementread" action is required - // foreach (var dialogElement in dto.Elements.Where( - // dialogElement => (dialogElement.AuthorizationAttribute is null) - // || (dialogElement.AuthorizationAttribute is not null - // && action == Constants.ElementReadAction))) - // { - // dialogElement.IsAuthorized = true; - // } - // } - //} -} diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/GetQueryable/GetQueryableDialogDto.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/GetQueryable/GetQueryableDialogDto.cs deleted file mode 100644 index 18b0546ea..000000000 --- a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/GetQueryable/GetQueryableDialogDto.cs +++ /dev/null @@ -1,120 +0,0 @@ -using Digdir.Domain.Dialogporten.Application.Features.V1.Common.Localizations; -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.Content; -using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Elements; -using Digdir.Domain.Dialogporten.Domain.Http; - -namespace Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.Dialogs.Queries.GetQueryable; - -public sealed class GetQueryableDialogDto -{ - public Guid Id { get; set; } - public Guid Revision { get; set; } - public string Org { get; set; } = null!; - public string ServiceResource { get; set; } = null!; - public string Party { get; set; } = null!; - public int? Progress { get; set; } - public string? ExtendedStatus { get; set; } - public string? ExternalReference { get; set; } - public DateTimeOffset? VisibleFrom { get; set; } - public DateTimeOffset? DueAt { get; set; } - public DateTimeOffset? ExpiresAt { get; set; } - public DateTimeOffset CreatedAt { get; set; } - public DateTimeOffset UpdatedAt { get; set; } - - //public DialogStatus.Values Status { get; set; } - - public List Content { get; set; } = []; - - public List Elements { get; set; } = []; - public List GuiActions { get; set; } = []; - public List ApiActions { get; set; } = []; - public List Activities { get; set; } = []; -} - -public sealed class GetDialogContentDto -{ - //public DialogContentType.Values Type { get; set; } - public List Value { get; set; } = []; -} - -public sealed class GetDialogDialogActivityDto -{ - public Guid Id { get; set; } - public DateTimeOffset? CreatedAt { get; set; } - public Uri? ExtendedType { get; set; } - public string? SeenByEndUserIdHash { get; init; } - - //public DialogActivityType.Values Type { get; set; } - - public Guid? RelatedActivityId { get; set; } - public Guid? DialogElementId { get; set; } - - public List? PerformedBy { get; set; } = []; - public List Description { get; set; } = []; -} - -public sealed class GetDialogDialogApiActionDto -{ - public Guid Id { get; set; } - public string Action { get; set; } = null!; - public string? AuthorizationAttribute { get; set; } - public bool IsAuthorized { get; set; } - - public Guid? DialogElementId { get; set; } - - public List Endpoints { get; set; } = []; -} - -public sealed class GetDialogDialogApiActionEndpointDto -{ - public Guid Id { get; set; } - public string? Version { get; set; } - public Uri Url { get; set; } = null!; - //public HttpVerb.Values HttpMethod { get; set; } - public Uri? DocumentationUrl { get; set; } - public Uri? RequestSchema { get; set; } - public Uri? ResponseSchema { get; set; } - public bool Deprecated { get; set; } - public DateTimeOffset? SunsetAt { get; set; } -} - -public sealed class GetDialogDialogGuiActionDto -{ - public Guid Id { get; set; } - public string Action { get; set; } = null!; - public Uri Url { get; set; } = null!; - public string? AuthorizationAttribute { get; set; } - public bool IsAuthorized { get; set; } - public bool IsBackChannel { get; set; } - public bool IsDeleteAction { get; set; } - - //public DialogGuiActionPriority.Values Priority { get; set; } - - public List Title { get; set; } = []; -} - -public sealed class GetDialogDialogElementDto -{ - public Guid Id { get; set; } - public Uri? Type { get; set; } - public string? ExternalReference { get; set; } - public string? AuthorizationAttribute { get; set; } - public bool IsAuthorized { get; set; } - - public Guid? RelatedDialogElementId { get; set; } - - public List DisplayName { get; set; } = []; - public List Urls { get; set; } = []; -} - -public sealed class GetDialogDialogElementUrlDto -{ - public Guid Id { get; set; } - public Uri Url { get; set; } = null!; - public string? MimeType { get; set; } - - //public DialogElementUrlConsumerType.Values ConsumerType { get; set; } -} diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/GetQueryable/MappingProfile.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/GetQueryable/MappingProfile.cs deleted file mode 100644 index 31170773e..000000000 --- a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/GetQueryable/MappingProfile.cs +++ /dev/null @@ -1,39 +0,0 @@ -using AutoMapper; -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.Content; -using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Elements; - -namespace Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.Dialogs.Queries.GetQueryable; - -internal sealed class MappingProfile : Profile -{ - public MappingProfile() - { - CreateMap() - .ForMember(dest => dest.Revision, opt => opt.MapFrom(src => src.Revision)); - //.ForMember(dest => dest.Status, opt => opt.MapFrom(src => src.StatusId)); - - CreateMap() - .ForMember(dest => dest.SeenByEndUserIdHash, opt => opt.MapFrom(src => src.SeenByEndUserId)); - //.ForMember(dest => dest.Type, opt => opt.MapFrom(src => src.TypeId)); - - CreateMap(); - - CreateMap(); - //.ForMember(dest => dest.HttpMethod, opt => opt.MapFrom(src => src.HttpMethodId)); - - CreateMap(); - //.ForMember(dest => dest.Priority, opt => opt.MapFrom(src => src.PriorityId)); - - CreateMap(); - - CreateMap(); - //.ForMember(dest => dest.ConsumerType, opt => opt.MapFrom(src => src.ConsumerTypeId)); - - CreateMap(); - //.ForMember(dest => dest.Type, opt => opt.MapFrom(src => src.TypeId)); - } - -} diff --git a/src/Digdir.Domain.Dialogporten.Domain/Dialogs/Entities/DialogStatus.cs b/src/Digdir.Domain.Dialogporten.Domain/Dialogs/Entities/DialogStatus.cs index 7443da2af..a1d2c0bdd 100644 --- a/src/Digdir.Domain.Dialogporten.Domain/Dialogs/Entities/DialogStatus.cs +++ b/src/Digdir.Domain.Dialogporten.Domain/Dialogs/Entities/DialogStatus.cs @@ -10,13 +10,13 @@ public DialogStatus(Values id) : base(id) { } public enum Values { /// - /// Dialogen er Ã¥ regne som ny. Brukes typisk for enkle meldinger som ikke krever noe - /// interaksjon, eller som et initielt steg for dialoger. Dette er default. + /// Dialogen er Ã¥ regne som ny. Brukes typisk for enkle meldinger som ikke krever noe + /// interaksjon, eller som et initielt steg for dialoger. Dette er default. /// New = 1, /// - /// Under arbeid. Generell status som brukes for dialogtjenester der ytterligere bruker-input er + /// Under arbeid. Generell status som brukes for dialogtjenester der ytterligere bruker-input er /// forventet. /// InProgress = 2, @@ -27,8 +27,8 @@ public enum Values Waiting = 3, /// - /// Dialogen er i en tilstand hvor den venter pÃ¥ signering. Typisk siste steg etter at all - /// utfylling er gjennomført og validert. + /// Dialogen er i en tilstand hvor den venter pÃ¥ signering. Typisk siste steg etter at all + /// utfylling er gjennomført og validert. /// Signing = 4, diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/LocalDevelopmentUser.cs b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authentication/LocalDevelopmentUser.cs similarity index 95% rename from src/Digdir.Domain.Dialogporten.GraphQL/LocalDevelopmentUser.cs rename to src/Digdir.Domain.Dialogporten.GraphQL/Common/Authentication/LocalDevelopmentUser.cs index 775f0ef34..499f1b16f 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/LocalDevelopmentUser.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authentication/LocalDevelopmentUser.cs @@ -2,7 +2,7 @@ using System.Security.Claims; using Digdir.Domain.Dialogporten.Application.Externals.Presentation; -namespace Digdir.Domain.Dialogporten.GraphQL; +namespace Digdir.Domain.Dialogporten.GraphQL.Common.Authentication; internal sealed class LocalDevelopmentUser : IUser { diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/DialogQueries.cs b/src/Digdir.Domain.Dialogporten.GraphQL/DialogQueries.cs deleted file mode 100644 index 438395125..000000000 --- a/src/Digdir.Domain.Dialogporten.GraphQL/DialogQueries.cs +++ /dev/null @@ -1,119 +0,0 @@ -using System.Threading; -using AutoMapper; -using Digdir.Domain.Dialogporten.Application.Common.Pagination; -using Digdir.Domain.Dialogporten.Application.Externals.AltinnAuthorization; -using Digdir.Domain.Dialogporten.Application.Externals; -using Digdir.Domain.Dialogporten.Application.Features.V1.Common.Localizations; -using MediatR; -using Yarp.ReverseProxy.Utilities; -using Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.Dialogs.Queries.GetQueryable; -using Digdir.Domain.Dialogporten.GraphQL.Common.Authorization; -using HotChocolate.Authorization; - -namespace Digdir.Domain.Dialogporten.GraphQL; - -[Authorize(Policy = AuthorizationPolicy.EndUser)] -public class DialogQueries -{ - //[UsePaging] - [UseProjection] - [UseFiltering] - [UseSorting] - public async Task> GetDialogs( - [Service] ISender mediator, - GetDialogQuery? query) - { - return await mediator.Send(query ?? new()); - } -} - -//public class MappingProfile : Profile -//{ -// public MappingProfile() -// { -// CreateMap() -// .ForMember(dest => dest.Status, opt => opt.MapFrom(src => src.Status)); - -// CreateMap() -// .ForMember(dest => dest.Type, opt => opt.MapFrom(src => src.Type)); - -// CreateMap(); -// } -//} - -//public enum DialogStatus -//{ -// New = 1, - -// /// -// /// Under arbeid. Generell status som brukes for dialogtjenester der ytterligere bruker-input er -// /// forventet. -// /// -// InProgress = 2, - -// /// -// /// Venter pÃ¥ tilbakemelding fra tjenesteeier -// /// -// Waiting = 3, - -// /// -// /// Dialogen er i en tilstand hvor den venter pÃ¥ signering. Typisk siste steg etter at all -// /// utfylling er gjennomført og validert. -// /// -// Signing = 4, - -// /// -// /// Dialogen ble avbrutt. Dette gjør at dialogen typisk fjernes fra normale GUI-visninger. -// /// -// Cancelled = 5, - -// /// -// /// Dialigen ble fullført. Dette gjør at dialogen typisk flyttes til et GUI-arkiv eller lignende. -// /// -// Completed = 6 -//} - -//public class Dialog -//{ -// public Guid Id { get; set; } -// public Guid Revision { get; set; } -// public string Org { get; set; } = null!; -// public string ServiceResource { get; set; } = null!; -// public string Party { get; set; } = null!; -// public int? Progress { get; set; } -// public string? ExtendedStatus { get; set; } -// public string? ExternalReference { get; set; } -// public DateTimeOffset? VisibleFrom { get; set; } -// public DateTimeOffset? DueAt { get; set; } -// public DateTimeOffset? ExpiresAt { get; set; } -// public DateTimeOffset CreatedAt { get; set; } -// public DateTimeOffset UpdatedAt { get; set; } - -// public DialogStatus Status { get; set; } - -// public List Content { get; set; } = []; -// // public List Elements { get; set; } = []; -// // public List GuiActions { get; set; } = []; -// // public List ApiActions { get; set; } = []; -// // public List Activities { get; set; } = []; -//} - -//public enum ContentType -//{ -// Title = 1, -// SenderName = 2, -// Summary = 3, -// AdditionalInfo = 4 -//} - -//public sealed class Content -//{ -// public ContentType Type { get; set; } -// public List Value { get; set; } = []; -//} - -//public sealed class Localization -//{ -// public string Value { get; set; } = null!; -// public string CultureCode { get; set; } = null!; -//} diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/Common/ObjectTypes.cs b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/Common/ObjectTypes.cs new file mode 100644 index 000000000..de8d33bb8 --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/Common/ObjectTypes.cs @@ -0,0 +1,7 @@ +namespace Digdir.Domain.Dialogporten.GraphQL.EndUser.Common; + +public sealed class Localization +{ + public string Value { get; set; } = null!; + public string CultureCode { get; set; } = null!; +} diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogById/DialogByIdQuery.cs b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogById/DialogByIdQuery.cs new file mode 100644 index 000000000..bc6c13c1c --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogById/DialogByIdQuery.cs @@ -0,0 +1,14 @@ +using AutoMapper; +using Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.Dialogs.Queries.Get; +using MediatR; + +namespace Digdir.Domain.Dialogporten.GraphQL.EndUser.DialogById; + +public interface IDialogByIdQuery +{ + Task GetDialogById( + [Service] ISender mediator, + [Service] IMapper mapper, + [Argument] Guid dialogId, + CancellationToken cancellationToken); +} diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogById/MappingProfile.cs b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogById/MappingProfile.cs new file mode 100644 index 000000000..b4cd8cbec --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogById/MappingProfile.cs @@ -0,0 +1,35 @@ +using AutoMapper; +using Digdir.Domain.Dialogporten.Application.Features.V1.Common.Localizations; +using Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.Dialogs.Queries.Get; +using Digdir.Domain.Dialogporten.GraphQL.EndUser.Common; + +namespace Digdir.Domain.Dialogporten.GraphQL.EndUser.DialogById; + +public class MappingProfile : Profile +{ + public MappingProfile() + { + CreateMap() + .ForMember(dest => dest.Status, opt => opt.MapFrom(src => src.Status)); + + CreateMap() + .ForMember(dest => dest.Type, opt => opt.MapFrom(src => src.Type)); + + CreateMap(); + CreateMap() + .ForMember(dest => dest.ConsumerType, opt => opt.MapFrom(src => src.ConsumerType)); + + CreateMap() + .ForMember(dest => dest.Priority, opt => opt.MapFrom(src => src.Priority)); + + CreateMap(); + CreateMap() + .ForMember(dest => dest.HttpMethod, opt => opt.MapFrom(src => src.HttpMethod)); + + CreateMap() + .ForMember(dest => dest.Type, opt => opt.MapFrom(src => src.Type)); + + CreateMap(); + CreateMap(); + } +} diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogById/ObjectTypes.cs b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogById/ObjectTypes.cs new file mode 100644 index 000000000..979a1d23b --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogById/ObjectTypes.cs @@ -0,0 +1,229 @@ +using Digdir.Domain.Dialogporten.GraphQL.EndUser.Common; + +namespace Digdir.Domain.Dialogporten.GraphQL.EndUser.DialogById; + +public enum DialogStatus +{ + New = 1, + + /// + /// Under arbeid. Generell status som brukes for dialogtjenester der ytterligere bruker-input er + /// forventet. + /// + InProgress = 2, + + /// + /// Venter pÃ¥ tilbakemelding fra tjenesteeier + /// + Waiting = 3, + + /// + /// Dialogen er i en tilstand hvor den venter pÃ¥ signering. Typisk siste steg etter at all + /// utfylling er gjennomført og validert. + /// + Signing = 4, + + /// + /// Dialogen ble avbrutt. Dette gjør at dialogen typisk fjernes fra normale GUI-visninger. + /// + Cancelled = 5, + + /// + /// Dialigen ble fullført. Dette gjør at dialogen typisk flyttes til et GUI-arkiv eller lignende. + /// + Completed = 6 +} + +public sealed class Dialog +{ + public Guid Id { get; set; } + public Guid Revision { get; set; } + public string Org { get; set; } = null!; + public string ServiceResource { get; set; } = null!; + public string Party { get; set; } = null!; + public int? Progress { get; set; } + public string? ExtendedStatus { get; set; } + public string? ExternalReference { get; set; } + public DateTimeOffset? VisibleFrom { get; set; } + public DateTimeOffset? DueAt { get; set; } + public DateTimeOffset? ExpiresAt { get; set; } + public DateTimeOffset CreatedAt { get; set; } + public DateTimeOffset UpdatedAt { get; set; } + + public string? DialogToken { get; set; } + + public DialogStatus Status { get; set; } + + public List Content { get; set; } = []; + public List Elements { get; set; } = []; + public List GuiActions { get; set; } = []; + public List ApiActions { get; set; } = []; + public List Activities { get; set; } = []; + public List SeenSinceLastUpdate { get; set; } = []; +} + +public sealed class SeenLog +{ + public Guid Id { get; set; } + public DateTimeOffset CreatedAt { get; set; } + + public string EndUserIdHash { get; set; } = null!; + + public string? EndUserName { get; set; } + + public bool IsCurrentEndUser { get; set; } +} + +public sealed class Activity +{ + public Guid Id { get; set; } + public DateTimeOffset? CreatedAt { get; set; } + public Uri? ExtendedType { get; set; } + + public ActivityType Type { get; set; } + + public Guid? RelatedActivityId { get; set; } + public Guid? DialogElementId { get; set; } + + public List? PerformedBy { get; set; } = []; + public List Description { get; set; } = []; +} + +public enum ActivityType +{ + /// + /// Refererer en innsending utført av party som er mottatt hos tjenestetilbyder. + /// + Submission = 1, + + /// + /// Indikerer en tilbakemelding fra tjenestetilbyder pÃ¥ en innsending. Inneholder + /// referanse til den aktuelle innsendingen. + /// + Feedback = 2, + + /// + /// Informasjon fra tjenestetilbyder, ikke (direkte) relatert til noen innsending. + /// + Information = 3, + + /// + /// Brukes for Ã¥ indikere en feilsituasjon, typisk pÃ¥ en innsending. Inneholder en + /// tjenestespesifikk activityErrorCode. + /// + Error = 4, + + /// + /// Indikerer at dialogen er lukket for videre endring. Dette skjer typisk ved fullføring + /// av dialogen, eller sletting. + /// + Closed = 5, + + /// + /// NÃ¥r dialogen blir videresendt (tilgang delegert) av noen med tilgang til andre. + /// + Forwarded = 7 +} + +public sealed class ApiAction +{ + public Guid Id { get; set; } + public string Action { get; set; } = null!; + public string? AuthorizationAttribute { get; set; } + public bool IsAuthorized { get; set; } + + public Guid? DialogElementId { get; set; } + + public List Endpoints { get; set; } = []; +} + +// ReSharper disable InconsistentNaming +public enum HttpVerb +{ + GET = 1, + POST = 2, + PUT = 3, + PATCH = 4, + DELETE = 5, + HEAD = 6, + OPTIONS = 7, + TRACE = 8, + CONNECT = 9 +} + +public sealed class ApiActionEndpoint +{ + public Guid Id { get; set; } + public string? Version { get; set; } + public Uri Url { get; set; } = null!; + public HttpVerb HttpMethod { get; set; } + public Uri? DocumentationUrl { get; set; } + public Uri? RequestSchema { get; set; } + public Uri? ResponseSchema { get; set; } + public bool Deprecated { get; set; } + public DateTimeOffset? SunsetAt { get; set; } +} + +public sealed class GuiAction +{ + public Guid Id { get; set; } + public string Action { get; set; } = null!; + public Uri Url { get; set; } = null!; + public string? AuthorizationAttribute { get; set; } + public bool IsAuthorized { get; set; } + public bool IsBackChannel { get; set; } + public bool IsDeleteAction { get; set; } + + public GuiActionPriority Priority { get; set; } + + public List Title { get; set; } = []; +} + +public enum GuiActionPriority +{ + Primary = 1, + Secondary = 2, + Tertiary = 3 +} + +public sealed class Element +{ + public Guid Id { get; set; } + public Uri? Type { get; set; } + public string? ExternalReference { get; set; } + public string? AuthorizationAttribute { get; set; } + public bool IsAuthorized { get; set; } + + public Guid? RelatedDialogElementId { get; set; } + + public List DisplayName { get; set; } = []; + public List Urls { get; set; } = []; +} + +public sealed class ElementUrl +{ + public Guid Id { get; set; } + public Uri Url { get; set; } = null!; + public string? MimeType { get; set; } + + public ElementUrlConsumer ConsumerType { get; set; } +} + +public enum ElementUrlConsumer +{ + Gui = 1, + Api = 2 +} +public enum ContentType +{ + Title = 1, + SenderName = 2, + Summary = 3, + AdditionalInfo = 4 +} + +public sealed class Content +{ + public ContentType Type { get; set; } + public List Value { get; set; } = []; +} diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogQueries.cs b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogQueries.cs new file mode 100644 index 000000000..a982e1437 --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogQueries.cs @@ -0,0 +1,53 @@ +using AutoMapper; +using Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.Dialogs.Queries.Get; +using Digdir.Domain.Dialogporten.GraphQL.Common.Authorization; +using Digdir.Domain.Dialogporten.GraphQL.EndUser.DialogById; +using Digdir.Domain.Dialogporten.GraphQL.EndUser.SearchDialog; +using HotChocolate.Authorization; +using MediatR; + +namespace Digdir.Domain.Dialogporten.GraphQL.EndUser; + +[Authorize(Policy = AuthorizationPolicy.EndUser)] +public class DialogQueries : ISearchDialogQuery, IDialogByIdQuery +{ + public async Task GetDialogById( + [Service] ISender mediator, + [Service] IMapper mapper, + [Argument] Guid dialogId, + CancellationToken cancellationToken) + { + var request = new GetDialogQuery { DialogId = dialogId }; + var result = await mediator.Send(request, cancellationToken); + var foo = result.Match( + dialog => dialog, + notFound => throw new NotImplementedException("Not found"), + deleted => throw new NotImplementedException("Deleted"), + forbidden => throw new NotImplementedException("Forbidden")); + + var dialog = mapper.Map(foo); + + return dialog; + // return foo; + } + + public async Task SearchDialog( + [Service] ISender mediator, + [Service] IMapper mapper, + [Argument] Guid dialogId, + CancellationToken cancellationToken) + { + var request = new GetDialogQuery { DialogId = dialogId }; + var result = await mediator.Send(request, cancellationToken); + var foo = result.Match( + dialog => dialog, + notFound => throw new NotImplementedException("Not found"), + deleted => throw new NotImplementedException("Deleted"), + forbidden => throw new NotImplementedException("Forbidden")); + + var dialog = mapper.Map(foo); + + return dialog; + // return foo; + } +} diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/SearchDialog/MappingProfile.cs b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/SearchDialog/MappingProfile.cs new file mode 100644 index 000000000..ba16e7de3 --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/SearchDialog/MappingProfile.cs @@ -0,0 +1,35 @@ +using AutoMapper; +using Digdir.Domain.Dialogporten.Application.Features.V1.Common.Localizations; +using Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.Dialogs.Queries.Get; +using Digdir.Domain.Dialogporten.GraphQL.EndUser.Common; + +namespace Digdir.Domain.Dialogporten.GraphQL.EndUser.SearchDialog; + +public class MappingProfile : Profile +{ + public MappingProfile() + { + CreateMap() + .ForMember(dest => dest.StatusSearch, opt => opt.MapFrom(src => src.Status)); + + CreateMap() + .ForMember(dest => dest.TypeSearch, opt => opt.MapFrom(src => src.Type)); + + CreateMap(); + CreateMap() + .ForMember(dest => dest.ConsumerSearchType, opt => opt.MapFrom(src => src.ConsumerType)); + + CreateMap() + .ForMember(dest => dest.PrioritySearch, opt => opt.MapFrom(src => src.Priority)); + + CreateMap(); + CreateMap() + .ForMember(dest => dest.HttpMethod, opt => opt.MapFrom(src => src.HttpMethod)); + + CreateMap() + .ForMember(dest => dest.TypeSearch, opt => opt.MapFrom(src => src.Type)); + + CreateMap(); + CreateMap(); + } +} diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/SearchDialog/ObjectTypes.cs b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/SearchDialog/ObjectTypes.cs new file mode 100644 index 000000000..802438752 --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/SearchDialog/ObjectTypes.cs @@ -0,0 +1,235 @@ +using Digdir.Domain.Dialogporten.GraphQL.EndUser.Common; + +namespace Digdir.Domain.Dialogporten.GraphQL.EndUser.SearchDialog; + +public enum DialogStatusSearch +{ + New = 1, + + /// + /// Under arbeid. Generell status som brukes for dialogtjenester der ytterligere bruker-input er + /// forventet. + /// + InProgress = 2, + + /// + /// Venter pÃ¥ tilbakemelding fra tjenesteeier + /// + Waiting = 3, + + /// + /// Dialogen er i en tilstand hvor den venter pÃ¥ signering. Typisk siste steg etter at all + /// utfylling er gjennomført og validert. + /// + Signing = 4, + + /// + /// Dialogen ble avbrutt. Dette gjør at dialogen typisk fjernes fra normale GUI-visninger. + /// + Cancelled = 5, + + /// + /// Dialigen ble fullført. Dette gjør at dialogen typisk flyttes til et GUI-arkiv eller lignende. + /// + Completed = 6 +} + +public sealed class DialogSearch +{ + public Guid Id { get; set; } + public Guid Revision { get; set; } + public string Org { get; set; } = null!; + public string ServiceResource { get; set; } = null!; + public string Party { get; set; } = null!; + public int? Progress { get; set; } + public string? ExtendedStatus { get; set; } + public string? ExternalReference { get; set; } + public DateTimeOffset? VisibleFrom { get; set; } + public DateTimeOffset? DueAt { get; set; } + public DateTimeOffset? ExpiresAt { get; set; } + public DateTimeOffset CreatedAt { get; set; } + public DateTimeOffset UpdatedAt { get; set; } + + public string? DialogToken { get; set; } + + public DialogStatusSearch StatusSearch { get; set; } + + public List Content { get; set; } = []; + public List Elements { get; set; } = []; + public List GuiActions { get; set; } = []; + public List ApiActions { get; set; } = []; + public List Activities { get; set; } = []; + public List SeenSinceLastUpdate { get; set; } = []; +} + +public sealed class SeenLogSearch +{ + public Guid Id { get; set; } + public DateTimeOffset CreatedAt { get; set; } + + public string EndUserIdHash { get; set; } = null!; + + public string? EndUserName { get; set; } + + public bool IsCurrentEndUser { get; set; } +} + +public sealed class ActivitySearch +{ + public Guid Id { get; set; } + public DateTimeOffset? CreatedAt { get; set; } + public Uri? ExtendedType { get; set; } + + public ActivityTypeSearch TypeSearch { get; set; } + + public Guid? RelatedActivityId { get; set; } + public Guid? DialogElementId { get; set; } + + public List? PerformedBy { get; set; } = []; + public List Description { get; set; } = []; +} + +public enum ActivityTypeSearch +{ + /// + /// Refererer en innsending utført av party som er mottatt hos tjenestetilbyder. + /// + Submission = 1, + + /// + /// Indikerer en tilbakemelding fra tjenestetilbyder pÃ¥ en innsending. Inneholder + /// referanse til den aktuelle innsendingen. + /// + Feedback = 2, + + /// + /// Informasjon fra tjenestetilbyder, ikke (direkte) relatert til noen innsending. + /// + Information = 3, + + /// + /// Brukes for Ã¥ indikere en feilsituasjon, typisk pÃ¥ en innsending. Inneholder en + /// tjenestespesifikk activityErrorCode. + /// + Error = 4, + + /// + /// Indikerer at dialogen er lukket for videre endring. Dette skjer typisk ved fullføring + /// av dialogen, eller sletting. + /// + Closed = 5, + + /// + /// NÃ¥r dialogen blir videresendt (tilgang delegert) av noen med tilgang til andre. + /// + Forwarded = 7 +} + +public sealed class ApiActionSearch +{ + public Guid Id { get; set; } + public string Action { get; set; } = null!; + public string? AuthorizationAttribute { get; set; } + public bool IsAuthorized { get; set; } + + public Guid? DialogElementId { get; set; } + + public List Endpoints { get; set; } = []; +} + +// ReSharper disable InconsistentNaming +public enum HttpVerbSearch +{ + GET = 1, + POST = 2, + PUT = 3, + PATCH = 4, + DELETE = 5, + HEAD = 6, + OPTIONS = 7, + TRACE = 8, + CONNECT = 9 +} + +public sealed class ApiActionEndpointSearch +{ + public Guid Id { get; set; } + public string? Version { get; set; } + public Uri Url { get; set; } = null!; + public HttpVerbSearch HttpMethod { get; set; } + public Uri? DocumentationUrl { get; set; } + public Uri? RequestSchema { get; set; } + public Uri? ResponseSchema { get; set; } + public bool Deprecated { get; set; } + public DateTimeOffset? SunsetAt { get; set; } +} + +public sealed class GuiActionSearch +{ + public Guid Id { get; set; } + public string Action { get; set; } = null!; + public Uri Url { get; set; } = null!; + public string? AuthorizationAttribute { get; set; } + public bool IsAuthorized { get; set; } + public bool IsBackChannel { get; set; } + public bool IsDeleteAction { get; set; } + + public GuiActionPrioritySearch PrioritySearch { get; set; } + + public List Title { get; set; } = []; +} + +public enum GuiActionPrioritySearch +{ + Primary = 1, + Secondary = 2, + Tertiary = 3 +} + +public sealed class ElementSearch +{ + public Guid Id { get; set; } + public Uri? Type { get; set; } + public string? ExternalReference { get; set; } + public string? AuthorizationAttribute { get; set; } + public bool IsAuthorized { get; set; } + + public Guid? RelatedDialogElementId { get; set; } + + public List DisplayName { get; set; } = []; + public List Urls { get; set; } = []; +} + +public sealed class ElementUrlSearch +{ + public Guid Id { get; set; } + public Uri Url { get; set; } = null!; + public string? MimeType { get; set; } + + public ElementUrlConsumerSearch ConsumerSearchType { get; set; } +} + +public enum ElementUrlConsumerSearch +{ + Gui = 1, + Api = 2 +} +public enum ContentTypeSearch +{ + Title = 1, + SenderName = 2, + Summary = 3, + AdditionalInfo = 4 +} + +public sealed class ContentSearch +{ + public ContentTypeSearch TypeSearch { get; set; } + public List Value { get; set; } = []; +} + +// public sealed class Localization +// { +// public string Value { get; set; } = null!; +// public string CultureCode { get; set; } = null!; +// } diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/SearchDialog/SearchDialogQuery.cs b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/SearchDialog/SearchDialogQuery.cs new file mode 100644 index 000000000..ad4360ef5 --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/SearchDialog/SearchDialogQuery.cs @@ -0,0 +1,15 @@ +using AutoMapper; +using Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.Dialogs.Queries.Get; +using MediatR; + +namespace Digdir.Domain.Dialogporten.GraphQL.EndUser.SearchDialog; + +public interface ISearchDialogQuery +{ + Task SearchDialog( + [Service] ISender mediator, + [Service] IMapper mapper, + [Argument] Guid dialogId, + CancellationToken cancellationToken); + +} diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/Program.cs b/src/Digdir.Domain.Dialogporten.GraphQL/Program.cs index d4689a144..ada597669 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/Program.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/Program.cs @@ -1,8 +1,8 @@ using System.Globalization; using System.Reflection; using Digdir.Domain.Dialogporten.Application; +using Digdir.Domain.Dialogporten.Application.Common.Extensions; using Digdir.Domain.Dialogporten.Application.Externals.Presentation; -using Digdir.Domain.Dialogporten.GraphQL; using Digdir.Domain.Dialogporten.GraphQL.Common; using Digdir.Domain.Dialogporten.GraphQL.Common.Authentication; using Digdir.Domain.Dialogporten.GraphQL.Common.Authorization; @@ -10,9 +10,11 @@ using Digdir.Domain.Dialogporten.Infrastructure.Persistence; using Microsoft.ApplicationInsights.Extensibility; using Digdir.Domain.Dialogporten.Application.Common.Extensions.OptionExtensions; +using Digdir.Domain.Dialogporten.GraphQL.EndUser; using Serilog; using FluentValidation; using HotChocolate.AspNetCore; +using Microsoft.AspNetCore.Authorization; Log.Logger = new LoggerConfiguration() .MinimumLevel.Warning() @@ -47,6 +49,7 @@ static void BuildAndRun(string[] args) .ReadFrom.Configuration(context.Configuration) .ReadFrom.Services(services) .Enrich.FromLogContext() + .WriteTo.Console(formatProvider: CultureInfo.InvariantCulture) .WriteTo.ApplicationInsights( services.GetRequiredService(), TelemetryConverter.Traces)); @@ -63,6 +66,14 @@ static void BuildAndRun(string[] args) * */ + if (builder.Environment.IsDevelopment()) + { + var localDevelopmentSettings = builder.Configuration.GetLocalDevelopmentSettings(); + builder.Services + .ReplaceSingleton(predicate: localDevelopmentSettings.UseLocalDevelopmentUser) + .ReplaceSingleton( + predicate: localDevelopmentSettings.DisableAuth); + } var thisAssembly = Assembly.GetExecutingAssembly(); builder.Services @@ -100,23 +111,19 @@ static void BuildAndRun(string[] args) app.UseAuthentication(); app.UseAuthorization(); - // app.MapGraphQL() - // .AllowAnonymous() - // .WithOptions(new GraphQLServerOptions - // { - // EnableSchemaRequests = builder.Environment.IsDevelopment(), - // Tool = { Enable = builder.Environment.IsDevelopment() } - // }); + if (app.Environment.IsDevelopment()) + { + app.MapBananaCakePop("/bcp"); + } - app.MapBananaCakePop("/bcp"); app.MapGraphQL() .RequireAuthorization() .WithOptions(new GraphQLServerOptions { - EnableSchemaRequests = builder.Environment.IsDevelopment(), + EnableSchemaRequests = true, Tool = { - Enable = builder.Environment.IsDevelopment(), + Enable = false } }); From ad0f37934651802c2137d9dc5a7d5027ad57615a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Skogstad?= Date: Mon, 15 Apr 2024 17:05:16 +0200 Subject: [PATCH 10/23] --wip-- [skip ci] --- .../EndUser/DialogById/DialogByIdQuery.cs | 1 - .../EndUser/SearchDialog/SearchDialogQuery.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogById/DialogByIdQuery.cs b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogById/DialogByIdQuery.cs index bc6c13c1c..c6b823886 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogById/DialogByIdQuery.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogById/DialogByIdQuery.cs @@ -1,5 +1,4 @@ using AutoMapper; -using Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.Dialogs.Queries.Get; using MediatR; namespace Digdir.Domain.Dialogporten.GraphQL.EndUser.DialogById; diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/SearchDialog/SearchDialogQuery.cs b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/SearchDialog/SearchDialogQuery.cs index ad4360ef5..7a317fcdf 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/SearchDialog/SearchDialogQuery.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/SearchDialog/SearchDialogQuery.cs @@ -1,5 +1,4 @@ using AutoMapper; -using Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.Dialogs.Queries.Get; using MediatR; namespace Digdir.Domain.Dialogporten.GraphQL.EndUser.SearchDialog; From 37c2332dec0b31a61b4baded73e6f9ad867d5fac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Skogstad?= Date: Mon, 15 Apr 2024 17:06:56 +0200 Subject: [PATCH 11/23] --wip-- [skip ci] --- .../EndUser/DialogQueries.cs | 10 ++++------ src/Digdir.Domain.Dialogporten.GraphQL/Program.cs | 1 + 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogQueries.cs b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogQueries.cs index a982e1437..7c6487262 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogQueries.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogQueries.cs @@ -19,16 +19,15 @@ public async Task GetDialogById( { var request = new GetDialogQuery { DialogId = dialogId }; var result = await mediator.Send(request, cancellationToken); - var foo = result.Match( + var getDialogResult = result.Match( dialog => dialog, notFound => throw new NotImplementedException("Not found"), deleted => throw new NotImplementedException("Deleted"), forbidden => throw new NotImplementedException("Forbidden")); - var dialog = mapper.Map(foo); + var dialog = mapper.Map(getDialogResult); return dialog; - // return foo; } public async Task SearchDialog( @@ -39,15 +38,14 @@ public async Task SearchDialog( { var request = new GetDialogQuery { DialogId = dialogId }; var result = await mediator.Send(request, cancellationToken); - var foo = result.Match( + var dialogSearchResult = result.Match( dialog => dialog, notFound => throw new NotImplementedException("Not found"), deleted => throw new NotImplementedException("Deleted"), forbidden => throw new NotImplementedException("Forbidden")); - var dialog = mapper.Map(foo); + var dialog = mapper.Map(dialogSearchResult); return dialog; - // return foo; } } diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/Program.cs b/src/Digdir.Domain.Dialogporten.GraphQL/Program.cs index ada597669..a56613a1b 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/Program.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/Program.cs @@ -113,6 +113,7 @@ static void BuildAndRun(string[] args) if (app.Environment.IsDevelopment()) { + // GUI endpoint app.MapBananaCakePop("/bcp"); } From 2faa4fda9842a9c5516f2ea2feafecd5e8d20001 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Skogstad?= Date: Mon, 15 Apr 2024 22:28:34 +0200 Subject: [PATCH 12/23] --wip-- [skip ci] --- .../EndUser/Common/MappingProfile.cs | 27 ++ .../EndUser/Common/ObjectTypes.cs | 109 ++++++ .../EndUser/DialogById/MappingProfile.cs | 7 +- .../EndUser/DialogById/ObjectTypes.cs | 108 ------ .../EndUser/DialogQueries.cs | 25 +- .../EndUser/SearchDialog/MappingProfile.cs | 35 -- .../EndUser/SearchDialog/ObjectTypes.cs | 235 ------------ .../EndUser/SearchDialogs/MappingProfile.cs | 42 +++ .../EndUser/SearchDialogs/ObjectTypes.cs | 338 ++++++++++++++++++ .../SearchDialogQuery.cs | 7 +- 10 files changed, 534 insertions(+), 399 deletions(-) create mode 100644 src/Digdir.Domain.Dialogporten.GraphQL/EndUser/Common/MappingProfile.cs delete mode 100644 src/Digdir.Domain.Dialogporten.GraphQL/EndUser/SearchDialog/MappingProfile.cs delete mode 100644 src/Digdir.Domain.Dialogporten.GraphQL/EndUser/SearchDialog/ObjectTypes.cs create mode 100644 src/Digdir.Domain.Dialogporten.GraphQL/EndUser/SearchDialogs/MappingProfile.cs create mode 100644 src/Digdir.Domain.Dialogporten.GraphQL/EndUser/SearchDialogs/ObjectTypes.cs rename src/Digdir.Domain.Dialogporten.GraphQL/EndUser/{SearchDialog => SearchDialogs}/SearchDialogQuery.cs (75%) diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/Common/MappingProfile.cs b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/Common/MappingProfile.cs new file mode 100644 index 000000000..8924c36d8 --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/Common/MappingProfile.cs @@ -0,0 +1,27 @@ +using AutoMapper; +using Digdir.Domain.Dialogporten.Application.Features.V1.Common.Localizations; +using Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.Dialogs.Queries.Get; +using Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.Dialogs.Queries.Search; + +namespace Digdir.Domain.Dialogporten.GraphQL.EndUser.Common; + +public class MappingProfile : Profile +{ + public MappingProfile() + { + CreateMap(); + + CreateMap(); + CreateMap(); + + CreateMap() + .ForMember(dest => dest.Type, opt => opt.MapFrom(src => src.Type)); + CreateMap() + .ForMember(dest => dest.Type, opt => opt.MapFrom(src => src.Type)); + + CreateMap() + .ForMember(dest => dest.Type, opt => opt.MapFrom(src => src.Type)); + CreateMap() + .ForMember(dest => dest.Type, opt => opt.MapFrom(src => src.Type)); + } +} diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/Common/ObjectTypes.cs b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/Common/ObjectTypes.cs index de8d33bb8..84136a717 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/Common/ObjectTypes.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/Common/ObjectTypes.cs @@ -5,3 +5,112 @@ public sealed class Localization public string Value { get; set; } = null!; public string CultureCode { get; set; } = null!; } + +public enum ContentType +{ + Title = 1, + SenderName = 2, + Summary = 3, + AdditionalInfo = 4 +} + +public sealed class Content +{ + public ContentType Type { get; set; } + public List Value { get; set; } = []; +} + +public sealed class SeenLog +{ + public Guid Id { get; set; } + public DateTimeOffset CreatedAt { get; set; } + + public string EndUserIdHash { get; set; } = null!; + + public string? EndUserName { get; set; } + + public bool IsCurrentEndUser { get; set; } +} + +public sealed class Activity +{ + public Guid Id { get; set; } + public DateTimeOffset? CreatedAt { get; set; } + public Uri? ExtendedType { get; set; } + + public ActivityType Type { get; set; } + + public Guid? RelatedActivityId { get; set; } + public Guid? DialogElementId { get; set; } + + public List? PerformedBy { get; set; } = []; + public List Description { get; set; } = []; +} + +public enum ActivityType +{ + /// + /// Refererer en innsending utført av party som er mottatt hos tjenestetilbyder. + /// + Submission = 1, + + /// + /// Indikerer en tilbakemelding fra tjenestetilbyder pÃ¥ en innsending. Inneholder + /// referanse til den aktuelle innsendingen. + /// + Feedback = 2, + + /// + /// Informasjon fra tjenestetilbyder, ikke (direkte) relatert til noen innsending. + /// + Information = 3, + + /// + /// Brukes for Ã¥ indikere en feilsituasjon, typisk pÃ¥ en innsending. Inneholder en + /// tjenestespesifikk activityErrorCode. + /// + Error = 4, + + /// + /// Indikerer at dialogen er lukket for videre endring. Dette skjer typisk ved fullføring + /// av dialogen, eller sletting. + /// + Closed = 5, + + /// + /// NÃ¥r dialogen blir videresendt (tilgang delegert) av noen med tilgang til andre. + /// + Forwarded = 7 +} + +public enum DialogStatus +{ + New = 1, + + /// + /// Under arbeid. Generell status som brukes for dialogtjenester der ytterligere bruker-input er + /// forventet. + /// + InProgress = 2, + + /// + /// Venter pÃ¥ tilbakemelding fra tjenesteeier + /// + Waiting = 3, + + /// + /// Dialogen er i en tilstand hvor den venter pÃ¥ signering. Typisk siste steg etter at all + /// utfylling er gjennomført og validert. + /// + Signing = 4, + + /// + /// Dialogen ble avbrutt. Dette gjør at dialogen typisk fjernes fra normale GUI-visninger. + /// + Cancelled = 5, + + /// + /// Dialigen ble fullført. Dette gjør at dialogen typisk flyttes til et GUI-arkiv eller lignende. + /// + Completed = 6 +} diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogById/MappingProfile.cs b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogById/MappingProfile.cs index b4cd8cbec..5082d7d78 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogById/MappingProfile.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogById/MappingProfile.cs @@ -12,8 +12,7 @@ public MappingProfile() CreateMap() .ForMember(dest => dest.Status, opt => opt.MapFrom(src => src.Status)); - CreateMap() - .ForMember(dest => dest.Type, opt => opt.MapFrom(src => src.Type)); + CreateMap(); CreateMap() @@ -26,10 +25,6 @@ public MappingProfile() CreateMap() .ForMember(dest => dest.HttpMethod, opt => opt.MapFrom(src => src.HttpMethod)); - CreateMap() - .ForMember(dest => dest.Type, opt => opt.MapFrom(src => src.Type)); - CreateMap(); - CreateMap(); } } diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogById/ObjectTypes.cs b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogById/ObjectTypes.cs index 979a1d23b..46a905d1f 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogById/ObjectTypes.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogById/ObjectTypes.cs @@ -2,38 +2,6 @@ namespace Digdir.Domain.Dialogporten.GraphQL.EndUser.DialogById; -public enum DialogStatus -{ - New = 1, - - /// - /// Under arbeid. Generell status som brukes for dialogtjenester der ytterligere bruker-input er - /// forventet. - /// - InProgress = 2, - - /// - /// Venter pÃ¥ tilbakemelding fra tjenesteeier - /// - Waiting = 3, - - /// - /// Dialogen er i en tilstand hvor den venter pÃ¥ signering. Typisk siste steg etter at all - /// utfylling er gjennomført og validert. - /// - Signing = 4, - - /// - /// Dialogen ble avbrutt. Dette gjør at dialogen typisk fjernes fra normale GUI-visninger. - /// - Cancelled = 5, - - /// - /// Dialigen ble fullført. Dette gjør at dialogen typisk flyttes til et GUI-arkiv eller lignende. - /// - Completed = 6 -} - public sealed class Dialog { public Guid Id { get; set; } @@ -62,69 +30,6 @@ public sealed class Dialog public List SeenSinceLastUpdate { get; set; } = []; } -public sealed class SeenLog -{ - public Guid Id { get; set; } - public DateTimeOffset CreatedAt { get; set; } - - public string EndUserIdHash { get; set; } = null!; - - public string? EndUserName { get; set; } - - public bool IsCurrentEndUser { get; set; } -} - -public sealed class Activity -{ - public Guid Id { get; set; } - public DateTimeOffset? CreatedAt { get; set; } - public Uri? ExtendedType { get; set; } - - public ActivityType Type { get; set; } - - public Guid? RelatedActivityId { get; set; } - public Guid? DialogElementId { get; set; } - - public List? PerformedBy { get; set; } = []; - public List Description { get; set; } = []; -} - -public enum ActivityType -{ - /// - /// Refererer en innsending utført av party som er mottatt hos tjenestetilbyder. - /// - Submission = 1, - - /// - /// Indikerer en tilbakemelding fra tjenestetilbyder pÃ¥ en innsending. Inneholder - /// referanse til den aktuelle innsendingen. - /// - Feedback = 2, - - /// - /// Informasjon fra tjenestetilbyder, ikke (direkte) relatert til noen innsending. - /// - Information = 3, - - /// - /// Brukes for Ã¥ indikere en feilsituasjon, typisk pÃ¥ en innsending. Inneholder en - /// tjenestespesifikk activityErrorCode. - /// - Error = 4, - - /// - /// Indikerer at dialogen er lukket for videre endring. Dette skjer typisk ved fullføring - /// av dialogen, eller sletting. - /// - Closed = 5, - - /// - /// NÃ¥r dialogen blir videresendt (tilgang delegert) av noen med tilgang til andre. - /// - Forwarded = 7 -} - public sealed class ApiAction { public Guid Id { get; set; } @@ -214,16 +119,3 @@ public enum ElementUrlConsumer Gui = 1, Api = 2 } -public enum ContentType -{ - Title = 1, - SenderName = 2, - Summary = 3, - AdditionalInfo = 4 -} - -public sealed class Content -{ - public ContentType Type { get; set; } - public List Value { get; set; } = []; -} diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogQueries.cs b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogQueries.cs index 7c6487262..c34fb86ce 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogQueries.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogQueries.cs @@ -1,8 +1,9 @@ using AutoMapper; using Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.Dialogs.Queries.Get; +using Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.Dialogs.Queries.Search; using Digdir.Domain.Dialogporten.GraphQL.Common.Authorization; using Digdir.Domain.Dialogporten.GraphQL.EndUser.DialogById; -using Digdir.Domain.Dialogporten.GraphQL.EndUser.SearchDialog; +using Digdir.Domain.Dialogporten.GraphQL.EndUser.SearchDialogs; using HotChocolate.Authorization; using MediatR; @@ -30,22 +31,24 @@ public async Task GetDialogById( return dialog; } - public async Task SearchDialog( + public async Task SearchDialogs( [Service] ISender mediator, [Service] IMapper mapper, - [Argument] Guid dialogId, + SearchDialogInput input, CancellationToken cancellationToken) { - var request = new GetDialogQuery { DialogId = dialogId }; - var result = await mediator.Send(request, cancellationToken); - var dialogSearchResult = result.Match( - dialog => dialog, - notFound => throw new NotImplementedException("Not found"), - deleted => throw new NotImplementedException("Deleted"), + + var searchDialogQuery = mapper.Map(input); + + var result = await mediator.Send(searchDialogQuery, cancellationToken); + + var searchResultOneOf = result.Match( + paginatedList => paginatedList, + validationError => throw new NotImplementedException("Validation error"), forbidden => throw new NotImplementedException("Forbidden")); - var dialog = mapper.Map(dialogSearchResult); + var dialogSearchResult = mapper.Map(searchResultOneOf); - return dialog; + return dialogSearchResult; } } diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/SearchDialog/MappingProfile.cs b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/SearchDialog/MappingProfile.cs deleted file mode 100644 index ba16e7de3..000000000 --- a/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/SearchDialog/MappingProfile.cs +++ /dev/null @@ -1,35 +0,0 @@ -using AutoMapper; -using Digdir.Domain.Dialogporten.Application.Features.V1.Common.Localizations; -using Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.Dialogs.Queries.Get; -using Digdir.Domain.Dialogporten.GraphQL.EndUser.Common; - -namespace Digdir.Domain.Dialogporten.GraphQL.EndUser.SearchDialog; - -public class MappingProfile : Profile -{ - public MappingProfile() - { - CreateMap() - .ForMember(dest => dest.StatusSearch, opt => opt.MapFrom(src => src.Status)); - - CreateMap() - .ForMember(dest => dest.TypeSearch, opt => opt.MapFrom(src => src.Type)); - - CreateMap(); - CreateMap() - .ForMember(dest => dest.ConsumerSearchType, opt => opt.MapFrom(src => src.ConsumerType)); - - CreateMap() - .ForMember(dest => dest.PrioritySearch, opt => opt.MapFrom(src => src.Priority)); - - CreateMap(); - CreateMap() - .ForMember(dest => dest.HttpMethod, opt => opt.MapFrom(src => src.HttpMethod)); - - CreateMap() - .ForMember(dest => dest.TypeSearch, opt => opt.MapFrom(src => src.Type)); - - CreateMap(); - CreateMap(); - } -} diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/SearchDialog/ObjectTypes.cs b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/SearchDialog/ObjectTypes.cs deleted file mode 100644 index 802438752..000000000 --- a/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/SearchDialog/ObjectTypes.cs +++ /dev/null @@ -1,235 +0,0 @@ -using Digdir.Domain.Dialogporten.GraphQL.EndUser.Common; - -namespace Digdir.Domain.Dialogporten.GraphQL.EndUser.SearchDialog; - -public enum DialogStatusSearch -{ - New = 1, - - /// - /// Under arbeid. Generell status som brukes for dialogtjenester der ytterligere bruker-input er - /// forventet. - /// - InProgress = 2, - - /// - /// Venter pÃ¥ tilbakemelding fra tjenesteeier - /// - Waiting = 3, - - /// - /// Dialogen er i en tilstand hvor den venter pÃ¥ signering. Typisk siste steg etter at all - /// utfylling er gjennomført og validert. - /// - Signing = 4, - - /// - /// Dialogen ble avbrutt. Dette gjør at dialogen typisk fjernes fra normale GUI-visninger. - /// - Cancelled = 5, - - /// - /// Dialigen ble fullført. Dette gjør at dialogen typisk flyttes til et GUI-arkiv eller lignende. - /// - Completed = 6 -} - -public sealed class DialogSearch -{ - public Guid Id { get; set; } - public Guid Revision { get; set; } - public string Org { get; set; } = null!; - public string ServiceResource { get; set; } = null!; - public string Party { get; set; } = null!; - public int? Progress { get; set; } - public string? ExtendedStatus { get; set; } - public string? ExternalReference { get; set; } - public DateTimeOffset? VisibleFrom { get; set; } - public DateTimeOffset? DueAt { get; set; } - public DateTimeOffset? ExpiresAt { get; set; } - public DateTimeOffset CreatedAt { get; set; } - public DateTimeOffset UpdatedAt { get; set; } - - public string? DialogToken { get; set; } - - public DialogStatusSearch StatusSearch { get; set; } - - public List Content { get; set; } = []; - public List Elements { get; set; } = []; - public List GuiActions { get; set; } = []; - public List ApiActions { get; set; } = []; - public List Activities { get; set; } = []; - public List SeenSinceLastUpdate { get; set; } = []; -} - -public sealed class SeenLogSearch -{ - public Guid Id { get; set; } - public DateTimeOffset CreatedAt { get; set; } - - public string EndUserIdHash { get; set; } = null!; - - public string? EndUserName { get; set; } - - public bool IsCurrentEndUser { get; set; } -} - -public sealed class ActivitySearch -{ - public Guid Id { get; set; } - public DateTimeOffset? CreatedAt { get; set; } - public Uri? ExtendedType { get; set; } - - public ActivityTypeSearch TypeSearch { get; set; } - - public Guid? RelatedActivityId { get; set; } - public Guid? DialogElementId { get; set; } - - public List? PerformedBy { get; set; } = []; - public List Description { get; set; } = []; -} - -public enum ActivityTypeSearch -{ - /// - /// Refererer en innsending utført av party som er mottatt hos tjenestetilbyder. - /// - Submission = 1, - - /// - /// Indikerer en tilbakemelding fra tjenestetilbyder pÃ¥ en innsending. Inneholder - /// referanse til den aktuelle innsendingen. - /// - Feedback = 2, - - /// - /// Informasjon fra tjenestetilbyder, ikke (direkte) relatert til noen innsending. - /// - Information = 3, - - /// - /// Brukes for Ã¥ indikere en feilsituasjon, typisk pÃ¥ en innsending. Inneholder en - /// tjenestespesifikk activityErrorCode. - /// - Error = 4, - - /// - /// Indikerer at dialogen er lukket for videre endring. Dette skjer typisk ved fullføring - /// av dialogen, eller sletting. - /// - Closed = 5, - - /// - /// NÃ¥r dialogen blir videresendt (tilgang delegert) av noen med tilgang til andre. - /// - Forwarded = 7 -} - -public sealed class ApiActionSearch -{ - public Guid Id { get; set; } - public string Action { get; set; } = null!; - public string? AuthorizationAttribute { get; set; } - public bool IsAuthorized { get; set; } - - public Guid? DialogElementId { get; set; } - - public List Endpoints { get; set; } = []; -} - -// ReSharper disable InconsistentNaming -public enum HttpVerbSearch -{ - GET = 1, - POST = 2, - PUT = 3, - PATCH = 4, - DELETE = 5, - HEAD = 6, - OPTIONS = 7, - TRACE = 8, - CONNECT = 9 -} - -public sealed class ApiActionEndpointSearch -{ - public Guid Id { get; set; } - public string? Version { get; set; } - public Uri Url { get; set; } = null!; - public HttpVerbSearch HttpMethod { get; set; } - public Uri? DocumentationUrl { get; set; } - public Uri? RequestSchema { get; set; } - public Uri? ResponseSchema { get; set; } - public bool Deprecated { get; set; } - public DateTimeOffset? SunsetAt { get; set; } -} - -public sealed class GuiActionSearch -{ - public Guid Id { get; set; } - public string Action { get; set; } = null!; - public Uri Url { get; set; } = null!; - public string? AuthorizationAttribute { get; set; } - public bool IsAuthorized { get; set; } - public bool IsBackChannel { get; set; } - public bool IsDeleteAction { get; set; } - - public GuiActionPrioritySearch PrioritySearch { get; set; } - - public List Title { get; set; } = []; -} - -public enum GuiActionPrioritySearch -{ - Primary = 1, - Secondary = 2, - Tertiary = 3 -} - -public sealed class ElementSearch -{ - public Guid Id { get; set; } - public Uri? Type { get; set; } - public string? ExternalReference { get; set; } - public string? AuthorizationAttribute { get; set; } - public bool IsAuthorized { get; set; } - - public Guid? RelatedDialogElementId { get; set; } - - public List DisplayName { get; set; } = []; - public List Urls { get; set; } = []; -} - -public sealed class ElementUrlSearch -{ - public Guid Id { get; set; } - public Uri Url { get; set; } = null!; - public string? MimeType { get; set; } - - public ElementUrlConsumerSearch ConsumerSearchType { get; set; } -} - -public enum ElementUrlConsumerSearch -{ - Gui = 1, - Api = 2 -} -public enum ContentTypeSearch -{ - Title = 1, - SenderName = 2, - Summary = 3, - AdditionalInfo = 4 -} - -public sealed class ContentSearch -{ - public ContentTypeSearch TypeSearch { get; set; } - public List Value { get; set; } = []; -} - -// public sealed class Localization -// { -// public string Value { get; set; } = null!; -// public string CultureCode { get; set; } = null!; -// } diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/SearchDialogs/MappingProfile.cs b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/SearchDialogs/MappingProfile.cs new file mode 100644 index 000000000..d363e152a --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/SearchDialogs/MappingProfile.cs @@ -0,0 +1,42 @@ +using AutoMapper; +using Digdir.Domain.Dialogporten.Application.Common.Pagination; +using Digdir.Domain.Dialogporten.Application.Features.V1.Common.Localizations; +using Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.Dialogs.Queries.Get; +using Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.Dialogs.Queries.Search; +using Digdir.Domain.Dialogporten.GraphQL.EndUser.Common; + +namespace Digdir.Domain.Dialogporten.GraphQL.EndUser.SearchDialogs; + +public class MappingProfile : Profile +{ + public MappingProfile() + { + CreateMap() + .ForMember(dest => dest.Status, opt => opt.MapFrom(src => src.Status)); + + CreateMap, SearchDialogsPayload>(); + + CreateMap(); + // CreateMap() + // .ForMember(dest => dest.StatusSearch, opt => opt.MapFrom(src => src.Status)); + // + // CreateMap() + // .ForMember(dest => dest.TypeSearch, opt => opt.MapFrom(src => src.Type)); + // + // CreateMap(); + // CreateMap() + // .ForMember(dest => dest.ConsumerSearchType, opt => opt.MapFrom(src => src.ConsumerType)); + // + // CreateMap() + // .ForMember(dest => dest.PrioritySearch, opt => opt.MapFrom(src => src.Priority)); + // + // CreateMap(); + // CreateMap() + // .ForMember(dest => dest.HttpMethod, opt => opt.MapFrom(src => src.HttpMethod)); + // + // CreateMap() + // .ForMember(dest => dest.TypeSearch, opt => opt.MapFrom(src => src.Type)); + // + // CreateMap(); + } +} diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/SearchDialogs/ObjectTypes.cs b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/SearchDialogs/ObjectTypes.cs new file mode 100644 index 000000000..f1a3f7a54 --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/SearchDialogs/ObjectTypes.cs @@ -0,0 +1,338 @@ +using Digdir.Domain.Dialogporten.GraphQL.EndUser.Common; + +namespace Digdir.Domain.Dialogporten.GraphQL.EndUser.SearchDialogs; + +public sealed class SearchDialogsPayload +{ + public List Items { get; } = []; + public bool HasNextPage { get; } + public string? ContinuationToken { get; } + public string OrderBy { get; } = null!; +} + +public sealed class SearchDialog +{ + public Guid Id { get; set; } + public string Org { get; set; } = null!; + public string ServiceResource { get; set; } = null!; + public string Party { get; set; } = null!; + public int? Progress { get; set; } + public int? GuiAttachmentCount { get; set; } + public string? ExtendedStatus { get; set; } + public DateTimeOffset CreatedAt { get; set; } + public DateTimeOffset UpdatedAt { get; set; } + public DateTimeOffset? DueAt { get; set; } + + public DialogStatus Status { get; set; } + + public Activity? LatestActivity { get; set; } + + public List Content { get; set; } = []; + public List SeenSinceLastUpdate { get; set; } = []; +} + +public sealed class SearchDialogInput +{ + /// + /// Filter by one or more service owner codes + /// + public List? Org { get; init; } + + /// + /// Filter by one or more service resources + /// + public List? ServiceResource { get; init; } + + /// + /// Filter by one or more owning parties + /// + public List? Party { get; init; } + + /// + /// Filter by one or more extended statuses + /// + public List? ExtendedStatus { get; init; } + + /// + /// Filter by external reference + /// + public string? ExternalReference { get; init; } + + /// + /// Filter by status + /// + public List? Status { get; init; } + + /// + /// Only return dialogs created after this date + /// + public DateTimeOffset? CreatedAfter { get; init; } + + /// + /// Only return dialogs created before this date + /// + public DateTimeOffset? CreatedBefore { get; init; } + + /// + /// Only return dialogs updated after this date + /// + public DateTimeOffset? UpdatedAfter { get; init; } + + /// + /// Only return dialogs updated before this date + /// + public DateTimeOffset? UpdatedBefore { get; init; } + + /// + /// Only return dialogs with due date after this date + /// + public DateTimeOffset? DueAfter { get; init; } + + /// + /// Only return dialogs with due date before this date + /// + public DateTimeOffset? DueBefore { get; init; } + + /// + /// Search string for free text search. Will attempt to fuzzily match in all free text fields in the aggregate + /// + public string? Search { get; init; } + + /// + /// Limit free text search to texts with this culture code, e.g. \"nb-NO\". Default: search all culture codes + /// + public string? SearchCultureCode { get; init; } +} + +// +// public enum DialogStatusSearch +// { +// New = 1, +// +// /// +// /// Under arbeid. Generell status som brukes for dialogtjenester der ytterligere bruker-input er +// /// forventet. +// /// +// InProgress = 2, +// +// /// +// /// Venter pÃ¥ tilbakemelding fra tjenesteeier +// /// +// Waiting = 3, +// +// /// +// /// Dialogen er i en tilstand hvor den venter pÃ¥ signering. Typisk siste steg etter at all +// /// utfylling er gjennomført og validert. +// /// +// Signing = 4, +// +// /// +// /// Dialogen ble avbrutt. Dette gjør at dialogen typisk fjernes fra normale GUI-visninger. +// /// +// Cancelled = 5, +// +// /// +// /// Dialigen ble fullført. Dette gjør at dialogen typisk flyttes til et GUI-arkiv eller lignende. +// /// +// Completed = 6 +// } +// +// public sealed class DialogSearch +// { +// public Guid Id { get; set; } +// public Guid Revision { get; set; } +// public string Org { get; set; } = null!; +// public string ServiceResource { get; set; } = null!; +// public string Party { get; set; } = null!; +// public int? Progress { get; set; } +// public string? ExtendedStatus { get; set; } +// public string? ExternalReference { get; set; } +// public DateTimeOffset? VisibleFrom { get; set; } +// public DateTimeOffset? DueAt { get; set; } +// public DateTimeOffset? ExpiresAt { get; set; } +// public DateTimeOffset CreatedAt { get; set; } +// public DateTimeOffset UpdatedAt { get; set; } +// +// public string? DialogToken { get; set; } +// +// public DialogStatusSearch StatusSearch { get; set; } +// +// public List Content { get; set; } = []; +// public List Elements { get; set; } = []; +// public List GuiActions { get; set; } = []; +// public List ApiActions { get; set; } = []; +// public List Activities { get; set; } = []; +// public List SeenSinceLastUpdate { get; set; } = []; +// } +// +// public sealed class SeenLogSearch +// { +// public Guid Id { get; set; } +// public DateTimeOffset CreatedAt { get; set; } +// +// public string EndUserIdHash { get; set; } = null!; +// +// public string? EndUserName { get; set; } +// +// public bool IsCurrentEndUser { get; set; } +// } +// +// public sealed class ActivitySearch +// { +// public Guid Id { get; set; } +// public DateTimeOffset? CreatedAt { get; set; } +// public Uri? ExtendedType { get; set; } +// +// public ActivityTypeSearch TypeSearch { get; set; } +// +// public Guid? RelatedActivityId { get; set; } +// public Guid? DialogElementId { get; set; } +// +// public List? PerformedBy { get; set; } = []; +// public List Description { get; set; } = []; +// } +// +// public enum ActivityTypeSearch +// { +// /// +// /// Refererer en innsending utført av party som er mottatt hos tjenestetilbyder. +// /// +// Submission = 1, +// +// /// +// /// Indikerer en tilbakemelding fra tjenestetilbyder pÃ¥ en innsending. Inneholder +// /// referanse til den aktuelle innsendingen. +// /// +// Feedback = 2, +// +// /// +// /// Informasjon fra tjenestetilbyder, ikke (direkte) relatert til noen innsending. +// /// +// Information = 3, +// +// /// +// /// Brukes for Ã¥ indikere en feilsituasjon, typisk pÃ¥ en innsending. Inneholder en +// /// tjenestespesifikk activityErrorCode. +// /// +// Error = 4, +// +// /// +// /// Indikerer at dialogen er lukket for videre endring. Dette skjer typisk ved fullføring +// /// av dialogen, eller sletting. +// /// +// Closed = 5, +// +// /// +// /// NÃ¥r dialogen blir videresendt (tilgang delegert) av noen med tilgang til andre. +// /// +// Forwarded = 7 +// } +// +// public sealed class ApiActionSearch +// { +// public Guid Id { get; set; } +// public string Action { get; set; } = null!; +// public string? AuthorizationAttribute { get; set; } +// public bool IsAuthorized { get; set; } +// +// public Guid? DialogElementId { get; set; } +// +// public List Endpoints { get; set; } = []; +// } +// +// // ReSharper disable InconsistentNaming +// public enum HttpVerbSearch +// { +// GET = 1, +// POST = 2, +// PUT = 3, +// PATCH = 4, +// DELETE = 5, +// HEAD = 6, +// OPTIONS = 7, +// TRACE = 8, +// CONNECT = 9 +// } +// +// public sealed class ApiActionEndpointSearch +// { +// public Guid Id { get; set; } +// public string? Version { get; set; } +// public Uri Url { get; set; } = null!; +// public HttpVerbSearch HttpMethod { get; set; } +// public Uri? DocumentationUrl { get; set; } +// public Uri? RequestSchema { get; set; } +// public Uri? ResponseSchema { get; set; } +// public bool Deprecated { get; set; } +// public DateTimeOffset? SunsetAt { get; set; } +// } +// +// public sealed class GuiActionSearch +// { +// public Guid Id { get; set; } +// public string Action { get; set; } = null!; +// public Uri Url { get; set; } = null!; +// public string? AuthorizationAttribute { get; set; } +// public bool IsAuthorized { get; set; } +// public bool IsBackChannel { get; set; } +// public bool IsDeleteAction { get; set; } +// +// public GuiActionPrioritySearch PrioritySearch { get; set; } +// +// public List Title { get; set; } = []; +// } +// +// public enum GuiActionPrioritySearch +// { +// Primary = 1, +// Secondary = 2, +// Tertiary = 3 +// } +// +// public sealed class ElementSearch +// { +// public Guid Id { get; set; } +// public Uri? Type { get; set; } +// public string? ExternalReference { get; set; } +// public string? AuthorizationAttribute { get; set; } +// public bool IsAuthorized { get; set; } +// +// public Guid? RelatedDialogElementId { get; set; } +// +// public List DisplayName { get; set; } = []; +// public List Urls { get; set; } = []; +// } +// +// public sealed class ElementUrlSearch +// { +// public Guid Id { get; set; } +// public Uri Url { get; set; } = null!; +// public string? MimeType { get; set; } +// +// public ElementUrlConsumerSearch ConsumerSearchType { get; set; } +// } +// +// public enum ElementUrlConsumerSearch +// { +// Gui = 1, +// Api = 2 +// } +// public enum ContentTypeSearch +// { +// Title = 1, +// SenderName = 2, +// Summary = 3, +// AdditionalInfo = 4 +// } +// +// public sealed class ContentSearch +// { +// public ContentTypeSearch TypeSearch { get; set; } +// public List Value { get; set; } = []; +// } +// +// // public sealed class Localization +// // { +// // public string Value { get; set; } = null!; +// // public string CultureCode { get; set; } = null!; +// // } diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/SearchDialog/SearchDialogQuery.cs b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/SearchDialogs/SearchDialogQuery.cs similarity index 75% rename from src/Digdir.Domain.Dialogporten.GraphQL/EndUser/SearchDialog/SearchDialogQuery.cs rename to src/Digdir.Domain.Dialogporten.GraphQL/EndUser/SearchDialogs/SearchDialogQuery.cs index 7a317fcdf..c27fcf974 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/SearchDialog/SearchDialogQuery.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/SearchDialogs/SearchDialogQuery.cs @@ -1,14 +1,13 @@ using AutoMapper; using MediatR; -namespace Digdir.Domain.Dialogporten.GraphQL.EndUser.SearchDialog; +namespace Digdir.Domain.Dialogporten.GraphQL.EndUser.SearchDialogs; public interface ISearchDialogQuery { - Task SearchDialog( + Task SearchDialogs( [Service] ISender mediator, [Service] IMapper mapper, - [Argument] Guid dialogId, + SearchDialogInput input, CancellationToken cancellationToken); - } From d0be71f8d319bafc983b1cabf8d014569b42f2ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Skogstad?= Date: Tue, 16 Apr 2024 10:01:35 +0200 Subject: [PATCH 13/23] rename settings --- .../Common/GraphQlSettings.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/Common/GraphQlSettings.cs b/src/Digdir.Domain.Dialogporten.GraphQL/Common/GraphQlSettings.cs index 756126a7c..e65004c58 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/Common/GraphQlSettings.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/Common/GraphQlSettings.cs @@ -10,9 +10,9 @@ public sealed class GraphQlSettings public required AuthenticationOptions Authentication { get; init; } } -internal sealed class WebApiOptionsValidator : AbstractValidator +internal sealed class GraphQlOptionsValidator : AbstractValidator { - public WebApiOptionsValidator( + public GraphQlOptionsValidator( IValidator authenticationOptionsValidator) { RuleFor(x => x.Authentication) From 5a4bd98b8614b9007f8641d146f9a8ae7e23f301 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Skogstad?= Date: Tue, 16 Apr 2024 10:20:30 +0200 Subject: [PATCH 14/23] Translate object type descriptions --- .../EndUser/Common/ObjectTypes.cs | 50 +++++-------------- 1 file changed, 12 insertions(+), 38 deletions(-) diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/Common/ObjectTypes.cs b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/Common/ObjectTypes.cs index 84136a717..ec6718f8c 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/Common/ObjectTypes.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/Common/ObjectTypes.cs @@ -49,68 +49,42 @@ public sealed class Activity public enum ActivityType { - /// - /// Refererer en innsending utført av party som er mottatt hos tjenestetilbyder. - /// + [GraphQLDescription("Refers to a submission made by a party that has been received by the service provider.")] Submission = 1, - /// - /// Indikerer en tilbakemelding fra tjenestetilbyder pÃ¥ en innsending. Inneholder - /// referanse til den aktuelle innsendingen. - /// + [GraphQLDescription("Indicates feedback from the service provider on a submission. Contains a reference to the current submission.")] Feedback = 2, - /// - /// Informasjon fra tjenestetilbyder, ikke (direkte) relatert til noen innsending. - /// + [GraphQLDescription("Information from the service provider, not (directly) related to any submission.")] Information = 3, - /// - /// Brukes for Ã¥ indikere en feilsituasjon, typisk pÃ¥ en innsending. Inneholder en - /// tjenestespesifikk activityErrorCode. - /// + [GraphQLDescription("Used to indicate an error situation, typically on a submission. Contains a service-specific activityErrorCode.")] Error = 4, - /// - /// Indikerer at dialogen er lukket for videre endring. Dette skjer typisk ved fullføring - /// av dialogen, eller sletting. - /// + [GraphQLDescription("Indicates that the dialog is closed for further changes. This typically happens when the dialog is completed or deleted.")] Closed = 5, - /// - /// NÃ¥r dialogen blir videresendt (tilgang delegert) av noen med tilgang til andre. - /// + [GraphQLDescription("When the dialog is forwarded (delegated access) by someone with access to others.")] Forwarded = 7 } public enum DialogStatus { + [GraphQLDescription("New")] New = 1, - /// - /// Under arbeid. Generell status som brukes for dialogtjenester der ytterligere bruker-input er - /// forventet. - /// + [GraphQLDescription("In progress. General status used for dialog services where further user input is expected.")] InProgress = 2, - /// - /// Venter pÃ¥ tilbakemelding fra tjenesteeier - /// + [GraphQLDescription("Waiting for feedback from the service provider")] Waiting = 3, - /// - /// Dialogen er i en tilstand hvor den venter pÃ¥ signering. Typisk siste steg etter at all - /// utfylling er gjennomført og validert. - /// + [GraphQLDescription("The dialog is in a state where it is waiting for signing. Typically the last step after all completion is carried out and validated.")] Signing = 4, - /// - /// Dialogen ble avbrutt. Dette gjør at dialogen typisk fjernes fra normale GUI-visninger. - /// + [GraphQLDescription("The dialog was cancelled. This typically removes the dialog from normal GUI views.")] Cancelled = 5, - /// - /// Dialigen ble fullført. Dette gjør at dialogen typisk flyttes til et GUI-arkiv eller lignende. - /// + [GraphQLDescription("The dialog was completed. This typically moves the dialog to a GUI archive or similar.")] Completed = 6 } From 45cc73c5fe40f2a4d0ac0cdfe430d501de2366b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Skogstad?= Date: Tue, 16 Apr 2024 10:21:29 +0200 Subject: [PATCH 15/23] --wip-- [skip ci] --- .../EndUser/SearchDialogs/MappingProfile.cs | 21 ------------------- 1 file changed, 21 deletions(-) diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/SearchDialogs/MappingProfile.cs b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/SearchDialogs/MappingProfile.cs index d363e152a..a04aa829f 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/SearchDialogs/MappingProfile.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/SearchDialogs/MappingProfile.cs @@ -17,26 +17,5 @@ public MappingProfile() CreateMap, SearchDialogsPayload>(); CreateMap(); - // CreateMap() - // .ForMember(dest => dest.StatusSearch, opt => opt.MapFrom(src => src.Status)); - // - // CreateMap() - // .ForMember(dest => dest.TypeSearch, opt => opt.MapFrom(src => src.Type)); - // - // CreateMap(); - // CreateMap() - // .ForMember(dest => dest.ConsumerSearchType, opt => opt.MapFrom(src => src.ConsumerType)); - // - // CreateMap() - // .ForMember(dest => dest.PrioritySearch, opt => opt.MapFrom(src => src.Priority)); - // - // CreateMap(); - // CreateMap() - // .ForMember(dest => dest.HttpMethod, opt => opt.MapFrom(src => src.HttpMethod)); - // - // CreateMap() - // .ForMember(dest => dest.TypeSearch, opt => opt.MapFrom(src => src.Type)); - // - // CreateMap(); } } From e1befcb5c45ea78800293568887293147a3e9976 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Skogstad?= Date: Tue, 16 Apr 2024 10:27:38 +0200 Subject: [PATCH 16/23] --wip-- [skip ci] --- .../EndUser/SearchDialogs/ObjectTypes.cs | 289 +----------------- 1 file changed, 14 insertions(+), 275 deletions(-) diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/SearchDialogs/ObjectTypes.cs b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/SearchDialogs/ObjectTypes.cs index f1a3f7a54..1129a43dc 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/SearchDialogs/ObjectTypes.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/SearchDialogs/ObjectTypes.cs @@ -33,306 +33,45 @@ public sealed class SearchDialog public sealed class SearchDialogInput { - /// - /// Filter by one or more service owner codes - /// + [GraphQLDescription("Filter by one or more service owner codes")] public List? Org { get; init; } - /// - /// Filter by one or more service resources - /// + [GraphQLDescription("Filter by one or more service resources")] public List? ServiceResource { get; init; } - /// - /// Filter by one or more owning parties - /// + [GraphQLDescription("Filter by one or more owning parties")] public List? Party { get; init; } - /// - /// Filter by one or more extended statuses - /// + [GraphQLDescription("Filter by one or more extended statuses")] public List? ExtendedStatus { get; init; } - /// - /// Filter by external reference - /// + [GraphQLDescription("Filter by external reference")] public string? ExternalReference { get; init; } - /// - /// Filter by status - /// + [GraphQLDescription("Filter by status")] public List? Status { get; init; } - /// - /// Only return dialogs created after this date - /// + [GraphQLDescription("Only return dialogs created after this date")] public DateTimeOffset? CreatedAfter { get; init; } - /// - /// Only return dialogs created before this date - /// + [GraphQLDescription("Only return dialogs created before this date")] public DateTimeOffset? CreatedBefore { get; init; } - /// - /// Only return dialogs updated after this date - /// + [GraphQLDescription("Only return dialogs updated after this date")] public DateTimeOffset? UpdatedAfter { get; init; } - /// - /// Only return dialogs updated before this date - /// + [GraphQLDescription("Only return dialogs updated before this date")] public DateTimeOffset? UpdatedBefore { get; init; } - /// - /// Only return dialogs with due date after this date - /// + [GraphQLDescription("Only return dialogs with due date after this date")] public DateTimeOffset? DueAfter { get; init; } - /// - /// Only return dialogs with due date before this date - /// + [GraphQLDescription("Only return dialogs with due date before this date")] public DateTimeOffset? DueBefore { get; init; } - /// - /// Search string for free text search. Will attempt to fuzzily match in all free text fields in the aggregate - /// + [GraphQLDescription("Search string for free text search. Will attempt to fuzzily match in all free text fields in the aggregate")] public string? Search { get; init; } - /// - /// Limit free text search to texts with this culture code, e.g. \"nb-NO\". Default: search all culture codes - /// + [GraphQLDescription("Limit free text search to texts with this culture code, e.g. \"nb-NO\". Default: search all culture codes")] public string? SearchCultureCode { get; init; } } - -// -// public enum DialogStatusSearch -// { -// New = 1, -// -// /// -// /// Under arbeid. Generell status som brukes for dialogtjenester der ytterligere bruker-input er -// /// forventet. -// /// -// InProgress = 2, -// -// /// -// /// Venter pÃ¥ tilbakemelding fra tjenesteeier -// /// -// Waiting = 3, -// -// /// -// /// Dialogen er i en tilstand hvor den venter pÃ¥ signering. Typisk siste steg etter at all -// /// utfylling er gjennomført og validert. -// /// -// Signing = 4, -// -// /// -// /// Dialogen ble avbrutt. Dette gjør at dialogen typisk fjernes fra normale GUI-visninger. -// /// -// Cancelled = 5, -// -// /// -// /// Dialigen ble fullført. Dette gjør at dialogen typisk flyttes til et GUI-arkiv eller lignende. -// /// -// Completed = 6 -// } -// -// public sealed class DialogSearch -// { -// public Guid Id { get; set; } -// public Guid Revision { get; set; } -// public string Org { get; set; } = null!; -// public string ServiceResource { get; set; } = null!; -// public string Party { get; set; } = null!; -// public int? Progress { get; set; } -// public string? ExtendedStatus { get; set; } -// public string? ExternalReference { get; set; } -// public DateTimeOffset? VisibleFrom { get; set; } -// public DateTimeOffset? DueAt { get; set; } -// public DateTimeOffset? ExpiresAt { get; set; } -// public DateTimeOffset CreatedAt { get; set; } -// public DateTimeOffset UpdatedAt { get; set; } -// -// public string? DialogToken { get; set; } -// -// public DialogStatusSearch StatusSearch { get; set; } -// -// public List Content { get; set; } = []; -// public List Elements { get; set; } = []; -// public List GuiActions { get; set; } = []; -// public List ApiActions { get; set; } = []; -// public List Activities { get; set; } = []; -// public List SeenSinceLastUpdate { get; set; } = []; -// } -// -// public sealed class SeenLogSearch -// { -// public Guid Id { get; set; } -// public DateTimeOffset CreatedAt { get; set; } -// -// public string EndUserIdHash { get; set; } = null!; -// -// public string? EndUserName { get; set; } -// -// public bool IsCurrentEndUser { get; set; } -// } -// -// public sealed class ActivitySearch -// { -// public Guid Id { get; set; } -// public DateTimeOffset? CreatedAt { get; set; } -// public Uri? ExtendedType { get; set; } -// -// public ActivityTypeSearch TypeSearch { get; set; } -// -// public Guid? RelatedActivityId { get; set; } -// public Guid? DialogElementId { get; set; } -// -// public List? PerformedBy { get; set; } = []; -// public List Description { get; set; } = []; -// } -// -// public enum ActivityTypeSearch -// { -// /// -// /// Refererer en innsending utført av party som er mottatt hos tjenestetilbyder. -// /// -// Submission = 1, -// -// /// -// /// Indikerer en tilbakemelding fra tjenestetilbyder pÃ¥ en innsending. Inneholder -// /// referanse til den aktuelle innsendingen. -// /// -// Feedback = 2, -// -// /// -// /// Informasjon fra tjenestetilbyder, ikke (direkte) relatert til noen innsending. -// /// -// Information = 3, -// -// /// -// /// Brukes for Ã¥ indikere en feilsituasjon, typisk pÃ¥ en innsending. Inneholder en -// /// tjenestespesifikk activityErrorCode. -// /// -// Error = 4, -// -// /// -// /// Indikerer at dialogen er lukket for videre endring. Dette skjer typisk ved fullføring -// /// av dialogen, eller sletting. -// /// -// Closed = 5, -// -// /// -// /// NÃ¥r dialogen blir videresendt (tilgang delegert) av noen med tilgang til andre. -// /// -// Forwarded = 7 -// } -// -// public sealed class ApiActionSearch -// { -// public Guid Id { get; set; } -// public string Action { get; set; } = null!; -// public string? AuthorizationAttribute { get; set; } -// public bool IsAuthorized { get; set; } -// -// public Guid? DialogElementId { get; set; } -// -// public List Endpoints { get; set; } = []; -// } -// -// // ReSharper disable InconsistentNaming -// public enum HttpVerbSearch -// { -// GET = 1, -// POST = 2, -// PUT = 3, -// PATCH = 4, -// DELETE = 5, -// HEAD = 6, -// OPTIONS = 7, -// TRACE = 8, -// CONNECT = 9 -// } -// -// public sealed class ApiActionEndpointSearch -// { -// public Guid Id { get; set; } -// public string? Version { get; set; } -// public Uri Url { get; set; } = null!; -// public HttpVerbSearch HttpMethod { get; set; } -// public Uri? DocumentationUrl { get; set; } -// public Uri? RequestSchema { get; set; } -// public Uri? ResponseSchema { get; set; } -// public bool Deprecated { get; set; } -// public DateTimeOffset? SunsetAt { get; set; } -// } -// -// public sealed class GuiActionSearch -// { -// public Guid Id { get; set; } -// public string Action { get; set; } = null!; -// public Uri Url { get; set; } = null!; -// public string? AuthorizationAttribute { get; set; } -// public bool IsAuthorized { get; set; } -// public bool IsBackChannel { get; set; } -// public bool IsDeleteAction { get; set; } -// -// public GuiActionPrioritySearch PrioritySearch { get; set; } -// -// public List Title { get; set; } = []; -// } -// -// public enum GuiActionPrioritySearch -// { -// Primary = 1, -// Secondary = 2, -// Tertiary = 3 -// } -// -// public sealed class ElementSearch -// { -// public Guid Id { get; set; } -// public Uri? Type { get; set; } -// public string? ExternalReference { get; set; } -// public string? AuthorizationAttribute { get; set; } -// public bool IsAuthorized { get; set; } -// -// public Guid? RelatedDialogElementId { get; set; } -// -// public List DisplayName { get; set; } = []; -// public List Urls { get; set; } = []; -// } -// -// public sealed class ElementUrlSearch -// { -// public Guid Id { get; set; } -// public Uri Url { get; set; } = null!; -// public string? MimeType { get; set; } -// -// public ElementUrlConsumerSearch ConsumerSearchType { get; set; } -// } -// -// public enum ElementUrlConsumerSearch -// { -// Gui = 1, -// Api = 2 -// } -// public enum ContentTypeSearch -// { -// Title = 1, -// SenderName = 2, -// Summary = 3, -// AdditionalInfo = 4 -// } -// -// public sealed class ContentSearch -// { -// public ContentTypeSearch TypeSearch { get; set; } -// public List Value { get; set; } = []; -// } -// -// // public sealed class Localization -// // { -// // public string Value { get; set; } = null!; -// // public string CultureCode { get; set; } = null!; -// // } From 1b4f012fd1fc03051d6f56507d845d84dbb9a0e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Skogstad?= Date: Tue, 16 Apr 2024 10:32:05 +0200 Subject: [PATCH 17/23] --wip-- [skip ci] --- .../EndUser/DialogById/MappingProfile.cs | 2 -- src/Digdir.Domain.Dialogporten.GraphQL/Program.cs | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogById/MappingProfile.cs b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogById/MappingProfile.cs index 5082d7d78..c0c5c8ec3 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogById/MappingProfile.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogById/MappingProfile.cs @@ -12,8 +12,6 @@ public MappingProfile() CreateMap() .ForMember(dest => dest.Status, opt => opt.MapFrom(src => src.Status)); - - CreateMap(); CreateMap() .ForMember(dest => dest.ConsumerType, opt => opt.MapFrom(src => src.ConsumerType)); diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/Program.cs b/src/Digdir.Domain.Dialogporten.GraphQL/Program.cs index a56613a1b..2bace4710 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/Program.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/Program.cs @@ -74,6 +74,7 @@ static void BuildAndRun(string[] args) .ReplaceSingleton( predicate: localDevelopmentSettings.DisableAuth); } + var thisAssembly = Assembly.GetExecutingAssembly(); builder.Services From f8b4001e2a9492afb97388be76ebd628193c3a62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Skogstad?= Date: Tue, 16 Apr 2024 10:37:30 +0200 Subject: [PATCH 18/23] --wip-- [skip ci] --- src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogQueries.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogQueries.cs b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogQueries.cs index c34fb86ce..12d00989d 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogQueries.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogQueries.cs @@ -22,6 +22,7 @@ public async Task GetDialogById( var result = await mediator.Send(request, cancellationToken); var getDialogResult = result.Match( dialog => dialog, + // TODO: Error handling notFound => throw new NotImplementedException("Not found"), deleted => throw new NotImplementedException("Deleted"), forbidden => throw new NotImplementedException("Forbidden")); @@ -44,6 +45,7 @@ public async Task SearchDialogs( var searchResultOneOf = result.Match( paginatedList => paginatedList, + // TODO: Error handling validationError => throw new NotImplementedException("Validation error"), forbidden => throw new NotImplementedException("Forbidden")); From 6cecbc12446585fe638c60acd40b9960d143adf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Skogstad?= Date: Tue, 16 Apr 2024 10:40:45 +0200 Subject: [PATCH 19/23] --wip-- [skip ci] --- src/Digdir.Domain.Dialogporten.GraphQL/Program.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/Program.cs b/src/Digdir.Domain.Dialogporten.GraphQL/Program.cs index 2bace4710..70d874ab7 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/Program.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/Program.cs @@ -85,10 +85,8 @@ static void BuildAndRun(string[] args) .AddApplication(builder.Configuration, builder.Environment) .AddInfrastructure(builder.Configuration, builder.Environment) - // Asp infra .AddAutoMapper(Assembly.GetExecutingAssembly()) .AddApplicationInsightsTelemetry() - // .AddApplicationInsightsTelemetry() .AddScoped() .AddValidatorsFromAssembly(thisAssembly, ServiceLifetime.Transient, includeInternalTypes: true) From 28e4b719031e07cd032fe81919f8d54972cc8575 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Skogstad?= Date: Tue, 16 Apr 2024 12:15:16 +0200 Subject: [PATCH 20/23] --wip-- [skip ci] --- .../Properties/launchSettings.json | 28 +------------------ 1 file changed, 1 insertion(+), 27 deletions(-) diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/Properties/launchSettings.json b/src/Digdir.Domain.Dialogporten.GraphQL/Properties/launchSettings.json index 2233da99c..fa20d39ed 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/Properties/launchSettings.json +++ b/src/Digdir.Domain.Dialogporten.GraphQL/Properties/launchSettings.json @@ -1,24 +1,6 @@ { - "$schema": "http://json.schemastore.org/launchsettings.json", - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:53970", - "sslPort": 44306 - } - }, + "$schema": "https://json.schemastore.org/launchsettings.json", "profiles": { - "http": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": true, - "launchUrl": "graphql", - "applicationUrl": "http://localhost:5181", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, "https": { "commandName": "Project", "dotnetRunMessages": true, @@ -28,14 +10,6 @@ "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } - }, - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "launchUrl": "graphql", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } } } } From f59253a44836811d4c06f95ca529036438ef7abe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Skogstad?= Date: Tue, 16 Apr 2024 13:34:24 +0200 Subject: [PATCH 21/23] --wip-- [skip ci] --- .../EndUser/DialogById/DialogByIdQuery.cs | 13 ------------- .../EndUser/DialogQueries.cs | 2 +- .../EndUser/SearchDialogs/SearchDialogQuery.cs | 13 ------------- 3 files changed, 1 insertion(+), 27 deletions(-) delete mode 100644 src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogById/DialogByIdQuery.cs delete mode 100644 src/Digdir.Domain.Dialogporten.GraphQL/EndUser/SearchDialogs/SearchDialogQuery.cs diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogById/DialogByIdQuery.cs b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogById/DialogByIdQuery.cs deleted file mode 100644 index c6b823886..000000000 --- a/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogById/DialogByIdQuery.cs +++ /dev/null @@ -1,13 +0,0 @@ -using AutoMapper; -using MediatR; - -namespace Digdir.Domain.Dialogporten.GraphQL.EndUser.DialogById; - -public interface IDialogByIdQuery -{ - Task GetDialogById( - [Service] ISender mediator, - [Service] IMapper mapper, - [Argument] Guid dialogId, - CancellationToken cancellationToken); -} diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogQueries.cs b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogQueries.cs index 12d00989d..bb26778c4 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogQueries.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogQueries.cs @@ -10,7 +10,7 @@ namespace Digdir.Domain.Dialogporten.GraphQL.EndUser; [Authorize(Policy = AuthorizationPolicy.EndUser)] -public class DialogQueries : ISearchDialogQuery, IDialogByIdQuery +public class DialogQueries { public async Task GetDialogById( [Service] ISender mediator, diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/SearchDialogs/SearchDialogQuery.cs b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/SearchDialogs/SearchDialogQuery.cs deleted file mode 100644 index c27fcf974..000000000 --- a/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/SearchDialogs/SearchDialogQuery.cs +++ /dev/null @@ -1,13 +0,0 @@ -using AutoMapper; -using MediatR; - -namespace Digdir.Domain.Dialogporten.GraphQL.EndUser.SearchDialogs; - -public interface ISearchDialogQuery -{ - Task SearchDialogs( - [Service] ISender mediator, - [Service] IMapper mapper, - SearchDialogInput input, - CancellationToken cancellationToken); -} From 940ae142ce012ad61eac8035a3b54effab55e1a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Skogstad?= Date: Tue, 16 Apr 2024 15:10:24 +0200 Subject: [PATCH 22/23] rename created at --- .../EndUser/Common/ObjectTypes.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/Common/ObjectTypes.cs b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/Common/ObjectTypes.cs index ec6718f8c..f97895552 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/Common/ObjectTypes.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/Common/ObjectTypes.cs @@ -23,7 +23,7 @@ public sealed class Content public sealed class SeenLog { public Guid Id { get; set; } - public DateTimeOffset CreatedAt { get; set; } + public DateTimeOffset SeenAt { get; set; } public string EndUserIdHash { get; set; } = null!; From 469ca18a7b2655e4a3e6efcce6d500f5a9b08f48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Skogstad?= Date: Tue, 16 Apr 2024 15:14:00 +0200 Subject: [PATCH 23/23] internal dbcontext --- src/Digdir.Domain.Dialogporten.GraphQL/Program.cs | 6 ------ .../Digdir.Domain.Dialogporten.Infrastructure.csproj | 3 ++- .../Persistence/DialogDbContext.cs | 2 +- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/Program.cs b/src/Digdir.Domain.Dialogporten.GraphQL/Program.cs index 70d874ab7..abe7f33e5 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/Program.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/Program.cs @@ -59,12 +59,6 @@ static void BuildAndRun(string[] args) .Bind(builder.Configuration.GetSection(GraphQlSettings.SectionName)) .ValidateFluently() .ValidateOnStart(); - /* TODOS: - * - Gjør DialogDbContext til internal. Kan mÃ¥tte gjøre dette prosjektet til et "friend assembly" av infrastructure (internalsVisibleTo) - * - * - * - */ if (builder.Environment.IsDevelopment()) { diff --git a/src/Digdir.Domain.Dialogporten.Infrastructure/Digdir.Domain.Dialogporten.Infrastructure.csproj b/src/Digdir.Domain.Dialogporten.Infrastructure/Digdir.Domain.Dialogporten.Infrastructure.csproj index 1036b5a8e..a8b403935 100644 --- a/src/Digdir.Domain.Dialogporten.Infrastructure/Digdir.Domain.Dialogporten.Infrastructure.csproj +++ b/src/Digdir.Domain.Dialogporten.Infrastructure/Digdir.Domain.Dialogporten.Infrastructure.csproj @@ -35,6 +35,7 @@ + - \ No newline at end of file + diff --git a/src/Digdir.Domain.Dialogporten.Infrastructure/Persistence/DialogDbContext.cs b/src/Digdir.Domain.Dialogporten.Infrastructure/Persistence/DialogDbContext.cs index 66ba527dc..73129fd99 100644 --- a/src/Digdir.Domain.Dialogporten.Infrastructure/Persistence/DialogDbContext.cs +++ b/src/Digdir.Domain.Dialogporten.Infrastructure/Persistence/DialogDbContext.cs @@ -16,7 +16,7 @@ namespace Digdir.Domain.Dialogporten.Infrastructure.Persistence; -public sealed class DialogDbContext : DbContext, IDialogDbContext +internal sealed class DialogDbContext : DbContext, IDialogDbContext { public DialogDbContext(DbContextOptions options) : base(options) { }