├── version.txt ├── docs ├── schema │ └── V1 │ │ ├── src │ │ └── .gitkeep │ │ ├── .npmignore │ │ ├── .gitignore │ │ ├── package.json │ │ └── gql-to-js.sh └── deploy-process.png ├── local-otel-configuration ├── dashboards │ └── dashboards.yml ├── prometheus.yml ├── grafana-dashboards.yml └── grafana-datasources.yml ├── .release-please-manifest.json ├── tests ├── Digdir.Domain.Dialogporten.Application.Integration.Tests │ ├── Usings.cs │ ├── Common │ │ ├── ApplicationClassFixture.cs │ │ ├── ApplicationCollectionFixture.cs │ │ ├── IntegrationTestCloudBus.cs │ │ ├── TestDomainEventConsumer.cs │ │ ├── AddingEndUserTransmissionSentLabelTestData.cs │ │ └── TestClock.cs │ └── Features │ │ └── V1 │ │ ├── DialogCqrsCollectionFixture.cs │ │ └── Common │ │ └── UpdateDialogServiceOwnerContextCommandExtensions.cs ├── k6 │ ├── .gitignore │ ├── tests │ │ ├── performancetest_data │ │ │ ├── endusers-staging.csv │ │ │ ├── serviceowners-staging.csv │ │ │ └── serviceowners-yt01.csv │ │ ├── enduser-tests.js │ │ ├── serviceowner-tests.js │ │ ├── all-tests.js │ │ ├── enduser │ │ │ ├── parties.js │ │ │ └── all-tests.js │ │ ├── graphql │ │ │ └── performance │ │ │ │ ├── graphqlGetParties.js │ │ │ │ ├── graphqlGetAllDialogsForEnduser.js │ │ │ │ ├── graphqlGetAllDialogsForEnduserFTS.js │ │ │ │ ├── graphqlGetAllDialogsForEnduserCount.js │ │ │ │ ├── graphqlGetAllDialogsForParties.js │ │ │ │ ├── graphqlGetAllDialogsForPartiesFTS.js │ │ │ │ ├── graphqlGetAllDialogsForRandomParty.js │ │ │ │ ├── graphqlGetAllDialogsForPartiesCount.js │ │ │ │ ├── graphqlGetAllDialogsForRandomPartyCount.js │ │ │ │ └── graphqlSearchWithThresholds.js │ │ └── serviceowner │ │ │ ├── dialogCreateInvalidProcess.js │ │ │ └── performance │ │ │ └── createDialogWithThresholds.js │ ├── suites │ │ ├── sentinel.js │ │ ├── createremove-perf-10-vus-1-min.js │ │ └── all-single-pass.js │ └── common │ │ ├── k6-utils.js │ │ ├── summary.js │ │ └── console.js ├── .editorconfig ├── Digdir.Domain.Dialogporten.Application.Unit.Tests │ ├── Common │ │ └── IOptionsMock.cs │ └── Features │ │ └── V1 │ │ └── Common │ │ └── Utils │ │ ├── ApplicationEventHandlerUtilsTests.Developer_Should_Use_Caution_When_Modifying_Endpoints.verified.txt │ │ └── ApplicationEventHandlerUtilsTests.Developer_Should_Use_Caution_When_Modifying_Events.verified.txt ├── Digdir.Domain.Dialogporten.WebApi.Unit.Tests │ └── Utils.cs ├── Digdir.Domain.Dialogporten.GraphQl.Integration.Tests │ └── Utils.cs └── Digdir.Domain.Dialogporten.Infrastructure.Unit.Tests │ └── Digdir.Domain.Dialogporten.Infrastructure.Unit.Tests.csproj ├── global.json ├── src ├── Digdir.Domain.Dialogporten.Infrastructure │ ├── Persistence │ │ ├── Migrations │ │ │ └── .editorconfig │ │ ├── Constants.cs │ │ ├── ValueConverters │ │ │ └── DateTimeOffsetConverter.cs │ │ ├── Configurations │ │ │ ├── Actors │ │ │ │ └── ActorNameConfiguration.cs │ │ │ ├── DialogServiceOwnerContexts │ │ │ │ └── DialogServiceOwnerContextConfiguration.cs │ │ │ ├── SubjectResources │ │ │ │ └── SubjectResourceConfiguration.cs │ │ │ ├── DialogEndUserContexts │ │ │ │ ├── DialogEndUserContextSystemLabelConfiguration.cs │ │ │ │ └── DialogEndUserContextConfiguration.cs │ │ │ ├── ResourcePolicyInformation │ │ │ │ └── ResourcePolicyInformationConfiguration.cs │ │ │ └── Dialogs │ │ │ │ ├── Activities │ │ │ │ └── DialogActivityConfiguration.cs │ │ │ │ ├── Transmissions │ │ │ │ └── DialogTransmissionConfiguration.cs │ │ │ │ └── DialogSearchTagConfiguration.cs │ │ └── Sql │ │ │ └── Dialog │ │ │ └── Search │ │ │ ├── Function.UpsertDialogSearchOne.sql │ │ │ ├── View.DialogSearchRebuildProgress.sql │ │ │ ├── Function.UpsertDialogSearchOne.V2.sql │ │ │ ├── Function.SeedDialogSearchQueueFull.sql │ │ │ ├── View.VDialogDocument.sql │ │ │ ├── View.VDialogDocument.V2.sql │ │ │ └── Function.ClaimDialogsStandard.sql │ ├── Common │ │ ├── Exceptions │ │ │ ├── OptimisticConcurrencyTimeoutException.cs │ │ │ └── UpstreamServiceException.cs │ │ └── PollyPolicy.cs │ ├── InfrastructureAssemblyMarker.cs │ ├── GraphQL │ │ ├── GraphQlSubscriptionConstants.cs │ │ ├── DialogEventPayload.cs │ │ └── DummyRequestExecutorBuilder.cs │ └── IEnumerableExtensions.cs ├── Digdir.Tool.Dialogporten.LargeDataSetGenerator │ ├── .editorconfig │ ├── .gitignore │ ├── Parties.cs │ ├── Properties │ │ └── launchSettings.json │ ├── CopyTaskDto.cs │ ├── PersonNames.cs │ ├── EntityGenerators │ │ ├── SeenLog.cs │ │ └── EndUserContext.cs │ ├── service_resources │ └── Words.cs ├── Digdir.Library.Dialogporten.WebApiClient │ ├── Directory.Build.props │ ├── AssemblyMarker.cs │ ├── Common │ │ ├── Exceptions │ │ │ └── IncompleteDialogportenClientInitializationException.cs │ │ └── IClock.cs │ ├── .refitter │ ├── Services │ │ └── DefaultValidationResult.cs │ └── Infrastructure │ │ └── IInternalDialogportenApi.cs ├── Digdir.Domain.Dialogporten.Application │ ├── Common │ │ ├── ReturnTypes │ │ │ ├── ConcurrencyError.cs │ │ │ ├── ValidationError.cs │ │ │ ├── Conflict.cs │ │ │ ├── BadRequest.cs │ │ │ ├── DomainError.cs │ │ │ ├── EntityNotVisible.cs │ │ │ └── Forbidden.cs │ │ ├── Pagination │ │ │ ├── Order │ │ │ │ ├── OrderDirection.cs │ │ │ │ └── Order.cs │ │ │ ├── OrderOption │ │ │ │ ├── IOrderDefinition.cs │ │ │ │ └── OrderSelector.cs │ │ │ ├── PaginationConstants.cs │ │ │ └── Continuation │ │ │ │ └── ContinuationToken.cs │ │ ├── ResourceRegistry │ │ │ └── Constants.cs │ │ ├── IIgnoreOnAssemblyScan.cs │ │ ├── Extensions │ │ │ ├── TypeExtensions.cs │ │ │ ├── QueryableExtensions.cs │ │ │ ├── Enumerables │ │ │ │ ├── General.cs │ │ │ │ └── AsyncEnumerableExtensions.cs │ │ │ └── OptionExtensions │ │ │ │ └── OptionsBuilderFluentValidationExtensions.cs │ │ ├── Behaviours │ │ │ └── FeatureMetric │ │ │ │ └── IFeatureMetricDeliveryContext.cs │ │ ├── Context │ │ │ ├── ApplicationContext.cs │ │ │ └── IApplicationContext.cs │ │ ├── IClock.cs │ │ └── ITransactionTime.cs │ ├── Features │ │ └── V1 │ │ │ ├── Common │ │ │ ├── DeletedFilter.cs │ │ │ ├── Authorization │ │ │ │ └── Constants.cs │ │ │ ├── Content │ │ │ │ ├── PropertyInfoWithNullability.cs │ │ │ │ ├── ContentValueDto.cs │ │ │ │ └── MappingProfile.cs │ │ │ ├── Actors │ │ │ │ └── ActorValidationErrorMessages.cs │ │ │ ├── Extensions │ │ │ │ └── TransmissionEntityExtensions.cs │ │ │ ├── Localizations │ │ │ │ └── LocalizationDto.cs │ │ │ └── ValidationErrorStrings.cs │ │ │ ├── ServiceOwner │ │ │ ├── Dialogs │ │ │ │ ├── Queries │ │ │ │ │ ├── NotificationCondition │ │ │ │ │ │ └── NotificationConditionDto.cs │ │ │ │ │ ├── GetSeenLog │ │ │ │ │ │ ├── MappingProfile.cs │ │ │ │ │ │ └── SeenLogDto.cs │ │ │ │ │ ├── SearchSeenLogs │ │ │ │ │ │ ├── MappingProfile.cs │ │ │ │ │ │ └── SeenLogDto.cs │ │ │ │ │ ├── GetActivity │ │ │ │ │ │ └── MappingProfile.cs │ │ │ │ │ ├── SearchActivities │ │ │ │ │ │ ├── ActivityDto.cs │ │ │ │ │ │ └── MappingProfile.cs │ │ │ │ │ └── Get │ │ │ │ │ │ └── GetDialogDataLoader.cs │ │ │ │ └── Commands │ │ │ │ │ ├── Purge │ │ │ │ │ └── PurgeDialogCommandValidator.cs │ │ │ │ │ ├── Create │ │ │ │ │ └── Validators │ │ │ │ │ │ ├── CreateDialogSearchTagDtoValidator.cs │ │ │ │ │ │ ├── CreateDialogServiceOwnerLabelDtoValidator.cs │ │ │ │ │ │ ├── CreateDialogDialogAttachmentUrlDtoValidator.cs │ │ │ │ │ │ └── CreateDialogTransmissionAttachmentUrlDtoValidator.cs │ │ │ │ │ ├── Update │ │ │ │ │ ├── Validators │ │ │ │ │ │ ├── UpdateDialogSearchTagDtoValidator.cs │ │ │ │ │ │ ├── UpdateDialogDialogAttachmentUrlDtoValidator.cs │ │ │ │ │ │ └── UpdateDialogTransmissionAttachmentUrlDtoValidator.cs │ │ │ │ │ └── UpdateDialogDataLoader.cs │ │ │ │ │ └── UpdateFormSavedActivityTime │ │ │ │ │ └── UpdateFormSavedActivityTimeCommandValidator.cs │ │ │ ├── ServiceOwnerContext │ │ │ │ ├── Commands │ │ │ │ │ └── Update │ │ │ │ │ │ ├── MappingProfile.cs │ │ │ │ │ │ └── UpdateServiceOwnerContextDto.cs │ │ │ │ └── Queries │ │ │ │ │ └── GetServiceOwnerLabels │ │ │ │ │ ├── MappingProfile.cs │ │ │ │ │ └── ServiceOwnerLabelDto.cs │ │ │ └── Common │ │ │ │ └── Actors │ │ │ │ └── ActorDto.cs │ │ │ ├── ResourceRegistry │ │ │ └── Commands │ │ │ │ ├── SyncPolicy │ │ │ │ └── SyncPolicyCommandValidator.cs │ │ │ │ └── SyncSubjectMap │ │ │ │ └── SyncSubjectMapCommandValidator.cs │ │ │ ├── WellKnown │ │ │ ├── OauthAuthorizationServer │ │ │ │ └── Queries │ │ │ │ │ └── Get │ │ │ │ │ └── GetOauthAuthorizationServerDto.cs │ │ │ └── Jwks │ │ │ │ └── Queries │ │ │ │ └── Get │ │ │ │ └── GetJwksDto.cs │ │ │ ├── EndUser │ │ │ ├── EndUserContext │ │ │ │ └── Queries │ │ │ │ │ └── SearchLabelAssignmentLog │ │ │ │ │ ├── MappingProfile.cs │ │ │ │ │ └── LabelAssignmentLogDto.cs │ │ │ ├── Dialogs │ │ │ │ └── Queries │ │ │ │ │ ├── GetSeenLog │ │ │ │ │ ├── MappingProfile.cs │ │ │ │ │ └── SeenLogDto.cs │ │ │ │ │ ├── SearchSeenLogs │ │ │ │ │ ├── MappingProfile.cs │ │ │ │ │ └── SeenLogDto.cs │ │ │ │ │ ├── GetActivity │ │ │ │ │ ├── MappingProfile.cs │ │ │ │ │ └── ActivityDto.cs │ │ │ │ │ └── SearchActivities │ │ │ │ │ ├── MappingProfile.cs │ │ │ │ │ └── ActivityDto.cs │ │ │ └── Common │ │ │ │ └── Actors │ │ │ │ └── ActorDto.cs │ │ │ └── AccessManagement │ │ │ └── Queries │ │ │ └── GetParties │ │ │ └── MappingProfile.cs │ ├── Externals │ │ ├── Presentation │ │ │ └── IUser.cs │ │ ├── IPartyNameRegistry.cs │ │ ├── IServiceOwnerNameRegistry.cs │ │ ├── AltinnAuthorization │ │ │ └── ActionResource.cs │ │ └── ICloudEventBus.cs │ └── ApplicationAssemblyMarker.cs ├── Digdir.Domain.Dialogporten.Domain │ ├── Dialogs │ │ ├── Events │ │ │ ├── IProcessEvent.cs │ │ │ ├── DialogSeenDomainEvent.cs │ │ │ ├── DialogCreatedDomainEvent.cs │ │ │ ├── DialogDeletedDomainEvent.cs │ │ │ ├── DialogUpdatedDomainEvent.cs │ │ │ ├── DialogRestoredDomainEvent.cs │ │ │ ├── DialogTransmissionCreatedDomainEvent.cs │ │ │ └── Activities │ │ │ │ └── DialogActivityCreatedDomainEvent.cs │ │ └── Entities │ │ │ ├── Actions │ │ │ ├── DialogGuiActionPriority.cs │ │ │ └── DialogApiAction.cs │ │ │ └── DialogSearchTag.cs │ ├── Common │ │ ├── Exceptions │ │ │ └── ForbiddenException.cs │ │ ├── DomainEvents │ │ │ ├── DomainEventExtensions.cs │ │ │ └── DomainEvent.cs │ │ ├── EventPublisher │ │ │ ├── IEventPublisher.cs │ │ │ └── IDomainEvent.cs │ │ ├── Constants.cs │ │ └── DomainFailure.cs │ ├── DomainAssemblyMarker.cs │ ├── Outboxes │ │ └── OutboxMessageConsumer.cs │ ├── Digdir.Domain.Dialogporten.Domain.csproj │ ├── DomainExtensions.cs │ ├── SubjectResources │ │ └── SubjectResource.cs │ ├── Actors │ │ ├── ActorType.cs │ │ ├── Actor.cs │ │ └── ActorName.cs │ ├── Parties │ │ └── Abstractions │ │ │ └── IPartyIdentifier.cs │ ├── ResourcePolicyInformation │ │ └── ResourcePolicyInformation.cs │ ├── Attachments │ │ ├── AttachmentUrlConsumerType.cs │ │ └── AttachmentUrl.cs │ ├── Localizations │ │ └── LocalizationSet.cs │ ├── Http │ │ └── HttpVerb.cs │ ├── DialogEndUserContexts │ │ └── Entities │ │ │ └── DialogEndUserContextSystemLabel.cs │ └── DialogServiceOwnerContexts │ │ └── Entities │ │ └── DialogServiceOwnerLabel.cs ├── Digdir.Domain.Dialogporten.WebApi │ ├── WebApiAssemblyMarker.cs │ ├── Common │ │ ├── Json │ │ │ └── ShortNameGenerator.cs │ │ ├── Extensions │ │ │ └── StringExtensions.cs │ │ ├── Authentication │ │ │ └── AuthenticationOptions.cs │ │ ├── Authorization │ │ │ ├── AuthorizationPolicy.cs │ │ │ └── AllowAnonymousHandler.cs │ │ ├── FeatureMetric │ │ │ └── FeatureMetricOptions.cs │ │ ├── WebApiSettings.cs │ │ └── ApplicationUser.cs │ └── Endpoints │ │ └── V1 │ │ ├── EndUser │ │ ├── EndUserGroup.cs │ │ └── AccessManagement │ │ │ └── Queries │ │ │ └── GetParties │ │ │ └── GetPartiesEndpointSummary.cs │ │ ├── ServiceOwner │ │ └── ServiceOwnerGroup.cs │ │ ├── MetadataGroup.cs │ │ ├── WellKnown │ │ └── Jwks │ │ │ └── Get │ │ │ └── GetJwksEndpointSummary.cs │ │ └── Common │ │ └── Headers │ │ └── HttpResponseHeaderExamples.cs ├── Digdir.Domain.Dialogporten.GraphQL │ ├── GraphQLAssemblyMarker.cs │ ├── EndUser │ │ ├── Queries.cs │ │ ├── Parties │ │ │ └── MappingProfile.cs │ │ └── MutationTypes │ │ │ └── MappingProfile.cs │ ├── Common │ │ ├── Constants.cs │ │ ├── Authentication │ │ │ └── AuthenticationOptions.cs │ │ ├── Authorization │ │ │ ├── AuthorizationPolicy.cs │ │ │ └── AllowAnonymousHandler.cs │ │ └── ApplicationUser.cs │ ├── Properties │ │ └── launchSettings.json │ └── appsettings.json ├── Digdir.Domain.Dialogporten.Janitor │ ├── JanitorAssemblyMarker.cs │ ├── appsettings.json │ ├── CostManagementAggregation │ │ ├── cost-coefficients.json │ │ └── MetricsAggregationOptions.cs │ └── ConsoleUser.cs ├── Digdir.Domain.Dialogporten.Service │ ├── ServiceAssemblyMarker.cs │ ├── Properties │ │ └── launchSettings.json │ ├── appsettings.json │ ├── ServiceUser.cs │ └── Digdir.Domain.Dialogporten.Service.csproj ├── Digdir.Library.Entity.Abstractions │ ├── Features │ │ ├── Immutable │ │ │ └── IImmutableEntity.cs │ │ ├── Aggregate │ │ │ └── AggregateParentAttribute.cs │ │ ├── Identifiable │ │ │ └── IIdentifiableEntity.cs │ │ ├── Versionable │ │ │ ├── IVersionableEntity.cs │ │ │ └── VersionableExtensions.cs │ │ ├── Creatable │ │ │ ├── ICreatableEntity.cs │ │ │ └── CreatableExtensions.cs │ │ └── Updatable │ │ │ └── IUpdateableEntity.cs │ ├── Digdir.Library.Entity.Abstractions.csproj │ ├── LibraryEntityAbstractionsAssemblyMarker.cs │ ├── IEntity.cs │ └── IJoinEntity.cs ├── Digdir.Tool.Dialogporten.Ed25519KeyPairGenerator │ └── Digdir.Tool.Dialogporten.Ed25519KeyPairGenerator.csproj ├── Digdir.Library.Dialogporten.WebApiClient.WebApiSample │ ├── WebApiSample.http │ ├── Digdir.Library.Dialogporten.WebApiClient.WebApiSample.csproj │ ├── appsettings.json │ └── Properties │ │ └── launchSettings.json ├── Digdir.Library.Utils.AspNet │ ├── WebHostCommonSettings.cs │ ├── ServiceCollectionExtensions.cs │ └── FusionCacheFilter.cs ├── Digdir.Library.Entity.EntityFrameworkCore │ ├── LibraryEntityFrameworkCoreAssemblyMarker.cs │ └── Digdir.Library.Entity.EntityFrameworkCore.csproj └── Digdir.Tool.Dialogporten.GenerateFakeData │ └── Digdir.Tool.Dialogporten.GenerateFakeData.csproj ├── sonar-project.properties ├── .github ├── CODEOWNERS ├── tools │ └── README.md ├── pr-title-checker-config.json ├── pull_request_template.md ├── ISSUE_TEMPLATE │ └── bug.md └── workflows │ ├── workflow-get-current-version.yml │ └── ci-cd-pull-request-title.yml ├── .dockerignore ├── .azure ├── modules │ ├── keyvault │ │ └── upsertSecret.bicep │ ├── managedIdentity │ │ └── main.bicep │ └── privateDnsZoneGroup │ │ └── main.bicep ├── applications │ ├── web-api-migration-job │ │ ├── prod.bicepparam │ │ ├── test.bicepparam │ │ ├── yt01.bicepparam │ │ └── staging.bicepparam │ ├── reindex-dialogsearch-job │ │ ├── test.bicepparam │ │ ├── prod.bicepparam │ │ ├── staging.bicepparam │ │ └── yt01.bicepparam │ ├── sync-resource-policy-information-job │ │ ├── prod.bicepparam │ │ ├── test.bicepparam │ │ ├── yt01.bicepparam │ │ └── staging.bicepparam │ ├── sync-subject-resource-mappings-job │ │ ├── prod.bicepparam │ │ ├── test.bicepparam │ │ ├── yt01.bicepparam │ │ └── staging.bicepparam │ ├── graphql │ │ ├── test.bicepparam │ │ ├── staging.bicepparam │ │ └── yt01.bicepparam │ ├── web-api-eu │ │ ├── test.bicepparam │ │ ├── staging.bicepparam │ │ └── yt01.bicepparam │ ├── web-api-so │ │ ├── test.bicepparam │ │ ├── staging.bicepparam │ │ └── yt01.bicepparam │ ├── aggregate-cost-metrics-job │ │ ├── prod.bicepparam │ │ └── staging.bicepparam │ └── service │ │ ├── test.bicepparam │ │ ├── staging.bicepparam │ │ ├── yt01.bicepparam │ │ └── prod.bicepparam └── bicepconfig.json ├── .env ├── docker-compose-cdc.yml ├── nginx-webapi.conf ├── nginx-graphql.conf ├── docker-compose-jobs.yml ├── Directory.Build.props └── release-please-config.json /version.txt: -------------------------------------------------------------------------------- 1 | 1.97.0 2 | -------------------------------------------------------------------------------- /docs/schema/V1/src/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /local-otel-configuration/dashboards/dashboards.yml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.release-please-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | ".": "1.97.0" 3 | } -------------------------------------------------------------------------------- /docs/schema/V1/.npmignore: -------------------------------------------------------------------------------- 1 | swagger.received.json 2 | schema.received.graphql 3 | .gitkeep 4 | -------------------------------------------------------------------------------- /tests/Digdir.Domain.Dialogporten.Application.Integration.Tests/Usings.cs: -------------------------------------------------------------------------------- 1 | global using Xunit; -------------------------------------------------------------------------------- /docs/deploy-process.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altinn/dialogporten/main/docs/deploy-process.png -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "9.0.308", 4 | "rollForward": "disable" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Infrastructure/Persistence/Migrations/.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | generated_code = true -------------------------------------------------------------------------------- /tests/k6/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore summary files generated by running some suites 2 | junit.xml 3 | summary.json 4 | -------------------------------------------------------------------------------- /src/Digdir.Tool.Dialogporten.LargeDataSetGenerator/.editorconfig: -------------------------------------------------------------------------------- 1 | [*.cs] 2 | dotnet_diagnostic.CA1305.severity = none -------------------------------------------------------------------------------- /src/Digdir.Tool.Dialogporten.LargeDataSetGenerator/.gitignore: -------------------------------------------------------------------------------- 1 | person_names 2 | parties 3 | wordlist_en 4 | wordlist_no 5 | -------------------------------------------------------------------------------- /tests/k6/tests/performancetest_data/endusers-staging.csv: -------------------------------------------------------------------------------- 1 | ssn,resource,scopes 2 | 08895699684,super-simple-service,digdir:dialogporten 3 | -------------------------------------------------------------------------------- /tests/k6/suites/sentinel.js: -------------------------------------------------------------------------------- 1 | import { default as sentinelCheck } from '../common/sentinel.js'; 2 | 3 | export default function () { 4 | sentinelCheck(); 5 | }; 6 | -------------------------------------------------------------------------------- /tests/k6/tests/enduser-tests.js: -------------------------------------------------------------------------------- 1 | import { default as endUserTests } from './enduser/all-tests.js'; 2 | 3 | export function runAllTests() { 4 | endUserTests(); 5 | }; -------------------------------------------------------------------------------- /src/Digdir.Library.Dialogporten.WebApiClient/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /tests/k6/tests/serviceowner-tests.js: -------------------------------------------------------------------------------- 1 | import { default as serviceOwnerTests } from './serviceowner/all-tests.js'; 2 | 3 | export function runAllTests() { 4 | serviceOwnerTests(); 5 | }; -------------------------------------------------------------------------------- /tests/k6/common/k6-utils.js: -------------------------------------------------------------------------------- 1 | export { uuidv4, randomItem, randomIntBetween } from 'https://jslib.k6.io/k6-utils/1.4.0/index.js'; 2 | export { URL } from 'https://jslib.k6.io/url/1.0.0/index.js'; 3 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Common/ReturnTypes/ConcurrencyError.cs: -------------------------------------------------------------------------------- 1 | namespace Digdir.Domain.Dialogporten.Application.Common.ReturnTypes; 2 | 3 | public sealed record ConcurrencyError; 4 | -------------------------------------------------------------------------------- /tests/k6/tests/performancetest_data/serviceowners-staging.csv: -------------------------------------------------------------------------------- 1 | org,orgno,scopes,resource 2 | digdir,991825827,digdir:dialogporten.serviceprovider digdir:dialogporten.serviceprovider.search,super-simple-service 3 | -------------------------------------------------------------------------------- /tests/k6/tests/performancetest_data/serviceowners-yt01.csv: -------------------------------------------------------------------------------- 1 | org,orgno,scopes,resource 2 | ttd,713431400,digdir:dialogporten.serviceprovider digdir:dialogporten.serviceprovider.search,ttd-dialogporten-performance-test-01 3 | -------------------------------------------------------------------------------- /sonar-project.properties: -------------------------------------------------------------------------------- 1 | # Disable code duplication detection for C# and k6 files 2 | sonar.cpd.exclusions=**/*.cs,**/k6/**/* 3 | 4 | # Disable all rules for auto-generated migration files 5 | sonar.exclusions=**/Migrations/**/* 6 | -------------------------------------------------------------------------------- /tests/.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | # CA1707: Identifiers should not contain underscores 3 | dotnet_diagnostic.CA1707.severity = none 4 | 5 | # IDE0010: Add missing cases to switch statements 6 | dotnet_diagnostic.IDE0010.severity = none -------------------------------------------------------------------------------- /local-otel-configuration/prometheus.yml: -------------------------------------------------------------------------------- 1 | global: 2 | scrape_interval: 15s 3 | evaluation_interval: 15s 4 | 5 | scrape_configs: 6 | - job_name: 'otel-collector' 7 | static_configs: 8 | - targets: ['otel-collector:8889'] -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Common/Pagination/Order/OrderDirection.cs: -------------------------------------------------------------------------------- 1 | namespace Digdir.Domain.Dialogporten.Application.Common.Pagination.Order; 2 | 3 | public enum OrderDirection 4 | { 5 | Asc = 1, 6 | Desc = 2 7 | } 8 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Features/V1/Common/DeletedFilter.cs: -------------------------------------------------------------------------------- 1 | namespace Digdir.Domain.Dialogporten.Application.Features.V1.Common; 2 | 3 | public enum DeletedFilter 4 | { 5 | Exclude = 1, 6 | Include = 2, 7 | Only = 3 8 | } 9 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Domain/Dialogs/Events/IProcessEvent.cs: -------------------------------------------------------------------------------- 1 | namespace Digdir.Domain.Dialogporten.Domain.Dialogs.Events; 2 | 3 | public interface IProcessEvent 4 | { 5 | string? Process { get; } 6 | string? PrecedingProcess { get; } 7 | } 8 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Infrastructure/Common/Exceptions/OptimisticConcurrencyTimeoutException.cs: -------------------------------------------------------------------------------- 1 | namespace Digdir.Domain.Dialogporten.Infrastructure.Common.Exceptions; 2 | 3 | internal sealed class OptimisticConcurrencyTimeoutException : Exception; 4 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Infrastructure/Common/PollyPolicy.cs: -------------------------------------------------------------------------------- 1 | namespace Digdir.Domain.Dialogporten.Infrastructure.Common; 2 | 3 | internal static class PollyPolicy 4 | { 5 | public const string DefaultHttpRetryPolicy = "DefaultHttpRetryPolicy"; 6 | } 7 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Externals/Presentation/IUser.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Claims; 2 | 3 | namespace Digdir.Domain.Dialogporten.Application.Externals.Presentation; 4 | 5 | public interface IUser 6 | { 7 | ClaimsPrincipal GetPrincipal(); 8 | } 9 | -------------------------------------------------------------------------------- /src/Digdir.Tool.Dialogporten.LargeDataSetGenerator/Parties.cs: -------------------------------------------------------------------------------- 1 | namespace Digdir.Tool.Dialogporten.LargeDataSetGenerator; 2 | 3 | internal static class Parties 4 | { 5 | internal static readonly string[] List = File.ReadAllLines("./parties").Distinct().ToArray(); 6 | } 7 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Common/ResourceRegistry/Constants.cs: -------------------------------------------------------------------------------- 1 | namespace Digdir.Domain.Dialogporten.Application.Common.ResourceRegistry; 2 | 3 | public sealed class Constants 4 | { 5 | public const string CorrespondenceService = "correspondenceservice"; 6 | } 7 | -------------------------------------------------------------------------------- /src/Digdir.Library.Dialogporten.WebApiClient/AssemblyMarker.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | namespace Altinn.ApiClients.Dialogporten; 4 | 5 | internal sealed class AssemblyMarker 6 | { 7 | public static readonly Assembly Assembly = typeof(AssemblyMarker).Assembly; 8 | } 9 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Features/V1/Common/Authorization/Constants.cs: -------------------------------------------------------------------------------- 1 | namespace Digdir.Domain.Dialogporten.Application.Features.V1.Common.Authorization; 2 | 3 | public static class Constants 4 | { 5 | public const string DialogTokenIssuerVersion = "/api/v1"; 6 | } 7 | -------------------------------------------------------------------------------- /local-otel-configuration/grafana-dashboards.yml: -------------------------------------------------------------------------------- 1 | apiVersion: 1 2 | 3 | providers: 4 | - name: 'Default' 5 | orgId: 1 6 | folder: '' 7 | type: file 8 | disableDeletion: false 9 | editable: true 10 | options: 11 | path: /etc/grafana/provisioning/dashboards -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Domain/Common/Exceptions/ForbiddenException.cs: -------------------------------------------------------------------------------- 1 | namespace Digdir.Domain.Dialogporten.Domain.Common.Exceptions; 2 | 3 | public sealed class ForbiddenException : ApplicationException 4 | { 5 | public ForbiddenException(string message) : base(message) { } 6 | } 7 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Domain/DomainAssemblyMarker.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | namespace Digdir.Domain.Dialogporten.Domain; 4 | 5 | public sealed class DomainAssemblyMarker 6 | { 7 | public static readonly Assembly Assembly = typeof(DomainAssemblyMarker).Assembly; 8 | } 9 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.WebApi/WebApiAssemblyMarker.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | namespace Digdir.Domain.Dialogporten.WebApi; 4 | 5 | public sealed class WebApiAssemblyMarker 6 | { 7 | public static readonly Assembly Assembly = typeof(WebApiAssemblyMarker).Assembly; 8 | } 9 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.GraphQL/GraphQLAssemblyMarker.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | namespace Digdir.Domain.Dialogporten.GraphQL; 4 | 5 | public sealed class GraphQLAssemblyMarker 6 | { 7 | public static readonly Assembly Assembly = typeof(GraphQLAssemblyMarker).Assembly; 8 | } 9 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Janitor/JanitorAssemblyMarker.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | namespace Digdir.Domain.Dialogporten.Janitor; 4 | 5 | public sealed class JanitorAssemblyMarker 6 | { 7 | public static readonly Assembly Assembly = typeof(JanitorAssemblyMarker).Assembly; 8 | } 9 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Service/ServiceAssemblyMarker.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | namespace Digdir.Domain.Dialogporten.Service; 4 | 5 | public sealed class ServiceAssemblyMarker 6 | { 7 | public static readonly Assembly Assembly = typeof(ServiceAssemblyMarker).Assembly; 8 | } 9 | -------------------------------------------------------------------------------- /src/Digdir.Library.Dialogporten.WebApiClient/Common/Exceptions/IncompleteDialogportenClientInitializationException.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.ApiClients.Dialogporten.Common.Exceptions; 2 | 3 | public sealed class IncompleteDialogportenClientInitializationException(string message) : Exception(message); 4 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @altinn/team-dialogporten-backend 2 | .azure/** @altinn/team-dialogporten-backend @altinn/team-dialogporten-infra 3 | .github/** @altinn/team-dialogporten-backend @altinn/team-dialogporten-infra 4 | .github/CODEOWNERS @altinn/team-dialogporten-backend -------------------------------------------------------------------------------- /src/Digdir.Tool.Dialogporten.LargeDataSetGenerator/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "LargeDataSetGenerator": { 4 | "commandName": "Project", 5 | "environmentVariables": { 6 | "ASPNETCORE_ENVIRONMENT": "Development" 7 | } 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Common/IIgnoreOnAssemblyScan.cs: -------------------------------------------------------------------------------- 1 | namespace Digdir.Domain.Dialogporten.Application.Common; 2 | 3 | /// 4 | /// Marker interface to indicate that a class should be ignored during assembly scanning. 5 | /// 6 | public interface IIgnoreOnAssemblyScan; 7 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/ApplicationAssemblyMarker.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | namespace Digdir.Domain.Dialogporten.Application; 4 | 5 | public sealed class ApplicationAssemblyMarker 6 | { 7 | public static readonly Assembly Assembly = typeof(ApplicationAssemblyMarker).Assembly; 8 | } 9 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.GraphQL/EndUser/Queries.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Domain.Dialogporten.GraphQL.Common.Authorization; 2 | using HotChocolate.Authorization; 3 | 4 | namespace Digdir.Domain.Dialogporten.GraphQL.EndUser; 5 | 6 | [Authorize(Policy = AuthorizationPolicy.EndUser)] 7 | public sealed partial class Queries; 8 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Features/V1/Common/Content/PropertyInfoWithNullability.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | namespace Digdir.Domain.Dialogporten.Application.Features.V1.Common.Content; 4 | 5 | internal sealed record PropertyInfoWithNullability(PropertyInfo Property, NullabilityInfo NullabilityInfo); 6 | -------------------------------------------------------------------------------- /src/Digdir.Library.Dialogporten.WebApiClient/Common/IClock.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.ApiClients.Dialogporten.Common; 2 | 3 | internal interface IClock 4 | { 5 | DateTimeOffset UtcNow { get; } 6 | } 7 | 8 | internal class DefaultClock : IClock 9 | { 10 | public DateTimeOffset UtcNow => DateTimeOffset.UtcNow; 11 | } 12 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Common/Pagination/OrderOption/IOrderDefinition.cs: -------------------------------------------------------------------------------- 1 | namespace Digdir.Domain.Dialogporten.Application.Common.Pagination.OrderOption; 2 | 3 | public interface IOrderDefinition 4 | { 5 | static abstract IOrderOptions Configure(IOrderOptionsBuilder options); 6 | } 7 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Infrastructure/InfrastructureAssemblyMarker.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | namespace Digdir.Domain.Dialogporten.Infrastructure; 4 | 5 | public sealed class InfrastructureAssemblyMarker 6 | { 7 | public static readonly Assembly Assembly = typeof(InfrastructureAssemblyMarker).Assembly; 8 | } 9 | -------------------------------------------------------------------------------- /src/Digdir.Tool.Dialogporten.LargeDataSetGenerator/CopyTaskDto.cs: -------------------------------------------------------------------------------- 1 | namespace Digdir.Tool.Dialogporten.LargeDataSetGenerator; 2 | 3 | public record struct CopyTaskDto( 4 | Func Generator, 5 | string EntityName, 6 | string CopyCommand, 7 | bool SingleLinePerTimestamp = false, 8 | int NumberOfTasks = 1); 9 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Common/Extensions/TypeExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Digdir.Domain.Dialogporten.Application.Common.Extensions; 2 | 3 | public static class TypeExtensions 4 | { 5 | public static bool IsNullableType(this Type type) 6 | => type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>); 7 | } 8 | -------------------------------------------------------------------------------- /local-otel-configuration/grafana-datasources.yml: -------------------------------------------------------------------------------- 1 | apiVersion: 1 2 | 3 | datasources: 4 | - name: Prometheus 5 | type: prometheus 6 | access: proxy 7 | url: http://prometheus:9090 8 | isDefault: true 9 | 10 | - name: Loki 11 | type: loki 12 | access: proxy 13 | url: http://loki:3100 14 | jsonData: 15 | maxLines: 1000 -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Common/ReturnTypes/ValidationError.cs: -------------------------------------------------------------------------------- 1 | using FluentValidation.Results; 2 | 3 | namespace Digdir.Domain.Dialogporten.Application.Common.ReturnTypes; 4 | 5 | public sealed record ValidationError(IEnumerable Errors) 6 | { 7 | public ValidationError(ValidationFailure error) : this([error]) { } 8 | } 9 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Queries/NotificationCondition/NotificationConditionDto.cs: -------------------------------------------------------------------------------- 1 | namespace Digdir.Domain.Dialogporten.Application.Features.V1.ServiceOwner.Dialogs.Queries.NotificationCondition; 2 | 3 | public sealed class NotificationConditionDto 4 | { 5 | public bool SendNotification { get; set; } 6 | } 7 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Infrastructure/GraphQL/GraphQlSubscriptionConstants.cs: -------------------------------------------------------------------------------- 1 | namespace Digdir.Domain.Dialogporten.Infrastructure.GraphQL; 2 | 3 | public static class GraphQlSubscriptionConstants 4 | { 5 | public const string SubscriptionTopicPrefix = "graphql_subscriptions_"; 6 | public const string DialogEventsTopic = "dialogEvents/"; 7 | } 8 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Common/Behaviours/FeatureMetric/IFeatureMetricDeliveryContext.cs: -------------------------------------------------------------------------------- 1 | namespace Digdir.Domain.Dialogporten.Application.Common.Behaviours.FeatureMetric; 2 | 3 | public interface IFeatureMetricDeliveryContext 4 | { 5 | void ReportOutcome(string presentationTag, params IEnumerable> additionalTags); 6 | } 7 | -------------------------------------------------------------------------------- /src/Digdir.Library.Entity.Abstractions/Features/Immutable/IImmutableEntity.cs: -------------------------------------------------------------------------------- 1 | namespace Digdir.Library.Entity.Abstractions.Features.Immutable; 2 | 3 | /// 4 | /// Interface to mark an entity as immutable. 5 | /// See Digdir.Library.Entity.EntityFrameworkCore.Features.Immutable.HandleImmutableEntities. 6 | /// 7 | public interface IImmutableEntity; 8 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Common/Context/ApplicationContext.cs: -------------------------------------------------------------------------------- 1 | namespace Digdir.Domain.Dialogporten.Application.Common.Context; 2 | 3 | public sealed class ApplicationContext : IApplicationContext 4 | { 5 | public Dictionary Metadata { get; } = []; 6 | public void AddMetadata(string key, string value) => Metadata[key] = value; 7 | } 8 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Common/ReturnTypes/Conflict.cs: -------------------------------------------------------------------------------- 1 | using FluentValidation.Results; 2 | 3 | namespace Digdir.Domain.Dialogporten.Application.Common.ReturnTypes; 4 | 5 | public sealed record Conflict(string PropertyName, string ErrorMessage) 6 | { 7 | public List ToValidationResults() => [new(PropertyName, ErrorMessage)]; 8 | } 9 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Externals/IPartyNameRegistry.cs: -------------------------------------------------------------------------------- 1 | namespace Digdir.Domain.Dialogporten.Application.Externals; 2 | 3 | public interface IPartyNameRegistry 4 | { 5 | Task GetName(string externalIdWithPrefix, CancellationToken cancellationToken); 6 | Task GetOrgName(string orgNumber, CancellationToken cancellationToken); 7 | } 8 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Domain/Outboxes/OutboxMessageConsumer.cs: -------------------------------------------------------------------------------- 1 | namespace Digdir.Domain.Dialogporten.Domain.Outboxes; 2 | 3 | public sealed class OutboxMessageConsumer 4 | { 5 | public Guid EventId { get; set; } 6 | public string ConsumerName { get; set; } = string.Empty; 7 | 8 | public OutboxMessage OutboxMessage { get; set; } = null!; 9 | } 10 | -------------------------------------------------------------------------------- /src/Digdir.Library.Entity.Abstractions/Features/Aggregate/AggregateParentAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace Digdir.Library.Entity.Abstractions.Features.Aggregate; 2 | 3 | /// 4 | /// Used to define a relationship from parent to child in the aggregate tree 5 | /// 6 | [AttributeUsage(AttributeTargets.Property)] 7 | public sealed class AggregateChildAttribute : Attribute; 8 | -------------------------------------------------------------------------------- /docs/schema/V1/.gitignore: -------------------------------------------------------------------------------- 1 | # in `npm prepublishOnly` we'll genenrate files for src/ 2 | # So here we gitignore all of the files since they're generated 3 | # But we add an exception for `src/.gitkeep` so that we can keep 4 | # the folder around. (Git only commits files, not folders, so 5 | # `.gitkeep` is just an empty file to force git to commit the folder) 6 | src/* 7 | !src/.gitkeep 8 | -------------------------------------------------------------------------------- /src/Digdir.Tool.Dialogporten.Ed25519KeyPairGenerator/Digdir.Tool.Dialogporten.Ed25519KeyPairGenerator.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Infrastructure/GraphQL/DialogEventPayload.cs: -------------------------------------------------------------------------------- 1 | namespace Digdir.Domain.Dialogporten.Infrastructure.GraphQL; 2 | 3 | internal struct DialogEventPayload 4 | { 5 | public Guid Id { get; set; } 6 | public DialogEventType Type { get; set; } 7 | } 8 | 9 | internal enum DialogEventType 10 | { 11 | DialogUpdated = 1, 12 | DialogDeleted = 2 13 | } 14 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Infrastructure/IEnumerableExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | 3 | namespace Digdir.Domain.Dialogporten.Infrastructure; 4 | 5 | internal static class IEnumerableExtensions 6 | { 7 | internal static bool IsNullOrEmpty([NotNullWhen(false)] this IEnumerable? enumerable) => 8 | enumerable is null || !enumerable.Any(); 9 | } 10 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.WebApi/Common/Json/ShortNameGenerator.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Domain.Dialogporten.WebApi.Common.Swagger; 2 | using NJsonSchema.Generation; 3 | 4 | namespace Digdir.Domain.Dialogporten.WebApi.Common.Json; 5 | 6 | internal sealed class ShortNameGenerator : ISchemaNameGenerator 7 | { 8 | public string Generate(Type type) => TypeNameConverter.ToShortName(type); 9 | } 10 | -------------------------------------------------------------------------------- /src/Digdir.Tool.Dialogporten.LargeDataSetGenerator/PersonNames.cs: -------------------------------------------------------------------------------- 1 | namespace Digdir.Tool.Dialogporten.LargeDataSetGenerator; 2 | 3 | internal static class PersonNames 4 | { 5 | internal static readonly string[] 6 | List = File.Exists("./person_names") 7 | ? File.ReadAllLines("./person_names") 8 | : throw new FileNotFoundException("./person_names"); 9 | } 10 | -------------------------------------------------------------------------------- /tests/Digdir.Domain.Dialogporten.Application.Integration.Tests/Common/ApplicationClassFixture.cs: -------------------------------------------------------------------------------- 1 | namespace Digdir.Domain.Dialogporten.Application.Integration.Tests.Common; 2 | 3 | public abstract class ApplicationClassFixture : ApplicationCollectionFixture, IClassFixture 4 | { 5 | protected ApplicationClassFixture(DialogApplication application) : base(application) { } 6 | } 7 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.WebApi/Common/Extensions/StringExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | 3 | namespace Digdir.Domain.Dialogporten.WebApi.Common.Extensions; 4 | 5 | internal static class StringExtensions 6 | { 7 | public static string FormatInvariant(this string pattern, params object[] args) 8 | => string.Format(CultureInfo.InvariantCulture, pattern, args); 9 | } 10 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Domain/Digdir.Domain.Dialogporten.Domain.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.GraphQL/Common/Constants.cs: -------------------------------------------------------------------------------- 1 | namespace Digdir.Domain.Dialogporten.GraphQL.Common; 2 | 3 | internal static class Constants 4 | { 5 | internal const string Authorization = "Authorization"; 6 | internal const string CurrentTokenIssuer = "CurrentIssuer"; 7 | internal const string AcceptLanguage = "Accept-Language"; 8 | internal const string ETag = "Etag"; 9 | } 10 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Infrastructure/Persistence/Constants.cs: -------------------------------------------------------------------------------- 1 | namespace Digdir.Domain.Dialogporten.Infrastructure.Persistence; 2 | 3 | internal static class Constants 4 | { 5 | internal const string Gin = "gin"; 6 | internal const string GinTrgmOps = "gin_trgm_ops"; 7 | internal const string PostgreSqlTrigram = "pg_trgm"; 8 | internal const string BtreeGin = "btree_gin"; 9 | } 10 | -------------------------------------------------------------------------------- /tests/Digdir.Domain.Dialogporten.Application.Unit.Tests/Common/IOptionsMock.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Options; 2 | 3 | namespace Digdir.Domain.Dialogporten.Application.Unit.Tests.Common; 4 | 5 | internal sealed class OptionsMock : IOptions where T : class 6 | { 7 | public T Value { get; set; } 8 | 9 | public OptionsMock(T value) 10 | { 11 | Value = value; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Service/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "Digdir.Domain.Dialogporten.Service": { 4 | "commandName": "Project", 5 | "launchBrowser": false, 6 | "environmentVariables": { 7 | "DOTNET_ENVIRONMENT": "Development" 8 | }, 9 | "applicationUrl": "https://localhost:56841;http://localhost:56842" 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Domain/Dialogs/Events/DialogSeenDomainEvent.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Domain.Dialogporten.Domain.Common.DomainEvents; 2 | 3 | namespace Digdir.Domain.Dialogporten.Domain.Dialogs.Events; 4 | 5 | public sealed record DialogSeenDomainEvent( 6 | Guid DialogId, 7 | string ServiceResource, 8 | string Party, 9 | string? Process, 10 | string? PrecedingProcess) : DomainEvent, IProcessEvent; 11 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | **/.dockerignore 2 | **/.env 3 | **/.git 4 | **/.gitignore 5 | **/.project 6 | **/.settings 7 | **/.toolstarget 8 | **/.vs 9 | **/.vscode 10 | **/.idea 11 | **/*.*proj.user 12 | **/*.dbmdl 13 | **/*.jfm 14 | **/azds.yaml 15 | **/bin 16 | **/charts 17 | **/docker-compose* 18 | **/Dockerfile* 19 | **/node_modules 20 | **/npm-debug.log 21 | **/obj 22 | **/secrets.dev.yaml 23 | **/values.dev.yaml 24 | LICENSE 25 | README.md -------------------------------------------------------------------------------- /docs/schema/V1/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@digdir/dialogporten-schema", 3 | "version": "1.0.11", 4 | "description": "GraphQl schema and OpenAPI spec for Dialogporten", 5 | "engines": { 6 | "node": "22" 7 | }, 8 | "author": "DigDir", 9 | "main": "src/index.js", 10 | "repository": { 11 | "url": "git+https://github.com/Altinn/dialogporten.git" 12 | }, 13 | "license": "ISC", 14 | "type": "module" 15 | } 16 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Domain/Dialogs/Events/DialogCreatedDomainEvent.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Domain.Dialogporten.Domain.Common.DomainEvents; 2 | 3 | namespace Digdir.Domain.Dialogporten.Domain.Dialogs.Events; 4 | 5 | public sealed record DialogCreatedDomainEvent( 6 | Guid DialogId, 7 | string ServiceResource, 8 | string Party, 9 | string? Process, 10 | string? PrecedingProcess) : DomainEvent, IProcessEvent; 11 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Domain/Dialogs/Events/DialogDeletedDomainEvent.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Domain.Dialogporten.Domain.Common.DomainEvents; 2 | 3 | namespace Digdir.Domain.Dialogporten.Domain.Dialogs.Events; 4 | 5 | public sealed record DialogDeletedDomainEvent( 6 | Guid DialogId, 7 | string ServiceResource, 8 | string Party, 9 | string? Process, 10 | string? PrecedingProcess) : DomainEvent, IProcessEvent; 11 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Domain/Dialogs/Events/DialogUpdatedDomainEvent.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Domain.Dialogporten.Domain.Common.DomainEvents; 2 | 3 | namespace Digdir.Domain.Dialogporten.Domain.Dialogs.Events; 4 | 5 | public sealed record DialogUpdatedDomainEvent( 6 | Guid DialogId, 7 | string ServiceResource, 8 | string Party, 9 | string? Process, 10 | string? PrecedingProcess) : DomainEvent, IProcessEvent; 11 | -------------------------------------------------------------------------------- /src/Digdir.Library.Entity.Abstractions/Features/Identifiable/IIdentifiableEntity.cs: -------------------------------------------------------------------------------- 1 | namespace Digdir.Library.Entity.Abstractions.Features.Identifiable; 2 | 3 | /// 4 | /// Abstraction implemented by entities that can be identified. 5 | /// 6 | public interface IIdentifiableEntity 7 | { 8 | /// 9 | /// The entity identification. 10 | /// 11 | Guid Id { get; set; } 12 | } 13 | -------------------------------------------------------------------------------- /.azure/modules/keyvault/upsertSecret.bicep: -------------------------------------------------------------------------------- 1 | param destKeyVaultName string 2 | param secretName string 3 | param tags object 4 | @secure() 5 | param secretValue string 6 | 7 | resource secret 'Microsoft.KeyVault/vaults/secrets@2024-11-01' = { 8 | name: '${destKeyVaultName}/${secretName}' 9 | properties: { 10 | value: secretValue 11 | } 12 | tags: tags 13 | } 14 | 15 | output secretUri string = secret.properties.secretUri 16 | -------------------------------------------------------------------------------- /.github/tools/README.md: -------------------------------------------------------------------------------- 1 | # Tools 2 | 3 | `.github/tools/containerAppJobVerifier.sh` is used to verify the status of a container application job. It checks if the job has completed successfully or not. 4 | 5 | `.github/tools/pwdGenerator.ps1` is used to generate passwords. 6 | 7 | `.github/tools/revisionVerifier.sh` is used to verify the revision of the deployed container app. It ensures that the current revision has been deployed successfully. 8 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Domain/Dialogs/Events/DialogRestoredDomainEvent.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Domain.Dialogporten.Domain.Common.DomainEvents; 2 | 3 | namespace Digdir.Domain.Dialogporten.Domain.Dialogs.Events; 4 | 5 | public sealed record DialogRestoredDomainEvent( 6 | Guid DialogId, 7 | string ServiceResource, 8 | string Party, 9 | string? Process, 10 | string? PrecedingProcess) : DomainEvent, IProcessEvent; 11 | -------------------------------------------------------------------------------- /tests/k6/common/summary.js: -------------------------------------------------------------------------------- 1 | import { generateJUnitXML, textSummary } from "./report.js"; 2 | 3 | export default function (data) { 4 | return { 5 | 'stdout': textSummary(data, { indent: ' ', enableColors: true }) + "\n", 6 | //'summary.txt': textSummary(data, { indent: ' ', enableColors: false }) + "\n", 7 | //'summary.json': JSON.stringify(data), 8 | 'junit.xml': generateJUnitXML(data) 9 | }; 10 | } -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.WebApi/Endpoints/V1/EndUser/EndUserGroup.cs: -------------------------------------------------------------------------------- 1 | using FastEndpoints; 2 | 3 | namespace Digdir.Domain.Dialogporten.WebApi.Endpoints.V1.EndUser; 4 | 5 | public sealed class EndUserGroup : Group 6 | { 7 | public const string RoutePrefix = "enduser"; 8 | public EndUserGroup() 9 | { 10 | Configure(RoutePrefix, ep => 11 | { 12 | ep.EndpointVersion(1); 13 | }); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Digdir.Library.Dialogporten.WebApiClient.WebApiSample/WebApiSample.http: -------------------------------------------------------------------------------- 1 | @HostAddress = http://localhost:5006 2 | @dialogId = DIALOG_ID_HERE 3 | @dialogToken = "DIALOG_TOKEN_HERE" 4 | 5 | ### Verify DialogToken 6 | 7 | POST {{HostAddress}}/dialogTokenVerify/ 8 | Accept: application/json 9 | 10 | { 11 | "token": {{dialogToken}} 12 | } 13 | 14 | ### Get dialog by id 15 | GET {{HostAddress}}/dialog/{{dialogId}} 16 | Accept: application/json 17 | -------------------------------------------------------------------------------- /src/Digdir.Library.Utils.AspNet/WebHostCommonSettings.cs: -------------------------------------------------------------------------------- 1 | namespace Digdir.Library.Utils.AspNet; 2 | 3 | public sealed class WebHostCommonSettings 4 | { 5 | public const string SectionName = "WebHostCommon"; 6 | 7 | public MaintenanceMode MaintenanceMode { get; init; } = new(); 8 | } 9 | 10 | public sealed class MaintenanceMode 11 | { 12 | public bool Enabled { get; init; } 13 | public DateTimeOffset? RetryAt { get; init; } 14 | } 15 | -------------------------------------------------------------------------------- /.azure/applications/web-api-migration-job/prod.bicepparam: -------------------------------------------------------------------------------- 1 | using './main.bicep' 2 | 3 | param environment = 'prod' 4 | param location = 'norwayeast' 5 | param imageTag = readEnvironmentVariable('IMAGE_TAG') 6 | param replicaTimeOutInSeconds = 86400 7 | 8 | //secrets 9 | param containerAppEnvironmentName = readEnvironmentVariable('AZURE_CONTAINER_APP_ENVIRONMENT_NAME') 10 | param environmentKeyVaultName = readEnvironmentVariable('AZURE_ENVIRONMENT_KEY_VAULT_NAME') 11 | -------------------------------------------------------------------------------- /.azure/applications/web-api-migration-job/test.bicepparam: -------------------------------------------------------------------------------- 1 | using './main.bicep' 2 | 3 | param environment = 'test' 4 | param location = 'norwayeast' 5 | param imageTag = readEnvironmentVariable('IMAGE_TAG') 6 | param replicaTimeOutInSeconds = 86400 7 | 8 | //secrets 9 | param containerAppEnvironmentName = readEnvironmentVariable('AZURE_CONTAINER_APP_ENVIRONMENT_NAME') 10 | param environmentKeyVaultName = readEnvironmentVariable('AZURE_ENVIRONMENT_KEY_VAULT_NAME') 11 | -------------------------------------------------------------------------------- /.azure/applications/web-api-migration-job/yt01.bicepparam: -------------------------------------------------------------------------------- 1 | using './main.bicep' 2 | 3 | param environment = 'yt01' 4 | param location = 'norwayeast' 5 | param imageTag = readEnvironmentVariable('IMAGE_TAG') 6 | param replicaTimeOutInSeconds = 86400 7 | 8 | //secrets 9 | param containerAppEnvironmentName = readEnvironmentVariable('AZURE_CONTAINER_APP_ENVIRONMENT_NAME') 10 | param environmentKeyVaultName = readEnvironmentVariable('AZURE_ENVIRONMENT_KEY_VAULT_NAME') 11 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | # ENV variables for docker-compose 2 | POSTGRES_USER=postgres 3 | POSTGRES_PASSWORD=supersecret 4 | POSTGRES_DB=dialogporten 5 | DB_CONNECTION_STRING=Server=dialogporten-postgres;Port=5432;Database=${POSTGRES_DB};User ID=${POSTGRES_USER};Password=${POSTGRES_PASSWORD}; 6 | 7 | COMPOSE_PROJECT_NAME=digdir 8 | 9 | # OTEL 10 | OTEL_NAMESPACE=dialogporten-local 11 | OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4318 12 | OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf -------------------------------------------------------------------------------- /src/Digdir.Library.Entity.Abstractions/Features/Versionable/IVersionableEntity.cs: -------------------------------------------------------------------------------- 1 | namespace Digdir.Library.Entity.Abstractions.Features.Versionable; 2 | 3 | /// 4 | /// Abstraction implemented by entities to keep track of their version. 5 | /// 6 | public interface IVersionableEntity 7 | { 8 | /// 9 | /// The entity tag unique to each version of the entity. 10 | /// 11 | Guid Revision { get; set; } 12 | } 13 | -------------------------------------------------------------------------------- /tests/k6/tests/all-tests.js: -------------------------------------------------------------------------------- 1 | import { default as serviceOwnerTests } from './serviceowner/all-tests.js'; 2 | import { default as enduserTests } from './enduser/all-tests.js'; 3 | import { default as sentinelCheck } from '../common/sentinel.js'; 4 | 5 | export function runAllTests() { 6 | serviceOwnerTests(); 7 | enduserTests(); 8 | 9 | // Run sentinel check last, which will warn about and purge any leftover dialogs 10 | sentinelCheck(); 11 | }; 12 | -------------------------------------------------------------------------------- /.azure/applications/web-api-migration-job/staging.bicepparam: -------------------------------------------------------------------------------- 1 | using './main.bicep' 2 | 3 | param environment = 'staging' 4 | param location = 'norwayeast' 5 | param imageTag = readEnvironmentVariable('IMAGE_TAG') 6 | param replicaTimeOutInSeconds = 86400 7 | 8 | //secrets 9 | param containerAppEnvironmentName = readEnvironmentVariable('AZURE_CONTAINER_APP_ENVIRONMENT_NAME') 10 | param environmentKeyVaultName = readEnvironmentVariable('AZURE_ENVIRONMENT_KEY_VAULT_NAME') 11 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Common/Extensions/QueryableExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | 3 | namespace Digdir.Domain.Dialogporten.Application.Common.Extensions; 4 | 5 | public static class QueryableExtensions 6 | { 7 | public static IQueryable WhereIf(this IQueryable source, bool predicate, Expression> queryPredicate) 8 | => predicate ? source.Where(queryPredicate) : source; 9 | } 10 | -------------------------------------------------------------------------------- /tests/k6/tests/enduser/parties.js: -------------------------------------------------------------------------------- 1 | import { describe, expect, expectStatusFor, getEU } from '../../common/testimports.js' 2 | 3 | export default function () { 4 | describe('Check if we get any parties', () => { 5 | let r = getEU("parties"); 6 | expectStatusFor(r).to.equal(200); 7 | expect(r, 'response').to.have.validJsonBody(); 8 | expect(r.json(), 'response json').to.have.property("authorizedParties").with.lengthOf.at.least(2); 9 | }); 10 | } -------------------------------------------------------------------------------- /.github/pr-title-checker-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "LABEL": { 3 | "name": "title needs formatting", 4 | "color": "EEEEEE" 5 | }, 6 | "CHECKS": { 7 | "regexp": "^(?!.*[\"'`])(feat|fix|docs|test|ci|chore|trivial)!?(\\(.*\\))?!?:.*" 8 | }, 9 | "MESSAGES": { 10 | "success": "PR title is valid", 11 | "failure": "PR title is invalid", 12 | "notice": "PR Title needs to pass regex '^(?!.*[\"'`])(feat|fix|docs|test|ci|chore|trivial)!?(\\(.*\\))?!?:.*'" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Externals/IServiceOwnerNameRegistry.cs: -------------------------------------------------------------------------------- 1 | namespace Digdir.Domain.Dialogporten.Application.Externals; 2 | 3 | public interface IServiceOwnerNameRegistry 4 | { 5 | Task GetServiceOwnerInfo(string orgNumber, CancellationToken cancellationToken); 6 | } 7 | 8 | public sealed class ServiceOwnerInfo 9 | { 10 | public required string OrgNumber { get; init; } 11 | public required string ShortName { get; init; } 12 | } 13 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Domain/Dialogs/Events/DialogTransmissionCreatedDomainEvent.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Domain.Dialogporten.Domain.Common.DomainEvents; 2 | 3 | namespace Digdir.Domain.Dialogporten.Domain.Dialogs.Events; 4 | 5 | public sealed record DialogTransmissionCreatedDomainEvent( 6 | Guid DialogId, 7 | Guid TransmissionId, 8 | string ServiceResource, 9 | string Party, 10 | string? Process, 11 | string? PrecedingProcess) : DomainEvent, IProcessEvent; 12 | -------------------------------------------------------------------------------- /src/Digdir.Library.Entity.Abstractions/Features/Creatable/ICreatableEntity.cs: -------------------------------------------------------------------------------- 1 | namespace Digdir.Library.Entity.Abstractions.Features.Creatable; 2 | 3 | /// 4 | /// Abstraction implemented by entities that can be created during the application lifetime. 5 | /// 6 | public interface ICreatableEntity 7 | { 8 | /// 9 | /// Time at which the entity was first created in UTC. 10 | /// 11 | DateTimeOffset CreatedAt { get; set; } 12 | } 13 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.GraphQL/Common/Authentication/AuthenticationOptions.cs: -------------------------------------------------------------------------------- 1 | namespace Digdir.Domain.Dialogporten.GraphQL.Common.Authentication; 2 | 3 | public sealed class AuthenticationOptions 4 | { 5 | public required List JwtBearerTokenSchemas { get; init; } 6 | } 7 | 8 | public sealed class JwtBearerTokenSchemasOptions 9 | { 10 | public required string Name { get; init; } 11 | public required string WellKnown { get; init; } 12 | } 13 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Infrastructure/Common/Exceptions/UpstreamServiceException.cs: -------------------------------------------------------------------------------- 1 | namespace Digdir.Domain.Dialogporten.Infrastructure.Common.Exceptions; 2 | 3 | public interface IUpstreamServiceError; 4 | internal sealed class UpstreamServiceException : Exception, IUpstreamServiceError 5 | { 6 | public UpstreamServiceException(Exception innerException) : base(innerException.Message, innerException) { } 7 | public UpstreamServiceException(string message) : base(message) { } 8 | } 9 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.WebApi/Common/Authentication/AuthenticationOptions.cs: -------------------------------------------------------------------------------- 1 | namespace Digdir.Domain.Dialogporten.WebApi.Common.Authentication; 2 | 3 | public sealed class AuthenticationOptions 4 | { 5 | public required List JwtBearerTokenSchemas { get; init; } 6 | } 7 | 8 | public sealed class JwtBearerTokenSchemasOptions 9 | { 10 | public required string Name { get; init; } 11 | public required string WellKnown { get; init; } 12 | } 13 | -------------------------------------------------------------------------------- /src/Digdir.Library.Entity.Abstractions/Features/Updatable/IUpdateableEntity.cs: -------------------------------------------------------------------------------- 1 | namespace Digdir.Library.Entity.Abstractions.Features.Updatable; 2 | 3 | /// 4 | /// Abstraction implemented by entities that can be updated during the application lifetime. 5 | /// 6 | public interface IUpdateableEntity 7 | { 8 | /// 9 | /// Time at which the entity was last updated in UTC. 10 | /// 11 | DateTimeOffset UpdatedAt { get; set; } 12 | } 13 | -------------------------------------------------------------------------------- /tests/Digdir.Domain.Dialogporten.Application.Integration.Tests/Common/ApplicationCollectionFixture.cs: -------------------------------------------------------------------------------- 1 | namespace Digdir.Domain.Dialogporten.Application.Integration.Tests.Common; 2 | 3 | public abstract class ApplicationCollectionFixture(DialogApplication application) : IAsyncLifetime 4 | { 5 | protected DialogApplication Application { get; } = application; 6 | 7 | public Task DisposeAsync() => Task.CompletedTask; 8 | public Task InitializeAsync() => Application.ResetState(); 9 | } 10 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Domain/Common/DomainEvents/DomainEventExtensions.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Domain.Dialogporten.Domain.Common.EventPublisher; 2 | 3 | namespace Digdir.Domain.Dialogporten.Domain.Common.DomainEvents; 4 | 5 | public static class DomainEventExtensions 6 | { 7 | public static bool ShouldNotBeSentToAltinnEvents(this IDomainEvent domainEvent) 8 | => domainEvent.Metadata.TryGetValue(Constants.IsSilentUpdate, out var value) 9 | && value == bool.TrueString; 10 | } 11 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Domain/DomainExtensions.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Domain.Dialogporten.Domain.Common.EventPublisher; 2 | 3 | namespace Digdir.Domain.Dialogporten.Domain; 4 | 5 | public static class DomainExtensions 6 | { 7 | public static IEnumerable GetDomainEventTypes() 8 | => DomainAssemblyMarker.Assembly.DefinedTypes 9 | .Where(x => !x.IsAbstract && !x.IsInterface && !x.IsGenericType) 10 | .Where(x => x.IsAssignableTo(typeof(IDomainEvent))); 11 | } 12 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Domain/SubjectResources/SubjectResource.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Library.Entity.Abstractions; 2 | 3 | namespace Digdir.Domain.Dialogporten.Domain.SubjectResources; 4 | 5 | public class SubjectResource : IEntity 6 | { 7 | public Guid Id { get; set; } 8 | public string Subject { get; set; } = null!; 9 | public string Resource { get; set; } = null!; 10 | public DateTimeOffset UpdatedAt { get; set; } 11 | public DateTimeOffset CreatedAt { get; set; } 12 | } 13 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.WebApi/Endpoints/V1/ServiceOwner/ServiceOwnerGroup.cs: -------------------------------------------------------------------------------- 1 | using FastEndpoints; 2 | 3 | namespace Digdir.Domain.Dialogporten.WebApi.Endpoints.V1.ServiceOwner; 4 | 5 | public sealed class ServiceOwnerGroup : Group 6 | { 7 | public const string RoutePrefix = "Serviceowner"; 8 | public ServiceOwnerGroup() 9 | { 10 | Configure(RoutePrefix.ToLowerInvariant(), ep => 11 | { 12 | ep.EndpointVersion(1); 13 | }); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Features/V1/ResourceRegistry/Commands/SyncPolicy/SyncPolicyCommandValidator.cs: -------------------------------------------------------------------------------- 1 | using FluentValidation; 2 | 3 | namespace Digdir.Domain.Dialogporten.Application.Features.V1.ResourceRegistry.Commands.SyncPolicy; 4 | 5 | internal sealed class SyncPolicyCommandValidator : AbstractValidator 6 | { 7 | public SyncPolicyCommandValidator() 8 | { 9 | RuleFor(x => x.NumberOfConcurrentRequests).InclusiveBetween(1, 50); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.GraphQL/EndUser/Parties/MappingProfile.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using Digdir.Domain.Dialogporten.Application.Features.V1.AccessManagement.Queries.GetParties; 3 | 4 | namespace Digdir.Domain.Dialogporten.GraphQL.EndUser.Parties; 5 | 6 | public sealed class MappingProfile : Profile 7 | { 8 | public MappingProfile() 9 | { 10 | CreateMap(); 11 | CreateMap(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Features/V1/WellKnown/OauthAuthorizationServer/Queries/Get/GetOauthAuthorizationServerDto.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace Digdir.Domain.Dialogporten.Application.Features.V1.WellKnown.OauthAuthorizationServer.Queries.Get; 4 | 5 | public sealed class GetOauthAuthorizationServerDto 6 | { 7 | public string Issuer { get; set; } = null!; 8 | 9 | [JsonPropertyName("jwks_uri")] 10 | public string JwksUri { get; set; } = null!; 11 | } 12 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Features/V1/ResourceRegistry/Commands/SyncSubjectMap/SyncSubjectMapCommandValidator.cs: -------------------------------------------------------------------------------- 1 | using FluentValidation; 2 | 3 | namespace Digdir.Domain.Dialogporten.Application.Features.V1.ResourceRegistry.Commands.SyncSubjectMap; 4 | 5 | internal sealed class SyncSubjectMapCommandValidator : AbstractValidator 6 | { 7 | public SyncSubjectMapCommandValidator() 8 | { 9 | RuleFor(x => x.BatchSize).InclusiveBetween(1, 1000); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /docker-compose-cdc.yml: -------------------------------------------------------------------------------- 1 | services: 2 | dialogporten-service: 3 | build: 4 | context: . 5 | dockerfile: src/Digdir.Domain.Dialogporten.Service/Dockerfile 6 | restart: always 7 | depends_on: 8 | dialogporten-postgres: 9 | condition: service_healthy 10 | dialogporten-migrations: 11 | condition: service_completed_successfully 12 | environment: 13 | - Infrastructure:DialogDbConnectionString=${DB_CONNECTION_STRING} 14 | - ASPNETCORE_ENVIRONMENT=Development 15 | -------------------------------------------------------------------------------- /docs/schema/V1/gql-to-js.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | 4 | INPUT_FILE="schema.verified.graphql" 5 | OUTPUT_FILE="src/index.js" 6 | VAR_NAME="schema_verified_graphql" 7 | 8 | if [[ ! -f "$INPUT_FILE" ]]; then 9 | echo "Error: $INPUT_FILE not found!" 10 | exit 1 11 | fi 12 | 13 | CLEANED_SCHEMA=$(sed '1s/^\xEF\xBB\xBF//' "$INPUT_FILE" | sed "s/\`/'/g") 14 | 15 | { 16 | printf "export const %s = \`" "$VAR_NAME" 17 | printf "%s" "$CLEANED_SCHEMA" 18 | printf "\`" 19 | } > "$OUTPUT_FILE" 20 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Domain/Actors/ActorType.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Library.Entity.Abstractions.Features.Lookup; 2 | 3 | namespace Digdir.Domain.Dialogporten.Domain.Actors; 4 | 5 | public sealed class ActorType : AbstractLookupEntity 6 | { 7 | public ActorType(Values id) : base(id) { } 8 | public override ActorType MapValue(Values id) => new(id); 9 | 10 | public enum Values 11 | { 12 | PartyRepresentative = 1, 13 | ServiceOwner = 2 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationPolicy.cs: -------------------------------------------------------------------------------- 1 | namespace Digdir.Domain.Dialogporten.GraphQL.Common.Authorization; 2 | 3 | internal static class AuthorizationPolicy 4 | { 5 | public const string EndUser = "enduser"; 6 | public const string EndUserSubscription = "enduserSubscription"; 7 | public const string ServiceProvider = "serviceprovider"; 8 | public const string ServiceProviderSearch = "serviceproviderSearch"; 9 | public const string Testing = "testing"; 10 | } 11 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Domain/Parties/Abstractions/IPartyIdentifier.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | 3 | namespace Digdir.Domain.Dialogporten.Domain.Parties.Abstractions; 4 | 5 | public interface IPartyIdentifier 6 | { 7 | string FullId { get; } 8 | string Id { get; } 9 | static abstract string Prefix { get; } 10 | static abstract string PrefixWithSeparator { get; } 11 | static abstract bool TryParse(ReadOnlySpan value, [NotNullWhen(true)] out IPartyIdentifier? identifier); 12 | } 13 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Common/Extensions/Enumerables/General.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Diagnostics.CodeAnalysis; 3 | 4 | namespace Digdir.Domain.Dialogporten.Application.Common.Extensions.Enumerables; 5 | 6 | internal static class General 7 | { 8 | internal static bool IsNullOrEmpty([NotNullWhen(false)] this ICollection? values) => values is null || values.Count == 0; 9 | 10 | internal static IEnumerable EmptyIfNull(this IEnumerable? enumerable) => enumerable ?? []; 11 | } 12 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/EndUserContext/Queries/SearchLabelAssignmentLog/MappingProfile.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using Digdir.Domain.Dialogporten.Domain.DialogEndUserContexts.Entities; 3 | 4 | namespace Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.EndUserContext.Queries.SearchLabelAssignmentLog; 5 | 6 | public sealed class MappingProfile : Profile 7 | { 8 | public MappingProfile() 9 | { 10 | CreateMap(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/ServiceOwnerContext/Commands/Update/MappingProfile.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using Digdir.Domain.Dialogporten.Domain.DialogServiceOwnerContexts.Entities; 3 | 4 | namespace Digdir.Domain.Dialogporten.Application.Features.V1.ServiceOwner.ServiceOwnerContext.Commands.Update; 5 | 6 | internal sealed class MappingProfile : Profile 7 | { 8 | public MappingProfile() 9 | { 10 | CreateMap(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Infrastructure/Persistence/ValueConverters/DateTimeOffsetConverter.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 2 | 3 | namespace Digdir.Domain.Dialogporten.Infrastructure.Persistence.ValueConverters; 4 | 5 | internal sealed class DateTimeOffsetConverter : ValueConverter 6 | { 7 | public DateTimeOffsetConverter() 8 | : base( 9 | d => d.ToUniversalTime(), 10 | d => d.ToUniversalTime()) 11 | { 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Digdir.Tool.Dialogporten.LargeDataSetGenerator/EntityGenerators/SeenLog.cs: -------------------------------------------------------------------------------- 1 | namespace Digdir.Tool.Dialogporten.LargeDataSetGenerator.EntityGenerators; 2 | 3 | internal static class SeenLog 4 | { 5 | public const string CopyCommand = """COPY "DialogSeenLog" ("Id", "CreatedAt", "IsViaServiceOwner", "DialogId", "EndUserTypeId") FROM STDIN (FORMAT csv, HEADER false, NULL '')"""; 6 | 7 | public static string Generate(DialogTimestamp dto) 8 | => $"{dto.DialogId},{dto.FormattedTimestamp},FALSE,{dto.DialogId},1"; 9 | } 10 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Features/V1/Common/Actors/ActorValidationErrorMessages.cs: -------------------------------------------------------------------------------- 1 | namespace Digdir.Domain.Dialogporten.Application.Features.V1.Common.Actors; 2 | 3 | public static class ActorValidationErrorMessages 4 | { 5 | public const string ActorIdActorNameExclusiveOr = "If 'ActorType' is 'ServiceOwner', both 'ActorId' and 'ActorName' must be null. " + 6 | "For any other value of 'ActorType', 'ActorId' or 'ActorName' must be set, but not both simultaneously."; 7 | } 8 | -------------------------------------------------------------------------------- /src/Digdir.Library.Entity.Abstractions/Digdir.Library.Entity.Abstractions.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | true 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Janitor/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Warning" 5 | } 6 | }, 7 | "Serilog": { 8 | "MinimumLevel": { 9 | "Default": "Warning", 10 | "Override": { 11 | "Microsoft.EntityFrameworkCore": "Error", 12 | "Digdir.Domain.Dialogporten": "Information" 13 | } 14 | } 15 | }, 16 | "Infrastructure": { 17 | "AltinnCdn": { 18 | "BaseUri": "https://altinncdn.no/" 19 | } 20 | }, 21 | "AllowedHosts": "*" 22 | } 23 | -------------------------------------------------------------------------------- /src/Digdir.Library.Utils.AspNet/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Microsoft.Extensions.DependencyInjection; 3 | 4 | namespace Digdir.Library.Utils.AspNet; 5 | 6 | public static class ServiceCollectionExtensions 7 | { 8 | public static IServiceCollection AddAspNetCommon(this IServiceCollection services, IConfiguration configuration) 9 | { 10 | services.AddOptions() 11 | .Bind(configuration); 12 | 13 | return services; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Domain/ResourcePolicyInformation/ResourcePolicyInformation.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Library.Entity.Abstractions; 2 | 3 | namespace Digdir.Domain.Dialogporten.Domain.ResourcePolicyInformation; 4 | 5 | public sealed class ResourcePolicyInformation : IEntity 6 | { 7 | public Guid Id { get; set; } 8 | public string Resource { get; set; } = null!; 9 | public int MinimumAuthenticationLevel { get; set; } 10 | public DateTimeOffset CreatedAt { get; set; } 11 | public DateTimeOffset UpdatedAt { get; set; } 12 | } 13 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Features/V1/AccessManagement/Queries/GetParties/MappingProfile.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using Digdir.Domain.Dialogporten.Application.Externals.AltinnAuthorization; 3 | 4 | namespace Digdir.Domain.Dialogporten.Application.Features.V1.AccessManagement.Queries.GetParties; 5 | 6 | internal sealed class MappingProfile : Profile 7 | { 8 | public MappingProfile() 9 | { 10 | CreateMap(); 11 | CreateMap(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/GetSeenLog/MappingProfile.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities; 3 | 4 | namespace Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.Dialogs.Queries.GetSeenLog; 5 | 6 | public sealed class MappingProfile : Profile 7 | { 8 | public MappingProfile() 9 | { 10 | CreateMap() 11 | .ForMember(dest => dest.SeenAt, opt => opt.MapFrom(src => src.CreatedAt)); 12 | 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/ServiceOwnerContext/Queries/GetServiceOwnerLabels/MappingProfile.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using Digdir.Domain.Dialogporten.Domain.DialogServiceOwnerContexts.Entities; 3 | 4 | namespace Digdir.Domain.Dialogporten.Application.Features.V1.ServiceOwner.ServiceOwnerContext.Queries.GetServiceOwnerLabels; 5 | 6 | internal sealed class MappingProfile : Profile 7 | { 8 | public MappingProfile() 9 | { 10 | CreateMap(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Digdir.Library.Entity.Abstractions/LibraryEntityAbstractionsAssemblyMarker.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | namespace Digdir.Library.Entity.Abstractions; 4 | 5 | /// 6 | /// Provides a marker for the Entity Abstractions assembly. 7 | /// 8 | public sealed class LibraryEntityAbstractionsAssemblyMarker 9 | { 10 | /// 11 | /// Gets the assembly of the Entity Abstractions. 12 | /// 13 | public static readonly Assembly Assembly = typeof(LibraryEntityAbstractionsAssemblyMarker).Assembly; 14 | } 15 | -------------------------------------------------------------------------------- /tests/Digdir.Domain.Dialogporten.Application.Integration.Tests/Common/IntegrationTestCloudBus.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Domain.Dialogporten.Application.Externals; 2 | 3 | namespace Digdir.Domain.Dialogporten.Application.Integration.Tests.Common; 4 | 5 | internal sealed class IntegrationTestCloudBus : ICloudEventBus 6 | { 7 | public List Events { get; } = []; 8 | public Task Publish(CloudEvent cloudEvent, CancellationToken cancellationToken) 9 | { 10 | Events.Add(cloudEvent); 11 | return Task.CompletedTask; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tests/k6/suites/createremove-perf-10-vus-1-min.js: -------------------------------------------------------------------------------- 1 | import { default as run, setup as testSetup } from "../tests/serviceowner/performance/createremove-no-delay.js"; 2 | import { default as summary } from "../common/summary.js"; 3 | export let options = { 4 | vus: 10, // Number of virtual users 5 | duration: '1m', // Test duration 6 | }; 7 | 8 | export function setup() { 9 | return testSetup(); 10 | } 11 | 12 | export default function (data) { run(data); } 13 | 14 | export function handleSummary(data) { 15 | return summary(data); 16 | } 17 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Common/ReturnTypes/BadRequest.cs: -------------------------------------------------------------------------------- 1 | using FluentValidation.Results; 2 | 3 | namespace Digdir.Domain.Dialogporten.Application.Common.ReturnTypes; 4 | 5 | public sealed record BadRequest(List Reasons) 6 | { 7 | private const string BadRequestMessage = "BadRequest"; 8 | 9 | public BadRequest(params string[] reasons) : this(reasons.ToList()) { } 10 | 11 | public List ToValidationResults() => 12 | Reasons.Select(x => new ValidationFailure(BadRequestMessage, x)).ToList(); 13 | } 14 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/SearchSeenLogs/MappingProfile.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities; 3 | 4 | namespace Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.Dialogs.Queries.SearchSeenLogs; 5 | 6 | public sealed class MappingProfile : Profile 7 | { 8 | public MappingProfile() 9 | { 10 | CreateMap() 11 | .ForMember(dest => dest.SeenAt, opt => opt.MapFrom(src => src.CreatedAt)); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Queries/GetSeenLog/MappingProfile.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities; 3 | 4 | namespace Digdir.Domain.Dialogporten.Application.Features.V1.ServiceOwner.Dialogs.Queries.GetSeenLog; 5 | 6 | public sealed class MappingProfile : Profile 7 | { 8 | public MappingProfile() 9 | { 10 | CreateMap() 11 | .ForMember(dest => dest.SeenAt, opt => opt.MapFrom(src => src.CreatedAt)); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Service/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Warning" 5 | } 6 | }, 7 | "Serilog": { 8 | "MinimumLevel": { 9 | "Default": "Warning", 10 | "Override": { 11 | "Microsoft.AspNetCore": "Warning", 12 | "Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware": "Fatal" 13 | } 14 | } 15 | }, 16 | "Infrastructure": { 17 | "AltinnCdn": { 18 | "BaseUri": "https://altinncdn.no/" 19 | } 20 | }, 21 | "AllowedHosts": "*" 22 | } 23 | -------------------------------------------------------------------------------- /tests/k6/tests/graphql/performance/graphqlGetParties.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The performance test for GraphQL search. 3 | * Run: k6 run tests/k6/tests/graphql/performance/graphqlGetParties.js --vus 1 --iterations 1 -e env=yt01 4 | */ 5 | 6 | import { _default, getOptions, _setup } from './graphqlCommonFunctions.js'; 7 | 8 | const label = "graphql-get-parties"; 9 | const queryType = "getParties" 10 | 11 | export const options = getOptions([label]); 12 | export function setup() { return _setup(label, queryType); } 13 | export default function (data) { _default(data); } 14 | -------------------------------------------------------------------------------- /.azure/applications/reindex-dialogsearch-job/test.bicepparam: -------------------------------------------------------------------------------- 1 | using './main.bicep' 2 | 3 | param environment = 'test' 4 | param location = 'norwayeast' 5 | param imageTag = readEnvironmentVariable('IMAGE_TAG') 6 | param replicaTimeOutInSeconds = 600 7 | 8 | //secrets 9 | param containerAppEnvironmentName = readEnvironmentVariable('AZURE_CONTAINER_APP_ENVIRONMENT_NAME') 10 | param environmentKeyVaultName = readEnvironmentVariable('AZURE_ENVIRONMENT_KEY_VAULT_NAME') 11 | param appInsightConnectionString = readEnvironmentVariable('AZURE_APP_INSIGHTS_CONNECTION_STRING') 12 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Common/ReturnTypes/DomainError.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Domain.Dialogporten.Domain.Common; 2 | using FluentValidation.Results; 3 | 4 | namespace Digdir.Domain.Dialogporten.Application.Common.ReturnTypes; 5 | 6 | public sealed record DomainError(IEnumerable Errors) 7 | { 8 | public DomainError(DomainFailure error) : this([error]) { } 9 | public List ToValidationResults() => Errors 10 | .Select(x => new ValidationFailure(x.PropertyName, x.ErrorMessage)) 11 | .ToList(); 12 | } 13 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Queries/GetSeenLog/SeenLogDto.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Domain.Dialogporten.Application.Features.V1.ServiceOwner.Common.Actors; 2 | 3 | namespace Digdir.Domain.Dialogporten.Application.Features.V1.ServiceOwner.Dialogs.Queries.GetSeenLog; 4 | 5 | public sealed class SeenLogDto 6 | { 7 | public Guid Id { get; set; } 8 | public DateTimeOffset SeenAt { get; set; } 9 | 10 | public ActorDto SeenBy { get; set; } = null!; 11 | 12 | public bool? IsViaServiceOwner { get; set; } 13 | } 14 | 15 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.WebApi/Endpoints/V1/MetadataGroup.cs: -------------------------------------------------------------------------------- 1 | using FastEndpoints; 2 | 3 | namespace Digdir.Domain.Dialogporten.WebApi.Endpoints.V1; 4 | 5 | public sealed class MetadataGroup : Group 6 | { 7 | private const string GroupName = "Metadata"; 8 | public MetadataGroup() 9 | { 10 | Configure(string.Empty, ep => 11 | { 12 | ep.DontAutoTag(); 13 | ep.Description(x => x.WithTags(GroupName)); 14 | ep.AllowAnonymous(); 15 | ep.EndpointVersion(1); 16 | }); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.azure/applications/reindex-dialogsearch-job/prod.bicepparam: -------------------------------------------------------------------------------- 1 | using './main.bicep' 2 | 3 | param environment = 'prod' 4 | param location = 'norwayeast' 5 | param imageTag = readEnvironmentVariable('IMAGE_TAG') 6 | param replicaTimeOutInSeconds = 172800 7 | 8 | //secrets 9 | param containerAppEnvironmentName = readEnvironmentVariable('AZURE_CONTAINER_APP_ENVIRONMENT_NAME') 10 | param environmentKeyVaultName = readEnvironmentVariable('AZURE_ENVIRONMENT_KEY_VAULT_NAME') 11 | param appInsightConnectionString = readEnvironmentVariable('AZURE_APP_INSIGHTS_CONNECTION_STRING') 12 | -------------------------------------------------------------------------------- /.azure/applications/reindex-dialogsearch-job/staging.bicepparam: -------------------------------------------------------------------------------- 1 | using './main.bicep' 2 | 3 | param environment = 'staging' 4 | param location = 'norwayeast' 5 | param imageTag = readEnvironmentVariable('IMAGE_TAG') 6 | param replicaTimeOutInSeconds = 600 7 | 8 | //secrets 9 | param containerAppEnvironmentName = readEnvironmentVariable('AZURE_CONTAINER_APP_ENVIRONMENT_NAME') 10 | param environmentKeyVaultName = readEnvironmentVariable('AZURE_ENVIRONMENT_KEY_VAULT_NAME') 11 | param appInsightConnectionString = readEnvironmentVariable('AZURE_APP_INSIGHTS_CONNECTION_STRING') 12 | -------------------------------------------------------------------------------- /.azure/applications/reindex-dialogsearch-job/yt01.bicepparam: -------------------------------------------------------------------------------- 1 | using './main.bicep' 2 | 3 | param environment = 'yt01' 4 | param location = 'norwayeast' 5 | param imageTag = readEnvironmentVariable('IMAGE_TAG') 6 | param replicaTimeOutInSeconds = 86400 7 | 8 | //secrets 9 | param containerAppEnvironmentName = readEnvironmentVariable('AZURE_CONTAINER_APP_ENVIRONMENT_NAME') 10 | param environmentKeyVaultName = readEnvironmentVariable('AZURE_ENVIRONMENT_KEY_VAULT_NAME') 11 | param appInsightConnectionString = readEnvironmentVariable('AZURE_APP_INSIGHTS_CONNECTION_STRING') 12 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/GetActivity/MappingProfile.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities; 3 | 4 | namespace Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.Dialogs.Queries.GetActivity; 5 | 6 | internal sealed class MappingProfile : Profile 7 | { 8 | public MappingProfile() 9 | { 10 | CreateMap() 11 | .ForMember(dest => dest.Type, opt => opt.MapFrom(src => src.TypeId)); 12 | 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/SearchActivities/MappingProfile.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities; 3 | 4 | namespace Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.Dialogs.Queries.SearchActivities; 5 | 6 | internal sealed class MappingProfile : Profile 7 | { 8 | public MappingProfile() 9 | { 10 | CreateMap() 11 | .ForMember(dest => dest.Type, opt => opt.MapFrom(src => src.TypeId)); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Queries/SearchSeenLogs/MappingProfile.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities; 3 | 4 | namespace Digdir.Domain.Dialogporten.Application.Features.V1.ServiceOwner.Dialogs.Queries.SearchSeenLogs; 5 | 6 | public sealed class MappingProfile : Profile 7 | { 8 | public MappingProfile() 9 | { 10 | CreateMap() 11 | .ForMember(dest => dest.SeenAt, opt => opt.MapFrom(src => src.CreatedAt)); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Queries/SearchSeenLogs/SeenLogDto.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Domain.Dialogporten.Application.Features.V1.ServiceOwner.Common.Actors; 2 | 3 | namespace Digdir.Domain.Dialogporten.Application.Features.V1.ServiceOwner.Dialogs.Queries.SearchSeenLogs; 4 | 5 | public sealed class SeenLogDto 6 | { 7 | public Guid Id { get; set; } 8 | public DateTimeOffset SeenAt { get; set; } 9 | 10 | public ActorDto SeenBy { get; set; } = null!; 11 | 12 | public bool? IsViaServiceOwner { get; set; } 13 | } 14 | 15 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Janitor/CostManagementAggregation/cost-coefficients.json: -------------------------------------------------------------------------------- 1 | { 2 | "CostCoefficients": { 3 | "CreateDialog": 1.5, 4 | "UpdateDialog": 1.8, 5 | "SoftDeleteDialog": 0.7, 6 | "HardDeleteDialog": 0.7, 7 | "GetDialogServiceOwner": 1.0, 8 | "GetDialogEndUser": 1.2, 9 | "SearchDialogsServiceOwner": 1.5, 10 | "SearchDialogsServiceOwnerWithEndUser": 2.5, 11 | "SearchDialogsEndUser": 2.5, 12 | "SetDialogLabel": 1.3, 13 | "BulkSetLabelsServiceOwnerWithEndUser": 2.0, 14 | "BulkSetLabelsEndUser": 2.0 15 | } 16 | } -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Service/ServiceUser.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Domain.Dialogporten.Application.Externals.Presentation; 2 | using System.Security.Claims; 3 | 4 | namespace Digdir.Domain.Dialogporten.Service; 5 | 6 | internal sealed class ServiceUser : IUser 7 | { 8 | public ClaimsPrincipal GetPrincipal() 9 | { 10 | throw new NotSupportedException( 11 | "At the time of this writing, Digdir.Domain.Dialogporten.Service should not " + 12 | "be using application commands or queries requiring the need for a user."); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Digdir.Tool.Dialogporten.LargeDataSetGenerator/EntityGenerators/EndUserContext.cs: -------------------------------------------------------------------------------- 1 | namespace Digdir.Tool.Dialogporten.LargeDataSetGenerator.EntityGenerators; 2 | 3 | internal static class EndUserContext 4 | { 5 | public const string CopyCommand = """COPY "DialogEndUserContext" ("Id", "CreatedAt", "UpdatedAt", "Revision", "DialogId", "SystemLabelId") FROM STDIN (FORMAT csv, HEADER false, NULL '')"""; 6 | 7 | public static string Generate(DialogTimestamp dto) => $"{dto.DialogId},{dto.FormattedTimestamp},{dto.FormattedTimestamp},{Guid.NewGuid()},{dto.DialogId},1"; 8 | } 9 | -------------------------------------------------------------------------------- /src/Digdir.Library.Entity.EntityFrameworkCore/LibraryEntityFrameworkCoreAssemblyMarker.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | namespace Digdir.Library.Entity.EntityFrameworkCore; 4 | 5 | /// 6 | /// Provides a marker for the Entity Framework Core assembly. 7 | /// 8 | public sealed class LibraryEntityFrameworkCoreAssemblyMarker 9 | { 10 | /// 11 | /// Gets the assembly of the Entity Framework Core. 12 | /// 13 | public static readonly Assembly Assembly = typeof(LibraryEntityFrameworkCoreAssemblyMarker).Assembly; 14 | } 15 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/ServiceOwnerContext/Commands/Update/UpdateServiceOwnerContextDto.cs: -------------------------------------------------------------------------------- 1 | namespace Digdir.Domain.Dialogporten.Application.Features.V1.ServiceOwner.ServiceOwnerContext.Commands.Update; 2 | 3 | public sealed class UpdateServiceOwnerContextDto 4 | { 5 | public List ServiceOwnerLabels { get; set; } = []; 6 | } 7 | 8 | public sealed class ServiceOwnerLabelDto 9 | { 10 | /// 11 | /// A label value. 12 | /// 13 | public string Value { get; set; } = null!; 14 | } 15 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Common/ReturnTypes/EntityNotVisible.cs: -------------------------------------------------------------------------------- 1 | using FluentValidation.Results; 2 | 3 | namespace Digdir.Domain.Dialogporten.Application.Common.ReturnTypes; 4 | 5 | public sealed record EntityNotVisible(DateTimeOffset VisibleFrom) 6 | : EntityNotVisible(typeof(T).Name, VisibleFrom); 7 | 8 | public record EntityNotVisible(string Name, DateTimeOffset VisibleFrom) 9 | { 10 | public string Message => $"Dialog is not visible until {VisibleFrom:s}"; 11 | 12 | public List ToValidationResults() => [new(Name, Message)]; 13 | } 14 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Domain/Common/DomainEvents/DomainEvent.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | using Digdir.Domain.Dialogporten.Domain.Common.EventPublisher; 3 | 4 | namespace Digdir.Domain.Dialogporten.Domain.Common.DomainEvents; 5 | 6 | public abstract record DomainEvent : IDomainEvent 7 | { 8 | [JsonInclude] 9 | public Guid EventId { get; private set; } = Guid.NewGuid(); 10 | 11 | [JsonInclude] 12 | public DateTimeOffset OccurredAt { get; set; } 13 | 14 | [JsonInclude] 15 | public Dictionary Metadata { get; set; } = []; 16 | } 17 | -------------------------------------------------------------------------------- /src/Digdir.Library.Entity.EntityFrameworkCore/Digdir.Library.Entity.EntityFrameworkCore.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | true 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/GetSeenLog/SeenLogDto.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.Common.Actors; 2 | 3 | namespace Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.Dialogs.Queries.GetSeenLog; 4 | 5 | public sealed class SeenLogDto 6 | { 7 | public Guid Id { get; set; } 8 | public DateTimeOffset SeenAt { get; set; } 9 | public ActorDto SeenBy { get; set; } = null!; 10 | 11 | public bool IsViaServiceOwner { get; set; } 12 | public bool IsCurrentEndUser { get; set; } 13 | } 14 | 15 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Purge/PurgeDialogCommandValidator.cs: -------------------------------------------------------------------------------- 1 | using FluentValidation; 2 | 3 | namespace Digdir.Domain.Dialogporten.Application.Features.V1.ServiceOwner.Dialogs.Commands.Purge; 4 | 5 | internal sealed class PurgeDialogCommandValidator : AbstractValidator 6 | { 7 | public PurgeDialogCommandValidator() 8 | { 9 | RuleFor(x => x.DialogId) 10 | .NotEqual(Guid.Empty) 11 | .WithMessage($"{{PropertyName}} was either badly formatted or {Guid.Empty}"); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Domain/Attachments/AttachmentUrlConsumerType.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Library.Entity.Abstractions.Features.Lookup; 2 | 3 | namespace Digdir.Domain.Dialogporten.Domain.Attachments; 4 | 5 | public sealed class AttachmentUrlConsumerType : AbstractLookupEntity 6 | { 7 | public AttachmentUrlConsumerType(Values id) : base(id) { } 8 | public override AttachmentUrlConsumerType MapValue(Values id) => new(id); 9 | 10 | public enum Values 11 | { 12 | Gui = 1, 13 | Api = 2 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Domain/Common/EventPublisher/IEventPublisher.cs: -------------------------------------------------------------------------------- 1 | namespace Digdir.Domain.Dialogporten.Domain.Common.EventPublisher; 2 | 3 | /// 4 | /// Abstraction representing functionality to publish domain events. 5 | /// 6 | public interface IEventPublisher 7 | { 8 | /// 9 | /// Get a collection of queued up for dispatching at the end of the next unit of work. 10 | /// This also clears the collection on the EventPublisher. 11 | /// 12 | IEnumerable PopDomainEvents(); 13 | } 14 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Janitor/CostManagementAggregation/MetricsAggregationOptions.cs: -------------------------------------------------------------------------------- 1 | namespace Digdir.Domain.Dialogporten.Janitor.CostManagementAggregation; 2 | 3 | public sealed class MetricsAggregationOptions 4 | { 5 | public const string SectionName = "MetricsAggregation"; 6 | 7 | public string StorageConnectionString { get; set; } = string.Empty; 8 | 9 | public string StorageAccountName { get; set; } = string.Empty; 10 | 11 | public string StorageContainerName { get; set; } = string.Empty; 12 | public string SubscriptionId { get; set; } = string.Empty; 13 | } 14 | -------------------------------------------------------------------------------- /.azure/modules/managedIdentity/main.bicep: -------------------------------------------------------------------------------- 1 | @description('The location where the resources will be deployed') 2 | param location string 3 | 4 | @description('The name of the managed identity') 5 | param name string 6 | 7 | @description('Tags to apply to resources') 8 | param tags object 9 | 10 | resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2024-11-30' = { 11 | name: name 12 | location: location 13 | tags: tags 14 | } 15 | 16 | output managedIdentityId string = managedIdentity.id 17 | output managedIdentityPrincipalId string = managedIdentity.properties.principalId 18 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Common/IClock.cs: -------------------------------------------------------------------------------- 1 | namespace Digdir.Domain.Dialogporten.Application.Common; 2 | 3 | internal interface IClock 4 | { 5 | DateTimeOffset UtcNowOffset { get; } 6 | DateTimeOffset NowOffset { get; } 7 | DateTime UtcNow { get; } 8 | DateTime Now { get; } 9 | } 10 | 11 | internal sealed class Clock : IClock 12 | { 13 | public DateTimeOffset UtcNowOffset => DateTimeOffset.UtcNow; 14 | public DateTimeOffset NowOffset => DateTimeOffset.Now; 15 | public DateTime UtcNow => DateTime.UtcNow; 16 | public DateTime Now => DateTime.Now; 17 | } 18 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/SearchSeenLogs/SeenLogDto.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.Common.Actors; 2 | 3 | namespace Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.Dialogs.Queries.SearchSeenLogs; 4 | 5 | public sealed class SeenLogDto 6 | { 7 | public Guid Id { get; set; } 8 | public DateTimeOffset SeenAt { get; set; } 9 | 10 | public ActorDto SeenBy { get; set; } = null!; 11 | 12 | public bool IsViaServiceOwner { get; set; } 13 | public bool IsCurrentEndUser { get; set; } 14 | } 15 | -------------------------------------------------------------------------------- /tests/Digdir.Domain.Dialogporten.WebApi.Unit.Tests/Utils.cs: -------------------------------------------------------------------------------- 1 | namespace Digdir.Domain.Dialogporten.WebApi.Unit.Tests; 2 | 3 | public static class Utils 4 | { 5 | public static string? GetSolutionRootFolder() 6 | { 7 | var currentDirectory = Directory.GetCurrentDirectory(); 8 | var solutionFolder = currentDirectory; 9 | while (solutionFolder != null && Directory.GetFiles(solutionFolder, "*.sln").Length == 0) 10 | { 11 | solutionFolder = Directory.GetParent(solutionFolder)?.FullName; 12 | } 13 | return solutionFolder; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Features/V1/Common/Extensions/TransmissionEntityExtensions.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Transmissions; 2 | 3 | namespace Digdir.Domain.Dialogporten.Application.Features.V1.Common.Extensions; 4 | 5 | internal static class TransmissionEntityExtensions 6 | { 7 | internal static bool ContainsTransmissionByEndUser(this List transmissions) => 8 | transmissions.Any(x => x.TypeId 9 | is DialogTransmissionType.Values.Submission 10 | or DialogTransmissionType.Values.Correction); 11 | } 12 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.WebApi/Common/Authorization/AuthorizationPolicy.cs: -------------------------------------------------------------------------------- 1 | namespace Digdir.Domain.Dialogporten.WebApi.Common.Authorization; 2 | 3 | internal static class AuthorizationPolicy 4 | { 5 | public const string EndUser = "enduser"; 6 | public const string ServiceProvider = "serviceprovider"; 7 | public const string NotificationConditionCheck = "notificationConditionCheck"; 8 | public const string ServiceProviderSearch = "serviceproviderSearch"; 9 | public const string Testing = "testing"; 10 | public const string ServiceProviderAdmin = "serviceproviderAdmin"; 11 | } 12 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.GraphQL/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/launchsettings.json", 3 | "profiles": { 4 | "http": { 5 | "commandName": "Project", 6 | "dotnetRunMessages": true, 7 | "launchBrowser": false, 8 | "launchUrl": "graphql", 9 | "applicationUrl": "http://localhost:5181", 10 | "environmentVariables": { 11 | "DOTNET_ENVIRONMENT": "Development", 12 | "OTEL_EXPORTER_OTLP_PROTOCOL": "grpc", 13 | "OTEL_EXPORTER_OTLP_ENDPOINT": "http://localhost:4317" 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Domain/Localizations/LocalizationSet.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Library.Entity.Abstractions.Features.Aggregate; 2 | using Digdir.Library.Entity.Abstractions.Features.Identifiable; 3 | using Digdir.Library.Entity.Abstractions.Features.Immutable; 4 | 5 | namespace Digdir.Domain.Dialogporten.Domain.Localizations; 6 | 7 | public abstract class LocalizationSet : IIdentifiableEntity, IImmutableEntity 8 | { 9 | public Guid Id { get; set; } 10 | 11 | // === Plural principal relationships === 12 | [AggregateChild] 13 | public List Localizations { get; set; } = []; 14 | } 15 | -------------------------------------------------------------------------------- /tests/Digdir.Domain.Dialogporten.GraphQl.Integration.Tests/Utils.cs: -------------------------------------------------------------------------------- 1 | namespace Digdir.Domain.Dialogporten.GraphQl.Integration.Tests; 2 | 3 | public static class Utils 4 | { 5 | public static string? GetSolutionRootFolder() 6 | { 7 | var currentDirectory = Directory.GetCurrentDirectory(); 8 | var solutionFolder = currentDirectory; 9 | while (solutionFolder != null && Directory.GetFiles(solutionFolder, "*.sln").Length == 0) 10 | { 11 | solutionFolder = Directory.GetParent(solutionFolder)?.FullName; 12 | } 13 | return solutionFolder; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/k6/tests/graphql/performance/graphqlGetAllDialogsForEnduser.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The performance test for GraphQL search. 3 | * Run: k6 run tests/k6/tests/graphql/performance/graphqlGetAllDialogsForEnduser.js --vus 1 --iterations 1 -e env=yt01 4 | */ 5 | 6 | import { _default, getOptions, _setup } from './graphqlCommonFunctions.js'; 7 | 8 | const label = "graphql-getall-dialogs-for-enduser"; 9 | const queryType = "getAllDialogsForEnduser" 10 | 11 | export const options = getOptions([label]); 12 | export function setup() { return _setup(label, queryType); } 13 | export default function (data) { _default(data); } 14 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Domain/Dialogs/Events/Activities/DialogActivityCreatedDomainEvent.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Domain.Dialogporten.Domain.Common.DomainEvents; 2 | using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities; 3 | 4 | namespace Digdir.Domain.Dialogporten.Domain.Dialogs.Events.Activities; 5 | 6 | public sealed record DialogActivityCreatedDomainEvent( 7 | Guid DialogId, 8 | Guid ActivityId, 9 | DialogActivityType.Values TypeId, 10 | string Party, 11 | string ServiceResource, 12 | string? Process, 13 | string? PrecedingProcess, 14 | Uri? ExtendedType) : DomainEvent; 15 | -------------------------------------------------------------------------------- /tests/Digdir.Domain.Dialogporten.Application.Integration.Tests/Features/V1/DialogCqrsCollectionFixture.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Domain.Dialogporten.Application.Integration.Tests.Common; 2 | 3 | namespace Digdir.Domain.Dialogporten.Application.Integration.Tests.Features.V1; 4 | 5 | [CollectionDefinition(nameof(DialogCqrsCollectionFixture))] 6 | public class DialogCqrsCollectionFixture : ICollectionFixture 7 | { 8 | // This class has no code, and is never created. Its purpose is simply 9 | // to be the place to apply [CollectionDefinition] and all the 10 | // ICollectionFixture<> interfaces. 11 | } 12 | -------------------------------------------------------------------------------- /src/Digdir.Library.Dialogporten.WebApiClient.WebApiSample/Digdir.Library.Dialogporten.WebApiClient.WebApiSample.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 750256a4-f332-4783-8802-8a7d9566f9cb 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Features/V1/WellKnown/Jwks/Queries/Get/GetJwksDto.cs: -------------------------------------------------------------------------------- 1 | namespace Digdir.Domain.Dialogporten.Application.Features.V1.WellKnown.Jwks.Queries.Get; 2 | 3 | public sealed class GetJwksDto 4 | { 5 | public List Keys { get; set; } = []; 6 | } 7 | 8 | public sealed class Jwk 9 | { 10 | public string Kty { get; set; } = "OKP"; 11 | public string Use { get; set; } = "sig"; 12 | public string Kid { get; set; } = null!; 13 | public string Crv { get; set; } = "Ed25519"; 14 | public string X { get; set; } = null!; 15 | public string Alg { get; set; } = "EdDSA"; 16 | } 17 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.WebApi/Common/FeatureMetric/FeatureMetricOptions.cs: -------------------------------------------------------------------------------- 1 | namespace Digdir.Domain.Dialogporten.WebApi.Common.FeatureMetric; 2 | 3 | /// 4 | /// Configuration options for feature metric tracking. 5 | /// 6 | public sealed class FeatureMetricOptions 7 | { 8 | /// 9 | /// Gets or sets the list of path prefixes to exclude from feature metric tracking. 10 | /// 11 | public List ExcludedPathPrefixes { get; set; } = new() 12 | { 13 | "/health", 14 | "/metrics", 15 | "/swagger", 16 | "/openapi" 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /nginx-webapi.conf: -------------------------------------------------------------------------------- 1 | # this nginx config is for the load balancer: dialogporten-webapi-ingress in docker-compose.yml 2 | events {} 3 | 4 | http { 5 | upstream webapi { 6 | least_conn; 7 | server dialogporten-webapi:8080; 8 | } 9 | 10 | server { 11 | listen 80; 12 | 13 | location / { 14 | proxy_pass http://webapi; 15 | proxy_set_header Host $host; 16 | proxy_set_header X-Real-IP $remote_addr; 17 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 18 | proxy_set_header X-Forwarded-Proto $scheme; 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Common/Context/IApplicationContext.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Domain.Dialogporten.Domain.Common; 2 | 3 | namespace Digdir.Domain.Dialogporten.Application.Common.Context; 4 | 5 | public interface IApplicationContext 6 | { 7 | Dictionary Metadata { get; } 8 | void AddMetadata(string key, string value); 9 | } 10 | 11 | public static class ApplicationContextExtensions 12 | { 13 | public static bool IsSilentUpdate(this IApplicationContext context) => 14 | context.Metadata.TryGetValue(Constants.IsSilentUpdate, out var value) 15 | && value == bool.TrueString; 16 | } 17 | -------------------------------------------------------------------------------- /src/Digdir.Library.Entity.Abstractions/IEntity.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Library.Entity.Abstractions.Features.Creatable; 2 | using Digdir.Library.Entity.Abstractions.Features.Identifiable; 3 | using Digdir.Library.Entity.Abstractions.Features.Updatable; 4 | 5 | namespace Digdir.Library.Entity.Abstractions; 6 | 7 | /// 8 | /// Convenience interface to mark an entity with 9 | /// , 10 | /// , and 11 | /// . 12 | /// 13 | public interface IEntity : 14 | IIdentifiableEntity, 15 | ICreatableEntity, 16 | IUpdateableEntity; 17 | -------------------------------------------------------------------------------- /tests/k6/tests/graphql/performance/graphqlGetAllDialogsForEnduserFTS.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The performance test for GraphQL search. 3 | * Run: k6 run tests/k6/tests/graphql/performance/graphqlGetAllDialogsForEnduserFTS.js --vus 1 --iterations 1 -e env=yt01 4 | */ 5 | 6 | import { _default, getOptions, _setup } from './graphqlCommonFunctions.js'; 7 | 8 | const label = "graphql-getall-dialogs-for-enduser-with-fts"; 9 | const queryType = "getAllDialogsForEnduserFts" 10 | 11 | export const options = getOptions([label]); 12 | export function setup() { return _setup(label, queryType); } 13 | export default function (data) { _default(data); } 14 | -------------------------------------------------------------------------------- /.azure/applications/sync-resource-policy-information-job/prod.bicepparam: -------------------------------------------------------------------------------- 1 | using './main.bicep' 2 | 3 | param environment = 'prod' 4 | param location = 'norwayeast' 5 | param imageTag = readEnvironmentVariable('IMAGE_TAG') 6 | param jobSchedule = '10 3 * * *' // 3:10AM every night 7 | param replicaTimeOutInSeconds = 600 8 | 9 | //secrets 10 | param containerAppEnvironmentName = readEnvironmentVariable('AZURE_CONTAINER_APP_ENVIRONMENT_NAME') 11 | param environmentKeyVaultName = readEnvironmentVariable('AZURE_ENVIRONMENT_KEY_VAULT_NAME') 12 | param appInsightConnectionString = readEnvironmentVariable('AZURE_APP_INSIGHTS_CONNECTION_STRING') 13 | -------------------------------------------------------------------------------- /.azure/applications/sync-resource-policy-information-job/test.bicepparam: -------------------------------------------------------------------------------- 1 | using './main.bicep' 2 | 3 | param environment = 'test' 4 | param location = 'norwayeast' 5 | param imageTag = readEnvironmentVariable('IMAGE_TAG') 6 | param jobSchedule = '20 3 * * *' // 3:20AM every night 7 | param replicaTimeOutInSeconds = 600 8 | 9 | //secrets 10 | param containerAppEnvironmentName = readEnvironmentVariable('AZURE_CONTAINER_APP_ENVIRONMENT_NAME') 11 | param environmentKeyVaultName = readEnvironmentVariable('AZURE_ENVIRONMENT_KEY_VAULT_NAME') 12 | param appInsightConnectionString = readEnvironmentVariable('AZURE_APP_INSIGHTS_CONNECTION_STRING') 13 | -------------------------------------------------------------------------------- /.azure/applications/sync-resource-policy-information-job/yt01.bicepparam: -------------------------------------------------------------------------------- 1 | using './main.bicep' 2 | 3 | param environment = 'yt01' 4 | param location = 'norwayeast' 5 | param imageTag = readEnvironmentVariable('IMAGE_TAG') 6 | param jobSchedule = '25 3 * * *' // 3:25AM every night 7 | param replicaTimeOutInSeconds = 600 8 | 9 | //secrets 10 | param containerAppEnvironmentName = readEnvironmentVariable('AZURE_CONTAINER_APP_ENVIRONMENT_NAME') 11 | param environmentKeyVaultName = readEnvironmentVariable('AZURE_ENVIRONMENT_KEY_VAULT_NAME') 12 | param appInsightConnectionString = readEnvironmentVariable('AZURE_APP_INSIGHTS_CONNECTION_STRING') 13 | -------------------------------------------------------------------------------- /.azure/applications/sync-subject-resource-mappings-job/prod.bicepparam: -------------------------------------------------------------------------------- 1 | using './main.bicep' 2 | 3 | param environment = 'prod' 4 | param location = 'norwayeast' 5 | param imageTag = readEnvironmentVariable('IMAGE_TAG') 6 | param jobSchedule = '*/5 * * * *' // Runs every 5 minutes 7 | param replicaTimeOutInSeconds = 600 8 | 9 | //secrets 10 | param containerAppEnvironmentName = readEnvironmentVariable('AZURE_CONTAINER_APP_ENVIRONMENT_NAME') 11 | param environmentKeyVaultName = readEnvironmentVariable('AZURE_ENVIRONMENT_KEY_VAULT_NAME') 12 | param appInsightConnectionString = readEnvironmentVariable('AZURE_APP_INSIGHTS_CONNECTION_STRING') 13 | -------------------------------------------------------------------------------- /.azure/applications/sync-subject-resource-mappings-job/test.bicepparam: -------------------------------------------------------------------------------- 1 | using './main.bicep' 2 | 3 | param environment = 'test' 4 | param location = 'norwayeast' 5 | param imageTag = readEnvironmentVariable('IMAGE_TAG') 6 | param jobSchedule = '*/5 * * * *' // Runs every 5 minutes 7 | param replicaTimeOutInSeconds = 600 8 | 9 | //secrets 10 | param containerAppEnvironmentName = readEnvironmentVariable('AZURE_CONTAINER_APP_ENVIRONMENT_NAME') 11 | param environmentKeyVaultName = readEnvironmentVariable('AZURE_ENVIRONMENT_KEY_VAULT_NAME') 12 | param appInsightConnectionString = readEnvironmentVariable('AZURE_APP_INSIGHTS_CONNECTION_STRING') 13 | -------------------------------------------------------------------------------- /.azure/applications/sync-subject-resource-mappings-job/yt01.bicepparam: -------------------------------------------------------------------------------- 1 | using './main.bicep' 2 | 3 | param environment = 'yt01' 4 | param location = 'norwayeast' 5 | param imageTag = readEnvironmentVariable('IMAGE_TAG') 6 | param jobSchedule = '*/5 * * * *' // Runs every 5 minutes 7 | param replicaTimeOutInSeconds = 600 8 | 9 | //secrets 10 | param containerAppEnvironmentName = readEnvironmentVariable('AZURE_CONTAINER_APP_ENVIRONMENT_NAME') 11 | param environmentKeyVaultName = readEnvironmentVariable('AZURE_ENVIRONMENT_KEY_VAULT_NAME') 12 | param appInsightConnectionString = readEnvironmentVariable('AZURE_APP_INSIGHTS_CONNECTION_STRING') 13 | -------------------------------------------------------------------------------- /nginx-graphql.conf: -------------------------------------------------------------------------------- 1 | # this nginx config is for the load balancer: dialogporten-graphql-ingress in docker-compose.yml 2 | events {} 3 | 4 | http { 5 | upstream graphql { 6 | least_conn; 7 | server dialogporten-graphql:8080; 8 | } 9 | 10 | server { 11 | listen 80; 12 | 13 | location / { 14 | proxy_pass http://graphql; 15 | proxy_set_header Host $host; 16 | proxy_set_header X-Real-IP $remote_addr; 17 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 18 | proxy_set_header X-Forwarded-Proto $scheme; 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/EndUserContext/Queries/SearchLabelAssignmentLog/LabelAssignmentLogDto.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.Common.Actors; 2 | 3 | namespace Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.EndUserContext.Queries.SearchLabelAssignmentLog; 4 | 5 | public sealed class LabelAssignmentLogDto 6 | { 7 | public DateTimeOffset CreatedAt { get; set; } 8 | 9 | public string Name { get; set; } = null!; 10 | 11 | public string Action { get; set; } = null!; 12 | 13 | public ActorDto PerformedBy { get; set; } = null!; 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Create/Validators/CreateDialogSearchTagDtoValidator.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Domain.Dialogporten.Domain.Common; 2 | using FluentValidation; 3 | 4 | namespace Digdir.Domain.Dialogporten.Application.Features.V1.ServiceOwner.Dialogs.Commands.Create.Validators; 5 | 6 | internal sealed class CreateDialogSearchTagDtoValidator : AbstractValidator 7 | { 8 | public CreateDialogSearchTagDtoValidator() 9 | { 10 | RuleFor(x => x.Value) 11 | .MinimumLength(3) 12 | .MaximumLength(Constants.MaxSearchTagLength); 13 | } 14 | } -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Update/Validators/UpdateDialogSearchTagDtoValidator.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Domain.Dialogporten.Domain.Common; 2 | using FluentValidation; 3 | 4 | namespace Digdir.Domain.Dialogporten.Application.Features.V1.ServiceOwner.Dialogs.Commands.Update.Validators; 5 | 6 | internal sealed class UpdateDialogSearchTagDtoValidator : AbstractValidator 7 | { 8 | public UpdateDialogSearchTagDtoValidator() 9 | { 10 | RuleFor(x => x.Value) 11 | .MinimumLength(3) 12 | .MaximumLength(Constants.MaxSearchTagLength); 13 | } 14 | } -------------------------------------------------------------------------------- /src/Digdir.Library.Entity.Abstractions/IJoinEntity.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Library.Entity.Abstractions.Features.Creatable; 2 | using Digdir.Library.Entity.Abstractions.Features.Updatable; 3 | 4 | namespace Digdir.Library.Entity.Abstractions; 5 | 6 | /// 7 | /// Convenience interface to mark an entity with 8 | /// , and 9 | /// . 10 | /// 11 | /// 12 | /// Usually used by entities that join multiple entities together and thereby has a composite key. 13 | /// 14 | public interface IJoinEntity : 15 | ICreatableEntity, 16 | IUpdateableEntity; 17 | -------------------------------------------------------------------------------- /.azure/applications/sync-resource-policy-information-job/staging.bicepparam: -------------------------------------------------------------------------------- 1 | using './main.bicep' 2 | 3 | param environment = 'staging' 4 | param location = 'norwayeast' 5 | param imageTag = readEnvironmentVariable('IMAGE_TAG') 6 | param jobSchedule = '15 3 * * *' // 3:15AM every night 7 | param replicaTimeOutInSeconds = 600 8 | 9 | //secrets 10 | param containerAppEnvironmentName = readEnvironmentVariable('AZURE_CONTAINER_APP_ENVIRONMENT_NAME') 11 | param environmentKeyVaultName = readEnvironmentVariable('AZURE_ENVIRONMENT_KEY_VAULT_NAME') 12 | param appInsightConnectionString = readEnvironmentVariable('AZURE_APP_INSIGHTS_CONNECTION_STRING') 13 | -------------------------------------------------------------------------------- /.azure/applications/sync-subject-resource-mappings-job/staging.bicepparam: -------------------------------------------------------------------------------- 1 | using './main.bicep' 2 | 3 | param environment = 'staging' 4 | param location = 'norwayeast' 5 | param imageTag = readEnvironmentVariable('IMAGE_TAG') 6 | param jobSchedule = '*/5 * * * *' // Runs every 5 minutes 7 | param replicaTimeOutInSeconds = 600 8 | 9 | //secrets 10 | param containerAppEnvironmentName = readEnvironmentVariable('AZURE_CONTAINER_APP_ENVIRONMENT_NAME') 11 | param environmentKeyVaultName = readEnvironmentVariable('AZURE_ENVIRONMENT_KEY_VAULT_NAME') 12 | param appInsightConnectionString = readEnvironmentVariable('AZURE_APP_INSIGHTS_CONNECTION_STRING') 13 | -------------------------------------------------------------------------------- /docker-compose-jobs.yml: -------------------------------------------------------------------------------- 1 | version: '3.9' 2 | include: 3 | - docker-compose-db-redis.yml 4 | 5 | services: 6 | dialogporten-sync-subject-resource-mappings-job: 7 | build: 8 | context: . 9 | dockerfile: src/Digdir.Domain.Dialogporten.Janitor/Dockerfile 10 | environment: 11 | - Infrastructure:DialogDbConnectionString=${DB_CONNECTION_STRING} 12 | - DOTNET_ENVIRONMENT=Development 13 | command: [ "sync-subject-resource-mappings" ] 14 | depends_on: 15 | dialogporten-postgres: 16 | condition: service_healthy 17 | dialogporten-migrations: 18 | condition: service_completed_successfully 19 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Domain/Dialogs/Entities/Actions/DialogGuiActionPriority.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Library.Entity.Abstractions.Features.Lookup; 2 | 3 | namespace Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actions; 4 | 5 | public sealed class DialogGuiActionPriority : AbstractLookupEntity 6 | { 7 | public DialogGuiActionPriority(Values id) : base(id) { } 8 | public override DialogGuiActionPriority MapValue(Values id) => new(id); 9 | 10 | public enum Values 11 | { 12 | Primary = 1, 13 | Secondary = 2, 14 | Tertiary = 3 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Digdir.Library.Dialogporten.WebApiClient.WebApiSample/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*", 9 | "DialogportenSettings": { 10 | "BaseUri": "https://localhost:7214", 11 | "ThrowOnPublicKeyFetchInit": false, 12 | "Maskinporten": { 13 | "Environment": "test", 14 | "Scope": "digdir:dialogporten.serviceprovider digdir:dialogporten.serviceprovider.search", 15 | "ClientId": "Configure in local secrets", 16 | "EncodedJwk": "Configure in local secrets" 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/SearchActivities/ActivityDto.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities; 2 | 3 | namespace Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.Dialogs.Queries.SearchActivities; 4 | 5 | public sealed class ActivityDto 6 | { 7 | public Guid Id { get; set; } 8 | public DateTimeOffset CreatedAt { get; set; } 9 | public Uri? ExtendedType { get; set; } 10 | public string? SeenByEndUserIdHash { get; set; } 11 | 12 | public DialogActivityType.Values Type { get; set; } 13 | 14 | public Guid? TransmissionId { get; set; } 15 | } 16 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Domain/Actors/Actor.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Library.Entity.Abstractions; 2 | 3 | // ReSharper disable ClassNeverInstantiated.Global 4 | 5 | namespace Digdir.Domain.Dialogporten.Domain.Actors; 6 | 7 | public abstract class Actor : IEntity 8 | { 9 | public Guid Id { get; set; } 10 | public DateTimeOffset CreatedAt { get; set; } 11 | public DateTimeOffset UpdatedAt { get; set; } 12 | public ActorType.Values ActorTypeId { get; set; } 13 | public ActorType ActorType { get; set; } = null!; 14 | 15 | public Guid? ActorNameEntityId { get; set; } 16 | public ActorName? ActorNameEntity { get; set; } = null!; 17 | } 18 | -------------------------------------------------------------------------------- /tests/k6/tests/graphql/performance/graphqlGetAllDialogsForEnduserCount.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The performance test for GraphQL search. 3 | * Run: k6 run tests/k6/tests/graphql/performance/graphqlGetAllDialogsForEnduserCount.js --vus 1 --iterations 1 -e env=yt01 4 | */ 5 | import { _default, getOptions, _setup } from './graphqlCommonFunctions.js'; 6 | 7 | const label = "graphql-getall-dialogs-for-enduser-count"; 8 | const queryType = "getAllDialogsForEnduserCount"; 9 | const labels = [label]; 10 | 11 | export const options = getOptions(labels); 12 | export function setup() { return _setup(label, queryType); } 13 | export default function (data) { _default(data); } 14 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Domain/Actors/ActorName.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Library.Entity.Abstractions.Features.Creatable; 2 | using Digdir.Library.Entity.Abstractions.Features.Identifiable; 3 | using Digdir.Library.Entity.Abstractions.Features.Immutable; 4 | 5 | namespace Digdir.Domain.Dialogporten.Domain.Actors; 6 | 7 | public sealed class ActorName : IImmutableEntity, IIdentifiableEntity, ICreatableEntity 8 | { 9 | public Guid Id { get; set; } 10 | public string? ActorId { get; set; } 11 | public string? Name { get; set; } 12 | public DateTimeOffset CreatedAt { get; set; } 13 | 14 | public List ActorEntities { get; set; } = []; 15 | } 16 | -------------------------------------------------------------------------------- /tests/k6/tests/serviceowner/dialogCreateInvalidProcess.js: -------------------------------------------------------------------------------- 1 | import { describe, expect, expectStatusFor, postSO } from '../../common/testimports.js' 2 | import { default as dialogToInsert } from './testdata/01-create-dialog.js'; 3 | 4 | 5 | export default function (){ 6 | 7 | describe ('Attempt to create dialog with invalid URI', () => { 8 | let dialog = dialogToInsert(); 9 | dialog.process = 'inval|d'; 10 | let r = postSO('dialogs', dialog) 11 | expectStatusFor(r).to.equal(400); 12 | expect(r, 'response').to.have.validJsonBody(); 13 | expect(r.json(), 'response body').to.have.property('errors'); 14 | }) 15 | } 16 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Queries/GetActivity/MappingProfile.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities; 3 | 4 | namespace Digdir.Domain.Dialogporten.Application.Features.V1.ServiceOwner.Dialogs.Queries.GetActivity; 5 | 6 | public sealed class MappingProfile : Profile 7 | { 8 | public MappingProfile() 9 | { 10 | CreateMap() 11 | .ForMember(dest => dest.Type, opt => opt.MapFrom(src => src.TypeId)) 12 | .ForMember(dest => dest.DeletedAt, opt => opt.MapFrom(src => src.Dialog.DeletedAt)); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Queries/SearchActivities/ActivityDto.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities; 2 | 3 | namespace Digdir.Domain.Dialogporten.Application.Features.V1.ServiceOwner.Dialogs.Queries.SearchActivities; 4 | 5 | public sealed class ActivityDto 6 | { 7 | public Guid Id { get; set; } 8 | public DateTimeOffset CreatedAt { get; set; } 9 | public Uri? ExtendedType { get; set; } 10 | 11 | public DialogActivityType.Values Type { get; set; } 12 | 13 | public DateTimeOffset? DeletedAt { get; set; } 14 | 15 | public Guid? TransmissionId { get; set; } 16 | } 17 | -------------------------------------------------------------------------------- /src/Digdir.Tool.Dialogporten.LargeDataSetGenerator/service_resources: -------------------------------------------------------------------------------- 1 | urn:altinn:resource:ttd-dialogporten-performance-test-01 2 | urn:altinn:resource:ttd-dialogporten-performance-test-02 3 | urn:altinn:resource:ttd-dialogporten-performance-test-03 4 | urn:altinn:resource:ttd-dialogporten-performance-test-04 5 | urn:altinn:resource:ttd-dialogporten-performance-test-05 6 | urn:altinn:resource:ttd-dialogporten-performance-test-06 7 | urn:altinn:resource:ttd-dialogporten-performance-test-07 8 | urn:altinn:resource:ttd-dialogporten-performance-test-08 9 | urn:altinn:resource:ttd-dialogporten-performance-test-09 10 | urn:altinn:resource:ttd-dialogporten-performance-test-10 11 | -------------------------------------------------------------------------------- /.azure/modules/privateDnsZoneGroup/main.bicep: -------------------------------------------------------------------------------- 1 | param dnsZoneId string 2 | param privateEndpointName string 3 | param name string 4 | param dnsZoneGroupName string 5 | 6 | resource privateEndpoint 'Microsoft.Network/privateEndpoints@2024-05-01' existing = { 7 | name: privateEndpointName 8 | } 9 | 10 | resource pe_dns_zone_group 'Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2024-05-01' = { 11 | name: name 12 | parent: privateEndpoint 13 | properties: { 14 | privateDnsZoneConfigs: [ 15 | { 16 | name: dnsZoneGroupName 17 | properties: { 18 | privateDnsZoneId: dnsZoneId 19 | } 20 | } 21 | ] 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0 5 | enable 6 | enable 7 | 750256a4-f332-4783-8802-8a7d9566f9ca 8 | 9 | 10 | 11 | true 12 | true 13 | Recommended 14 | true 15 | 8 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Queries/SearchActivities/MappingProfile.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities; 3 | 4 | namespace Digdir.Domain.Dialogporten.Application.Features.V1.ServiceOwner.Dialogs.Queries.SearchActivities; 5 | 6 | public sealed class MappingProfile : Profile 7 | { 8 | public MappingProfile() 9 | { 10 | CreateMap() 11 | .ForMember(dest => dest.Type, opt => opt.MapFrom(src => src.TypeId)) 12 | .ForMember(dest => dest.DeletedAt, opt => opt.MapFrom(src => src.Dialog.DeletedAt)); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Description 4 | 5 | 6 | 7 | ## Related Issue(s) 8 | 9 | - #{issue number} 10 | 11 | ## Verification 12 | 13 | - [ ] **Your** code builds clean without any errors or warnings 14 | - [ ] Manual testing done (required) 15 | - [ ] Relevant automated test added (if you find this hard, leave it and we'll help out) 16 | 17 | ## Documentation 18 | 19 | - [ ] Documentation is updated (either in `docs`-directory, Altinnpedia or a separate linked PR in [altinn-studio-docs.](https://github.com/Altinn/altinn-studio-docs), if applicable) 20 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Infrastructure/Persistence/Configurations/Actors/ActorNameConfiguration.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Domain.Dialogporten.Domain.Actors; 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 4 | 5 | namespace Digdir.Domain.Dialogporten.Infrastructure.Persistence.Configurations.Actors; 6 | 7 | internal sealed class ActorNameConfiguration : IEntityTypeConfiguration 8 | { 9 | public void Configure(EntityTypeBuilder builder) 10 | { 11 | builder 12 | .HasIndex(x => new { x.ActorId, x.Name }) 13 | .IsUnique() 14 | .AreNullsDistinct(false); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Domain/Http/HttpVerb.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Library.Entity.Abstractions.Features.Lookup; 2 | // ReSharper disable InconsistentNaming 3 | 4 | namespace Digdir.Domain.Dialogporten.Domain.Http; 5 | 6 | public sealed class HttpVerb : AbstractLookupEntity 7 | { 8 | public HttpVerb(Values id) : base(id) { } 9 | public override HttpVerb MapValue(Values id) => new(id); 10 | 11 | public enum Values 12 | { 13 | GET = 1, 14 | POST = 2, 15 | PUT = 3, 16 | PATCH = 4, 17 | DELETE = 5, 18 | HEAD = 6, 19 | OPTIONS = 7, 20 | TRACE = 8, 21 | CONNECT = 9 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Infrastructure/Persistence/Configurations/DialogServiceOwnerContexts/DialogServiceOwnerContextConfiguration.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Domain.Dialogporten.Domain.DialogServiceOwnerContexts.Entities; 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 4 | 5 | namespace Digdir.Domain.Dialogporten.Infrastructure.Persistence.Configurations.DialogServiceOwnerContexts; 6 | 7 | internal sealed class DialogServiceOwnerContextConfiguration : IEntityTypeConfiguration 8 | { 9 | public void Configure(EntityTypeBuilder builder) 10 | => builder.HasKey(x => x.DialogId); 11 | } 12 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Infrastructure/Persistence/Sql/Dialog/Search/Function.UpsertDialogSearchOne.sql: -------------------------------------------------------------------------------- 1 | -- Refreshes a single dialog's search vector by reusing the canonical aggregated document view. 2 | CREATE OR REPLACE FUNCTION search."UpsertDialogSearchOne"(p_dialog_id uuid) 3 | RETURNS void 4 | LANGUAGE sql 5 | AS $$ 6 | INSERT INTO search."DialogSearch" ("DialogId","UpdatedAt","SearchVector") 7 | SELECT "DialogId", now(), COALESCE("Document",''::tsvector) 8 | FROM search."VDialogDocument" 9 | WHERE "DialogId" = p_dialog_id 10 | ON CONFLICT ("DialogId") DO UPDATE 11 | SET "UpdatedAt" = EXCLUDED."UpdatedAt", 12 | "SearchVector" = EXCLUDED."SearchVector"; 13 | $$; 14 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Domain/DialogEndUserContexts/Entities/DialogEndUserContextSystemLabel.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Library.Entity.Abstractions.Features.Creatable; 2 | 3 | namespace Digdir.Domain.Dialogporten.Domain.DialogEndUserContexts.Entities; 4 | 5 | public sealed class DialogEndUserContextSystemLabel : ICreatableEntity 6 | { 7 | public SystemLabel.Values SystemLabelId { get; internal set; } = SystemLabel.Values.Default; 8 | public SystemLabel SystemLabel { get; private set; } = null!; 9 | 10 | public Guid DialogEndUserContextId { get; set; } 11 | public DialogEndUserContext DialogEndUserContext { get; set; } = null!; 12 | 13 | public DateTimeOffset CreatedAt { get; set; } 14 | } 15 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Infrastructure/Persistence/Configurations/SubjectResources/SubjectResourceConfiguration.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Domain.Dialogporten.Domain.SubjectResources; 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 4 | 5 | namespace Digdir.Domain.Dialogporten.Infrastructure.Persistence.Configurations.SubjectResources; 6 | 7 | internal sealed class SubjectResourceConfiguration : IEntityTypeConfiguration 8 | { 9 | public void Configure(EntityTypeBuilder builder) 10 | { 11 | builder 12 | .HasIndex(sr => new { sr.Resource, sr.Subject }) 13 | .IsUnique(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AllowAnonymousHandler.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authorization; 2 | 3 | namespace Digdir.Domain.Dialogporten.GraphQL.Common.Authorization; 4 | 5 | /// 6 | /// This authorization handler will bypass all requirements 7 | /// 8 | public sealed class AllowAnonymousHandler : IAuthorizationHandler 9 | { 10 | public Task HandleAsync(AuthorizationHandlerContext context) 11 | { 12 | foreach (var requirement in context.PendingRequirements) 13 | { 14 | //Simply pass all requirements 15 | context.Succeed(requirement); 16 | } 17 | 18 | return Task.CompletedTask; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.WebApi/Common/Authorization/AllowAnonymousHandler.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authorization; 2 | 3 | namespace Digdir.Domain.Dialogporten.WebApi.Common.Authorization; 4 | 5 | /// 6 | /// This authorisation handler will bypass all requirements 7 | /// 8 | public sealed class AllowAnonymousHandler : IAuthorizationHandler 9 | { 10 | public Task HandleAsync(AuthorizationHandlerContext context) 11 | { 12 | foreach (var requirement in context.PendingRequirements) 13 | { 14 | //Simply pass all requirements 15 | context.Succeed(requirement); 16 | } 17 | 18 | return Task.CompletedTask; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Infrastructure/GraphQL/DummyRequestExecutorBuilder.cs: -------------------------------------------------------------------------------- 1 | using HotChocolate.Execution.Configuration; 2 | using Microsoft.Extensions.DependencyInjection; 3 | 4 | namespace Digdir.Domain.Dialogporten.Infrastructure.GraphQL; 5 | 6 | /// 7 | /// This implementation is a workaround to allow the use of the AddRedisSubscriptions extension method 8 | /// from HotChocolate.Subscriptions.Redis without having to take the entire HotChocolate library as a dependency. 9 | /// 10 | internal sealed class DummyRequestExecutorBuilder : IRequestExecutorBuilder 11 | { 12 | public string Name => string.Empty; 13 | public IServiceCollection Services { get; init; } = null!; 14 | } 15 | -------------------------------------------------------------------------------- /tests/k6/suites/all-single-pass.js: -------------------------------------------------------------------------------- 1 | import { runAllTests } from "../tests/all-tests.js"; 2 | import { default as summary } from "../common/summary.js"; 3 | import { chai, describe } from '../common/testimports.js' 4 | 5 | export let options = {}; 6 | 7 | export default function () { 8 | try { 9 | runAllTests(); 10 | } catch (error) { 11 | describe('Exception during test suite', () => { 12 | // disable truncating so we can display the entire stack trace 13 | chai.config.truncateThreshold = 0; 14 | chai.assert(true == false, error.stack); 15 | }); 16 | } 17 | } 18 | 19 | export function handleSummary(data) { 20 | return summary(data); 21 | } 22 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Features/V1/Common/Content/ContentValueDto.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Domain.Dialogporten.Application.Features.V1.Common.Localizations; 2 | using Digdir.Domain.Dialogporten.Domain; 3 | 4 | namespace Digdir.Domain.Dialogporten.Application.Features.V1.Common.Content; 5 | 6 | public sealed class ContentValueDto 7 | { 8 | /// 9 | /// A list of localizations for the content. 10 | /// 11 | public List Value { get; set; } = []; 12 | 13 | /// 14 | /// Media type of the content, this can also indicate that the content is embeddable. 15 | /// 16 | public string MediaType { get; set; } = MediaTypes.PlainText; 17 | } 18 | -------------------------------------------------------------------------------- /tests/Digdir.Domain.Dialogporten.Application.Unit.Tests/Features/V1/Common/Utils/ApplicationEventHandlerUtilsTests.Developer_Should_Use_Caution_When_Modifying_Endpoints.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | DialogEventToAltinnForwarder_DialogActivityCreatedDomainEvent, 3 | DialogEventToAltinnForwarder_DialogCreatedDomainEvent, 4 | DialogEventToAltinnForwarder_DialogDeletedDomainEvent, 5 | DialogEventToAltinnForwarder_DialogRestoredDomainEvent, 6 | DialogEventToAltinnForwarder_DialogSeenDomainEvent, 7 | DialogEventToAltinnForwarder_DialogTransmissionCreatedDomainEvent, 8 | DialogEventToAltinnForwarder_DialogUpdatedDomainEvent, 9 | DialogSearchIndexer_DialogCreatedDomainEvent, 10 | DialogSearchIndexer_DialogUpdatedDomainEvent 11 | ] 12 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Domain/Common/Constants.cs: -------------------------------------------------------------------------------- 1 | namespace Digdir.Domain.Dialogporten.Domain.Common; 2 | 3 | public static class Constants 4 | { 5 | public const int MinSearchStringLength = 3; 6 | public const int MaxSearchTagLength = 63; 7 | public const int DefaultMaxStringLength = 255; 8 | public const int DefaultMaxUriLength = 1023; 9 | public const int CorrespondenceMaxStringLength = 512; 10 | 11 | public const string ServiceResourcePrefix = "urn:altinn:resource:"; 12 | public const string AppResourceIdPrefix = "app_"; 13 | public const string ServiceContextInstanceIdPrefix = "urn:altinn:integration:storage:"; 14 | 15 | public const string IsSilentUpdate = "IsSilentUpdate"; 16 | } 17 | -------------------------------------------------------------------------------- /src/Digdir.Library.Dialogporten.WebApiClient/.refitter: -------------------------------------------------------------------------------- 1 | { 2 | "openApiPath": "../Digdir.Domain.Dialogporten.WebApi/bin/Release/net9.0/swagger.json", 3 | "namespace": "Altinn.ApiClients.Dialogporten.Features.V1", 4 | "outputFolder": "Features/V1", 5 | "operationNameGenerator": "SingleClientFromOperationId", 6 | "trimUnusedSchema": true, 7 | "multipleInterfaces": "ByTag", 8 | "includeTags": [ 9 | "Serviceowner" 10 | ], 11 | "useCancellationTokens": true, 12 | "returnIApiResponse": true, 13 | "useDynamicQuerystringParameters": true, 14 | "outputFilename": "RefitterInterface.cs", 15 | "codeGeneratorSettings": { 16 | "dateFormat": "yyyy-MM-ddTHH:mm:ssZ" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/k6/tests/graphql/performance/graphqlGetAllDialogsForParties.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The performance test for GraphQL search. 3 | * Run: k6 run tests/k6/tests/graphql/performance/graphqlGetAllDialogsForParties.js --vus 1 --iterations 1 -e env=yt01 4 | */ 5 | import { getOptions, _setup, _defaultForParties } from './graphqlCommonFunctions.js'; 6 | const dialogs_label = "graphql-getall-dialogs-for-parties"; 7 | const parties_label = "graphql-get-parties"; 8 | const queryType = "getAllDialogsForParties" 9 | const labels = [dialogs_label, parties_label]; 10 | 11 | export const options = getOptions(labels); 12 | export function setup() { return _setup(dialogs_label, queryType); } 13 | export default function (data) { _defaultForParties(data); } -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Create/Validators/CreateDialogServiceOwnerLabelDtoValidator.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Domain.Dialogporten.Domain.Common; 2 | using FluentValidation; 3 | 4 | namespace Digdir.Domain.Dialogporten.Application.Features.V1.ServiceOwner.Dialogs.Commands.Create.Validators; 5 | 6 | internal sealed class CreateDialogServiceOwnerLabelDtoValidator : AbstractValidator 7 | { 8 | public CreateDialogServiceOwnerLabelDtoValidator() 9 | { 10 | RuleFor(x => x.Value) 11 | .NotEmpty() 12 | .MinimumLength(Constants.MinSearchStringLength) 13 | .MaximumLength(Constants.DefaultMaxStringLength); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.WebApi/Endpoints/V1/EndUser/AccessManagement/Queries/GetParties/GetPartiesEndpointSummary.cs: -------------------------------------------------------------------------------- 1 | using FastEndpoints; 2 | 3 | namespace Digdir.Domain.Dialogporten.WebApi.Endpoints.V1.EndUser.AccessManagement.Queries.GetParties; 4 | 5 | public sealed class GetPartiesEndpointSummary : Summary 6 | { 7 | public GetPartiesEndpointSummary() 8 | { 9 | Summary = "Gets the list of authorized parties for the end user"; 10 | Description = """ 11 | Gets the list of authorized parties for the end user. 12 | """; 13 | 14 | Responses[StatusCodes.Status200OK] = "The list of authorized parties for the end user"; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Externals/AltinnAuthorization/ActionResource.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Domain.Dialogporten.Application.Common.Authorization; 2 | 3 | namespace Digdir.Domain.Dialogporten.Application.Externals.AltinnAuthorization; 4 | 5 | public sealed record AltinnAction 6 | { 7 | public string Name { get; } 8 | public string AuthorizationAttribute { get; } 9 | 10 | public AltinnAction(string name, string? authorizationAttribute = null) 11 | { 12 | Name = name; 13 | AuthorizationAttribute = authorizationAttribute ?? Constants.MainResource; 14 | } 15 | 16 | public void Deconstruct(out string key, out string value) 17 | => (key, value) = (Name, AuthorizationAttribute); 18 | } 19 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Infrastructure/Persistence/Configurations/DialogEndUserContexts/DialogEndUserContextSystemLabelConfiguration.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Domain.Dialogporten.Domain.DialogEndUserContexts.Entities; 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 4 | 5 | namespace Digdir.Domain.Dialogporten.Infrastructure.Persistence.Configurations.DialogEndUserContexts; 6 | 7 | internal sealed class DialogEndUserContextSystemLabelConfiguration : IEntityTypeConfiguration 8 | { 9 | public void Configure(EntityTypeBuilder builder) => 10 | builder.HasKey(x => new { x.DialogEndUserContextId, x.SystemLabelId }); 11 | } 12 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Infrastructure/Persistence/Configurations/ResourcePolicyInformation/ResourcePolicyInformationConfiguration.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 3 | 4 | namespace Digdir.Domain.Dialogporten.Infrastructure.Persistence.Configurations.ResourcePolicyInformation; 5 | 6 | internal sealed class ResourcePolicyInformationConfiguration : IEntityTypeConfiguration 7 | { 8 | public void Configure(EntityTypeBuilder builder) 9 | { 10 | builder 11 | .HasIndex(r => r.Resource) 12 | .IsUnique(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tests/Digdir.Domain.Dialogporten.Application.Integration.Tests/Common/TestDomainEventConsumer.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Domain.Dialogporten.Domain.Common.EventPublisher; 2 | using MassTransit; 3 | using MediatR; 4 | 5 | namespace Digdir.Domain.Dialogporten.Application.Integration.Tests.Common; 6 | 7 | internal sealed class TestDomainEventConsumer : IConsumer 8 | where T : class, IDomainEvent 9 | { 10 | private readonly IPublisher _publisher; 11 | 12 | public TestDomainEventConsumer(IPublisher publisher) 13 | { 14 | _publisher = publisher ?? throw new ArgumentNullException(nameof(publisher)); 15 | } 16 | 17 | public async Task Consume(ConsumeContext context) => await _publisher.Publish(context.Message); 18 | } 19 | -------------------------------------------------------------------------------- /tests/k6/common/console.js: -------------------------------------------------------------------------------- 1 | var originalLog = console.log; 2 | 3 | function customDir(obj) { 4 | var seen = []; 5 | 6 | var replacer = function(key, value) { 7 | if (typeof value === "object" && value !== null) { 8 | if (seen.indexOf(value) !== -1) { 9 | return "[Circular]"; 10 | } 11 | seen.push(value); 12 | } 13 | return value; 14 | }; 15 | 16 | var stringified = JSON.stringify(obj, replacer, 2); 17 | originalLog(stringified); 18 | } 19 | 20 | export var customConsole = { 21 | log: originalLog, 22 | dir: customDir, 23 | // You can manually add other console methods if needed, such as warn, error, etc. 24 | warn: console.warn, 25 | error: console.error 26 | }; 27 | -------------------------------------------------------------------------------- /.azure/applications/graphql/test.bicepparam: -------------------------------------------------------------------------------- 1 | using './main.bicep' 2 | 3 | param environment = 'test' 4 | param location = 'norwayeast' 5 | param apimIp = '51.120.88.69' 6 | param imageTag = readEnvironmentVariable('IMAGE_TAG') 7 | param revisionSuffix = readEnvironmentVariable('REVISION_SUFFIX') 8 | 9 | param otelTraceSamplerRatio = '1' 10 | 11 | // secrets 12 | param environmentKeyVaultName = readEnvironmentVariable('AZURE_ENVIRONMENT_KEY_VAULT_NAME') 13 | param containerAppEnvironmentName = readEnvironmentVariable('AZURE_CONTAINER_APP_ENVIRONMENT_NAME') 14 | param appInsightConnectionString = readEnvironmentVariable('AZURE_APP_INSIGHTS_CONNECTION_STRING') 15 | param appConfigurationName = readEnvironmentVariable('AZURE_APP_CONFIGURATION_NAME') 16 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Infrastructure/Persistence/Configurations/Dialogs/Activities/DialogActivityConfiguration.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities; 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 4 | 5 | namespace Digdir.Domain.Dialogporten.Infrastructure.Persistence.Configurations.Dialogs.Activities; 6 | 7 | internal sealed class DialogActivityConfiguration : IEntityTypeConfiguration 8 | { 9 | public void Configure(EntityTypeBuilder builder) 10 | { 11 | builder.HasOne(x => x.Transmission) 12 | .WithMany(x => x.Activities) 13 | .OnDelete(DeleteBehavior.SetNull); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Infrastructure/Persistence/Sql/Dialog/Search/View.DialogSearchRebuildProgress.sql: -------------------------------------------------------------------------------- 1 | -- Summarises queue state for dashboards and periodic progress logs. 2 | CREATE OR REPLACE VIEW search."DialogSearchRebuildProgress" AS 3 | SELECT 4 | count(*) AS "Total", 5 | count(*) FILTER (WHERE "Status" = 0) AS "Pending", 6 | count(*) FILTER (WHERE "Status" = 1) AS "Processing", 7 | count(*) FILTER (WHERE "Status" = 2) AS "Done", 8 | (count(*) FILTER (WHERE "Status" = 2))::numeric 9 | / NULLIF(count(*), 0) AS "DoneRatio" -- Handy for log formatting and monitoring thresholds. 10 | FROM search."DialogSearchRebuildQueue"; 11 | -------------------------------------------------------------------------------- /src/Digdir.Tool.Dialogporten.LargeDataSetGenerator/Words.cs: -------------------------------------------------------------------------------- 1 | namespace Digdir.Tool.Dialogporten.LargeDataSetGenerator; 2 | 3 | internal static class Words 4 | { 5 | internal static readonly string[] 6 | English = File.Exists("./wordlist_en") ? File.ReadAllLines("./wordlist_en") : []; 7 | 8 | internal static readonly string[] 9 | Norwegian = File.Exists("./wordlist_no") ? File.ReadAllLines("./wordlist_no") : []; 10 | 11 | static Words() 12 | { 13 | if (English.Length > Norwegian.Length) 14 | { 15 | English = English.Except(Norwegian).ToArray(); 16 | } 17 | else 18 | { 19 | Norwegian = Norwegian.Except(English).ToArray(); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/k6/tests/graphql/performance/graphqlGetAllDialogsForPartiesFTS.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The performance test for GraphQL search. 3 | * Run: k6 run tests/k6/tests/graphql/performance/graphqlGetAllDialogsForPartiesFTS.js --vus 1 --iterations 1 -e env=yt01 4 | */ 5 | import { getOptions, _setup, _defaultForParties } from './graphqlCommonFunctions.js'; 6 | const dialogs_label = "graphql-getall-dialogs-for-parties-fts"; 7 | const parties_label = "graphql-get-parties"; 8 | const queryType = "getAllDialogsForPartiesFts" 9 | const labels = [dialogs_label, parties_label]; 10 | 11 | export const options = getOptions(labels); 12 | export function setup() { return _setup(dialogs_label, queryType); } 13 | export default function (data) { _defaultForParties(data); } -------------------------------------------------------------------------------- /tests/k6/tests/graphql/performance/graphqlGetAllDialogsForRandomParty.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The performance test for GraphQL search. 3 | * Run: k6 run tests/k6/tests/graphql/performance/graphqlGetAllDialogsForRandomParty.js --vus 1 --iterations 1 -e env=yt01 4 | */ 5 | import { getOptions, _setup, _defaultForParties } from './graphqlCommonFunctions.js'; 6 | const dialogs_label = "graphql-getall-dialogs-for-parties"; 7 | const parties_label = "graphql-get-parties"; 8 | const queryType = "getAllDialogsForParties" 9 | const labels = [dialogs_label, parties_label]; 10 | 11 | export const options = getOptions(labels); 12 | export function setup() { return _setup(dialogs_label, queryType); } 13 | export default function (data) { _defaultForParties(data, false); } -------------------------------------------------------------------------------- /.azure/applications/graphql/staging.bicepparam: -------------------------------------------------------------------------------- 1 | using './main.bicep' 2 | 3 | param environment = 'staging' 4 | param location = 'norwayeast' 5 | param apimIp = '51.13.86.131' 6 | param imageTag = readEnvironmentVariable('IMAGE_TAG') 7 | param revisionSuffix = readEnvironmentVariable('REVISION_SUFFIX') 8 | 9 | param otelTraceSamplerRatio = '1' 10 | 11 | // secrets 12 | param environmentKeyVaultName = readEnvironmentVariable('AZURE_ENVIRONMENT_KEY_VAULT_NAME') 13 | param containerAppEnvironmentName = readEnvironmentVariable('AZURE_CONTAINER_APP_ENVIRONMENT_NAME') 14 | param appInsightConnectionString = readEnvironmentVariable('AZURE_APP_INSIGHTS_CONNECTION_STRING') 15 | param appConfigurationName = readEnvironmentVariable('AZURE_APP_CONFIGURATION_NAME') 16 | -------------------------------------------------------------------------------- /.azure/applications/web-api-eu/test.bicepparam: -------------------------------------------------------------------------------- 1 | using './main.bicep' 2 | 3 | param environment = 'test' 4 | param location = 'norwayeast' 5 | param apimIp = '51.120.88.69' 6 | param imageTag = readEnvironmentVariable('IMAGE_TAG') 7 | param revisionSuffix = readEnvironmentVariable('REVISION_SUFFIX') 8 | 9 | param otelTraceSamplerRatio = '1' 10 | 11 | // secrets 12 | param environmentKeyVaultName = readEnvironmentVariable('AZURE_ENVIRONMENT_KEY_VAULT_NAME') 13 | param containerAppEnvironmentName = readEnvironmentVariable('AZURE_CONTAINER_APP_ENVIRONMENT_NAME') 14 | param appInsightConnectionString = readEnvironmentVariable('AZURE_APP_INSIGHTS_CONNECTION_STRING') 15 | param appConfigurationName = readEnvironmentVariable('AZURE_APP_CONFIGURATION_NAME') 16 | -------------------------------------------------------------------------------- /.azure/applications/web-api-so/test.bicepparam: -------------------------------------------------------------------------------- 1 | using './main.bicep' 2 | 3 | param environment = 'test' 4 | param location = 'norwayeast' 5 | param apimIp = '51.120.88.69' 6 | param imageTag = readEnvironmentVariable('IMAGE_TAG') 7 | param revisionSuffix = readEnvironmentVariable('REVISION_SUFFIX') 8 | 9 | param otelTraceSamplerRatio = '1' 10 | 11 | // secrets 12 | param environmentKeyVaultName = readEnvironmentVariable('AZURE_ENVIRONMENT_KEY_VAULT_NAME') 13 | param containerAppEnvironmentName = readEnvironmentVariable('AZURE_CONTAINER_APP_ENVIRONMENT_NAME') 14 | param appInsightConnectionString = readEnvironmentVariable('AZURE_APP_INSIGHTS_CONNECTION_STRING') 15 | param appConfigurationName = readEnvironmentVariable('AZURE_APP_CONFIGURATION_NAME') 16 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.GraphQL/Common/ApplicationUser.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Claims; 2 | using Digdir.Domain.Dialogporten.Application.Externals.Presentation; 3 | 4 | namespace Digdir.Domain.Dialogporten.GraphQL.Common; 5 | 6 | internal sealed class ApplicationUser : IUser 7 | { 8 | private readonly IHttpContextAccessor _httpContextAccessor; 9 | 10 | public ApplicationUser(IHttpContextAccessor httpContextAccessor) 11 | { 12 | _httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor)); 13 | } 14 | 15 | public ClaimsPrincipal GetPrincipal() 16 | => _httpContextAccessor.HttpContext?.User ?? throw new InvalidOperationException("No user principal found"); 17 | } 18 | -------------------------------------------------------------------------------- /.azure/applications/web-api-eu/staging.bicepparam: -------------------------------------------------------------------------------- 1 | using './main.bicep' 2 | 3 | param environment = 'staging' 4 | param location = 'norwayeast' 5 | param apimIp = '51.13.86.131' 6 | param imageTag = readEnvironmentVariable('IMAGE_TAG') 7 | param revisionSuffix = readEnvironmentVariable('REVISION_SUFFIX') 8 | 9 | param otelTraceSamplerRatio = '1' 10 | 11 | // secrets 12 | param environmentKeyVaultName = readEnvironmentVariable('AZURE_ENVIRONMENT_KEY_VAULT_NAME') 13 | param containerAppEnvironmentName = readEnvironmentVariable('AZURE_CONTAINER_APP_ENVIRONMENT_NAME') 14 | param appInsightConnectionString = readEnvironmentVariable('AZURE_APP_INSIGHTS_CONNECTION_STRING') 15 | param appConfigurationName = readEnvironmentVariable('AZURE_APP_CONFIGURATION_NAME') 16 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Common/Pagination/Order/Order.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Domain.Dialogporten.Application.Common.Pagination.OrderOption; 2 | 3 | namespace Digdir.Domain.Dialogporten.Application.Common.Pagination.Order; 4 | 5 | public sealed class Order 6 | { 7 | private readonly OrderSelector _selector; 8 | public string Key { get; } 9 | public OrderDirection Direction { get; } 10 | 11 | public Order(string key, OrderSelector selector, OrderDirection direction = PaginationConstants.DefaultOrderDirection) 12 | { 13 | _selector = selector; 14 | Key = key; 15 | Direction = direction; 16 | } 17 | 18 | public OrderSelector GetSelector() => _selector; 19 | } 20 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Infrastructure/Persistence/Sql/Dialog/Search/Function.UpsertDialogSearchOne.V2.sql: -------------------------------------------------------------------------------- 1 | -- Refreshes a single dialog's search vector by reusing the canonical aggregated document view. 2 | CREATE OR REPLACE FUNCTION search."UpsertDialogSearchOne"(p_dialog_id uuid) 3 | RETURNS void 4 | LANGUAGE sql 5 | AS $$ 6 | INSERT INTO search."DialogSearch" ("DialogId","UpdatedAt","Party","SearchVector") 7 | SELECT "DialogId", now(), "Party", COALESCE("Document",''::tsvector) 8 | FROM search."VDialogDocument" 9 | WHERE "DialogId" = p_dialog_id 10 | ON CONFLICT ("DialogId") DO UPDATE 11 | SET "UpdatedAt" = EXCLUDED."UpdatedAt", 12 | "Party" = EXCLUDED."Party", 13 | "SearchVector" = EXCLUDED."SearchVector"; 14 | $$; 15 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.WebApi/Common/WebApiSettings.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Domain.Dialogporten.WebApi.Common.Authentication; 2 | using FluentValidation; 3 | 4 | namespace Digdir.Domain.Dialogporten.WebApi.Common; 5 | 6 | public sealed class WebApiSettings 7 | { 8 | public const string SectionName = "WebApi"; 9 | 10 | public required AuthenticationOptions Authentication { get; init; } 11 | } 12 | 13 | internal sealed class WebApiOptionsValidator : AbstractValidator 14 | { 15 | public WebApiOptionsValidator( 16 | IValidator authenticationOptionsValidator) 17 | { 18 | RuleFor(x => x.Authentication) 19 | .SetValidator(authenticationOptionsValidator); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug 3 | about: If you find something that is not working as it should. 4 | title: '' 5 | type: Bug 6 | assignees: '' 7 | --- 8 | 9 | ### Description 10 | A short and concise description of the error 11 | 12 | ### Reproduction 13 | Step-by-step instructions to reproduce the error, with information about the requests that were made (URLs with parameters, methods, headers (no tokens!), including any DTOs used if POST/PUT/PATCH) 14 | 15 | ### Expected behavior 16 | A short and concise description of what you expected to happen 17 | 18 | ### Actual behavior 19 | A short and concise description of what actually happened 20 | 21 | ### Additional information 22 | If there is additional context that is relevant to include. 23 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Service/Digdir.Domain.Dialogporten.Service.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /tests/k6/tests/graphql/performance/graphqlGetAllDialogsForPartiesCount.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The performance test for GraphQL search. 3 | * Run: k6 run tests/k6/tests/graphql/performance/graphqlGetAllDialogsForPartiesCount.js --vus 1 --iterations 1 -e env=yt01 4 | */ 5 | import { getOptions, _setup, _defaultForParties } from './graphqlCommonFunctions.js'; 6 | const dialogs_label = "graphql-getall-dialogs-for-parties-for-count"; 7 | const parties_label = "graphql-get-parties"; 8 | const queryType = "getAllDialogsForPartiesForCount" 9 | const labels = [dialogs_label, parties_label]; 10 | 11 | export const options = getOptions(labels); 12 | export function setup() { return _setup(dialogs_label, queryType); } 13 | export default function (data) { _defaultForParties(data); } -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.WebApi/Common/ApplicationUser.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Domain.Dialogporten.Application.Externals.Presentation; 2 | using System.Security.Claims; 3 | 4 | namespace Digdir.Domain.Dialogporten.WebApi.Common; 5 | 6 | internal sealed class ApplicationUser : IUser 7 | { 8 | private readonly IHttpContextAccessor _httpContextAccessor; 9 | 10 | public ApplicationUser(IHttpContextAccessor httpContextAccessor) 11 | { 12 | _httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor)); 13 | } 14 | 15 | public ClaimsPrincipal GetPrincipal() 16 | => _httpContextAccessor.HttpContext?.User ?? 17 | throw new InvalidOperationException("No user principal found"); 18 | } 19 | -------------------------------------------------------------------------------- /.azure/applications/aggregate-cost-metrics-job/prod.bicepparam: -------------------------------------------------------------------------------- 1 | using './main.bicep' 2 | 3 | param environment = 'prod' 4 | param location = 'norwayeast' 5 | param imageTag = readEnvironmentVariable('IMAGE_TAG') 6 | param jobSchedule = '0 2 * * *' // 2:00 AM UTC every day 7 | param replicaTimeOutInSeconds = 1800 // 30 minutes 8 | param storageContainerName = 'costmetrics' 9 | 10 | //secrets 11 | param containerAppEnvironmentName = readEnvironmentVariable('AZURE_CONTAINER_APP_ENVIRONMENT_NAME') 12 | param appInsightConnectionString = readEnvironmentVariable('AZURE_APP_INSIGHTS_CONNECTION_STRING') 13 | param azureSubscriptionId = readEnvironmentVariable('AZURE_SUBSCRIPTION_ID') 14 | param environmentKeyVaultName = readEnvironmentVariable('AZURE_ENVIRONMENT_KEY_VAULT_NAME') 15 | -------------------------------------------------------------------------------- /tests/Digdir.Domain.Dialogporten.Application.Unit.Tests/Features/V1/Common/Utils/ApplicationEventHandlerUtilsTests.Developer_Should_Use_Caution_When_Modifying_Events.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | Digdir.Domain.Dialogporten.Domain.Dialogs.Events.Activities.DialogActivityCreatedDomainEvent, 3 | Digdir.Domain.Dialogporten.Domain.Dialogs.Events.DialogCreatedDomainEvent, 4 | Digdir.Domain.Dialogporten.Domain.Dialogs.Events.DialogDeletedDomainEvent, 5 | Digdir.Domain.Dialogporten.Domain.Dialogs.Events.DialogRestoredDomainEvent, 6 | Digdir.Domain.Dialogporten.Domain.Dialogs.Events.DialogSeenDomainEvent, 7 | Digdir.Domain.Dialogporten.Domain.Dialogs.Events.DialogTransmissionCreatedDomainEvent, 8 | Digdir.Domain.Dialogporten.Domain.Dialogs.Events.DialogUpdatedDomainEvent 9 | ] 10 | -------------------------------------------------------------------------------- /tests/k6/tests/graphql/performance/graphqlGetAllDialogsForRandomPartyCount.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The performance test for GraphQL search. 3 | * Run: k6 run tests/k6/tests/graphql/performance/graphqlGetAllDialogsForRandomPartyCount.js --vus 1 --iterations 1 -e env=yt01 4 | */ 5 | import { getOptions, _setup, _defaultForParties } from './graphqlCommonFunctions.js'; 6 | const dialogs_label = "graphql-getall-dialogs-for-party-for-count"; 7 | const parties_label = "graphql-get-parties"; 8 | const queryType = "getAllDialogsForPartyForCount" 9 | const labels = [dialogs_label, parties_label]; 10 | 11 | export const options = getOptions(labels); 12 | export function setup() { return _setup(dialogs_label, queryType); } 13 | export default function (data) { _defaultForParties(data, false); } -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Common/Pagination/OrderOption/OrderSelector.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | 3 | namespace Digdir.Domain.Dialogporten.Application.Common.Pagination.OrderOption; 4 | 5 | public sealed record OrderSelector(Expression> Expression, Lazy> Compiled, Expression Body) 6 | { 7 | public OrderSelector(Expression> expression) : this(expression, new(expression.Compile), RemoveConvertWrapper(expression.Body)) { } 8 | 9 | private static Expression RemoveConvertWrapper(Expression body) => 10 | body.NodeType == ExpressionType.Convert && body is UnaryExpression unaryExpression 11 | ? unaryExpression.Operand 12 | : body; 13 | } 14 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Infrastructure/Persistence/Configurations/DialogEndUserContexts/DialogEndUserContextConfiguration.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Domain.Dialogporten.Domain.DialogEndUserContexts.Entities; 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 4 | 5 | namespace Digdir.Domain.Dialogporten.Infrastructure.Persistence.Configurations.DialogEndUserContexts; 6 | 7 | internal sealed class DialogEndUserContextConfiguration : IEntityTypeConfiguration 8 | { 9 | public void Configure(EntityTypeBuilder builder) 10 | { 11 | builder.HasOne(d => d.Dialog) 12 | .WithOne(d => d.EndUserContext) 13 | .OnDelete(DeleteBehavior.SetNull); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.azure/applications/aggregate-cost-metrics-job/staging.bicepparam: -------------------------------------------------------------------------------- 1 | using './main.bicep' 2 | 3 | param environment = 'staging' 4 | param location = 'norwayeast' 5 | param imageTag = readEnvironmentVariable('IMAGE_TAG') 6 | param jobSchedule = '0 2 * * *' // 2:00 AM UTC every day 7 | param replicaTimeOutInSeconds = 1800 // 30 minutes 8 | param storageContainerName = 'costmetrics' 9 | 10 | //secrets 11 | param containerAppEnvironmentName = readEnvironmentVariable('AZURE_CONTAINER_APP_ENVIRONMENT_NAME') 12 | param appInsightConnectionString = readEnvironmentVariable('AZURE_APP_INSIGHTS_CONNECTION_STRING') 13 | param azureSubscriptionId = readEnvironmentVariable('AZURE_SUBSCRIPTION_ID') 14 | param environmentKeyVaultName = readEnvironmentVariable('AZURE_ENVIRONMENT_KEY_VAULT_NAME') 15 | 16 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Common/Pagination/PaginationConstants.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Domain.Dialogporten.Application.Common.Pagination.Order; 2 | 3 | namespace Digdir.Domain.Dialogporten.Application.Common.Pagination; 4 | 5 | public static class PaginationConstants 6 | { 7 | public const int MinLimit = 1; 8 | public const int MaxLimit = 1000; 9 | public const int DefaultLimit = 100; 10 | 11 | public const char OrderSetDelimiter = ','; 12 | public const char OrderDelimiter = '_'; 13 | public const string OrderIdKey = "id"; 14 | 15 | public const char ContinuationTokenSetDelimiter = ','; 16 | public const char ContinuationTokenDelimiter = '_'; 17 | 18 | public const OrderDirection DefaultOrderDirection = OrderDirection.Desc; 19 | } 20 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.GraphQL/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Warning" 5 | } 6 | }, 7 | "Serilog": { 8 | "MinimumLevel": { 9 | "Default": "Warning", 10 | "Override": { 11 | "Microsoft.AspNetCore": "Warning", 12 | "Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware": "Fatal", 13 | 14 | // This enables logging of parties/service tuples counts being fed into authorization 15 | "Digdir.Domain.Dialogporten.Infrastructure.Persistence.Repositories.DialogSearchRepository": "Information" 16 | } 17 | } 18 | }, 19 | "Infrastructure": { 20 | "AltinnCdn": { 21 | "BaseUri": "https://altinncdn.no/" 22 | } 23 | }, 24 | "AllowedHosts": "*" 25 | } 26 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Features/V1/Common/Content/MappingProfile.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Contents; 3 | using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Transmissions.Contents; 4 | 5 | namespace Digdir.Domain.Dialogporten.Application.Features.V1.Common.Content; 6 | 7 | public sealed class MappingProfile : Profile 8 | { 9 | public MappingProfile() 10 | { 11 | // See IntermediateDialogContent 12 | CreateMap(); 13 | CreateMap(); 14 | CreateMap(); 15 | CreateMap(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Infrastructure/Persistence/Configurations/Dialogs/Transmissions/DialogTransmissionConfiguration.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Transmissions; 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 4 | 5 | namespace Digdir.Domain.Dialogporten.Infrastructure.Persistence.Configurations.Dialogs.Transmissions; 6 | 7 | internal sealed class DialogTransmissionConfiguration : IEntityTypeConfiguration 8 | { 9 | public void Configure(EntityTypeBuilder builder) 10 | { 11 | builder.HasOne(x => x.RelatedTransmission) 12 | .WithMany(x => x.RelatedTransmissions) 13 | .OnDelete(DeleteBehavior.SetNull); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Common/ReturnTypes/Forbidden.cs: -------------------------------------------------------------------------------- 1 | using FluentValidation.Results; 2 | 3 | namespace Digdir.Domain.Dialogporten.Application.Common.ReturnTypes; 4 | 5 | public sealed record Forbidden(List Reasons) 6 | { 7 | private const string ForbiddenMessage = "Forbidden"; 8 | 9 | public Forbidden(params string[] reasons) : this(reasons.ToList()) { } 10 | 11 | public List ToValidationResults() => 12 | [.. Reasons.Select(x => new ValidationFailure(ForbiddenMessage, x))]; 13 | 14 | public Forbidden WithInvalidDialogIds(List dialogIds) 15 | { 16 | Reasons.Add($"The following dialog ids are unauthorized and/or missing: ({string.Join(", ", dialogIds)})."); 17 | return this; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Digdir.Library.Dialogporten.WebApiClient.WebApiSample/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/launchsettings.json", 3 | "profiles": { 4 | "http": { 5 | "commandName": "Project", 6 | "dotnetRunMessages": true, 7 | "launchBrowser": false, 8 | "applicationUrl": "http://localhost:5006", 9 | "environmentVariables": { 10 | "ASPNETCORE_ENVIRONMENT": "Development" 11 | } 12 | }, 13 | "https": { 14 | "commandName": "Project", 15 | "dotnetRunMessages": true, 16 | "launchBrowser": false, 17 | "applicationUrl": "https://localhost:7171;http://localhost:5006", 18 | "environmentVariables": { 19 | "ASPNETCORE_ENVIRONMENT": "Development" 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Digdir.Library.Entity.Abstractions/Features/Versionable/VersionableExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Digdir.Library.Entity.Abstractions.Features.Versionable; 2 | 3 | /// 4 | /// Provides extension methods for . 5 | /// 6 | public static class VersionableExtensions 7 | { 8 | /// 9 | /// Sets properties related to . 10 | /// 11 | /// The to update. 12 | /// The entity tag unique to each version of the entity. 13 | public static void NewVersion(this IVersionableEntity concurrentEntity, Guid? eTag = null) => 14 | concurrentEntity.Revision = eTag ?? Guid.NewGuid(); 15 | } 16 | -------------------------------------------------------------------------------- /tests/Digdir.Domain.Dialogporten.Application.Integration.Tests/Features/V1/Common/UpdateDialogServiceOwnerContextCommandExtensions.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Domain.Dialogporten.Application.Features.V1.ServiceOwner.ServiceOwnerContext.Commands.Update; 2 | 3 | namespace Digdir.Domain.Dialogporten.Application.Integration.Tests.Features.V1.Common; 4 | 5 | internal static class UpdateDialogServiceOwnerContextCommandExtensions 6 | { 7 | public static UpdateDialogServiceOwnerContextCommand AddServiceOwnerLabels(this UpdateDialogServiceOwnerContextCommand command, params string[] labels) 8 | { 9 | foreach (var label in labels) 10 | { 11 | command.Dto.ServiceOwnerLabels.Add(new ServiceOwnerLabelDto { Value = label }); 12 | } 13 | return command; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Digdir.Tool.Dialogporten.GenerateFakeData/Digdir.Tool.Dialogporten.GenerateFakeData.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | 6 | 7 | 8 | 9 | Release 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.azure/applications/service/test.bicepparam: -------------------------------------------------------------------------------- 1 | using './main.bicep' 2 | 3 | param environment = 'test' 4 | param location = 'norwayeast' 5 | param imageTag = readEnvironmentVariable('IMAGE_TAG') 6 | param revisionSuffix = readEnvironmentVariable('REVISION_SUFFIX') 7 | param environmentKeyVaultName = readEnvironmentVariable('AZURE_ENVIRONMENT_KEY_VAULT_NAME') 8 | param appConfigurationName = readEnvironmentVariable('AZURE_APP_CONFIGURATION_NAME') 9 | param containerAppEnvironmentName = readEnvironmentVariable('AZURE_CONTAINER_APP_ENVIRONMENT_NAME') 10 | param serviceBusNamespaceName = readEnvironmentVariable('AZURE_SERVICE_BUS_NAMESPACE_NAME') 11 | 12 | param otelTraceSamplerRatio = '1' 13 | 14 | // secrets 15 | param appInsightConnectionString = readEnvironmentVariable('AZURE_APP_INSIGHTS_CONNECTION_STRING') 16 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Common/Extensions/Enumerables/AsyncEnumerableExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Digdir.Domain.Dialogporten.Application.Common.Extensions.Enumerables; 2 | 3 | public static class AsyncEnumerableExtensions 4 | { 5 | public static IAsyncEnumerable Empty() => EmptyAsyncEnumerator.Instance; 6 | 7 | private sealed class EmptyAsyncEnumerator : IAsyncEnumerable, IAsyncEnumerator 8 | { 9 | public static readonly EmptyAsyncEnumerator Instance = new(); 10 | public T Current => default!; 11 | public ValueTask DisposeAsync() => default; 12 | public ValueTask MoveNextAsync() => new(false); 13 | public IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = new()) => this; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Domain/Attachments/AttachmentUrl.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Library.Entity.Abstractions; 2 | 3 | namespace Digdir.Domain.Dialogporten.Domain.Attachments; 4 | 5 | public sealed class AttachmentUrl : IEntity 6 | { 7 | public Guid Id { get; set; } 8 | public DateTimeOffset CreatedAt { get; set; } 9 | public DateTimeOffset UpdatedAt { get; set; } 10 | 11 | public string? MediaType { get; set; } = null!; 12 | public Uri Url { get; set; } = null!; 13 | 14 | // === Dependent relationships === 15 | public AttachmentUrlConsumerType.Values ConsumerTypeId { get; set; } 16 | public AttachmentUrlConsumerType ConsumerType { get; set; } = null!; 17 | 18 | public Guid AttachmentId { get; set; } 19 | public Attachment Attachment { get; set; } = null!; 20 | } 21 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Domain/Common/EventPublisher/IDomainEvent.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | 3 | namespace Digdir.Domain.Dialogporten.Domain.Common.EventPublisher; 4 | 5 | /// 6 | /// Represents a domain event. 7 | /// 8 | public interface IDomainEvent : INotification 9 | { 10 | /// 11 | /// The events' identification. Consumers can use this to ensure exactly once processing. 12 | /// 13 | Guid EventId { get; } 14 | 15 | /// 16 | /// The time at which the event, as well as all the actual changes, occurred. 17 | /// 18 | DateTimeOffset OccurredAt { get; set; } 19 | 20 | /// 21 | /// Dictionary of metadata. 22 | /// 23 | Dictionary Metadata { get; set; } 24 | } 25 | -------------------------------------------------------------------------------- /src/Digdir.Library.Dialogporten.WebApiClient/Services/DefaultValidationResult.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using System.Security.Claims; 3 | 4 | namespace Altinn.ApiClients.Dialogporten.Services; 5 | 6 | internal sealed class DefaultValidationResult : IValidationResult 7 | { 8 | public Dictionary> Errors { get; } = []; 9 | [MemberNotNullWhen(true, nameof(ClaimsPrincipal))] 10 | public bool IsValid => Errors.Count == 0; 11 | public ClaimsPrincipal? ClaimsPrincipal { get; internal set; } 12 | 13 | internal void AddError(string key, string message) 14 | { 15 | if (!Errors.TryGetValue(key, out var messages)) 16 | { 17 | Errors[key] = messages = []; 18 | } 19 | 20 | messages.Add(message); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/Digdir.Domain.Dialogporten.Application.Integration.Tests/Common/AddingEndUserTransmissionSentLabelTestData.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Transmissions; 2 | 3 | namespace Digdir.Domain.Dialogporten.Application.Integration.Tests.Common; 4 | 5 | internal sealed class AddingEndUserTransmissionSentLabelTestData : TheoryData 6 | { 7 | public AddingEndUserTransmissionSentLabelTestData() 8 | { 9 | foreach (var type in Enum.GetValues()) 10 | { 11 | // (transmissionType, shouldAddSentLabel) 12 | Add(type, type 13 | is DialogTransmissionType.Values.Submission 14 | or DialogTransmissionType.Values.Correction); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.azure/applications/service/staging.bicepparam: -------------------------------------------------------------------------------- 1 | using './main.bicep' 2 | 3 | param environment = 'staging' 4 | param location = 'norwayeast' 5 | param imageTag = readEnvironmentVariable('IMAGE_TAG') 6 | param revisionSuffix = readEnvironmentVariable('REVISION_SUFFIX') 7 | param environmentKeyVaultName = readEnvironmentVariable('AZURE_ENVIRONMENT_KEY_VAULT_NAME') 8 | param appConfigurationName = readEnvironmentVariable('AZURE_APP_CONFIGURATION_NAME') 9 | param containerAppEnvironmentName = readEnvironmentVariable('AZURE_CONTAINER_APP_ENVIRONMENT_NAME') 10 | param serviceBusNamespaceName = readEnvironmentVariable('AZURE_SERVICE_BUS_NAMESPACE_NAME') 11 | 12 | param otelTraceSamplerRatio = '1' 13 | 14 | // secrets 15 | param appInsightConnectionString = readEnvironmentVariable('AZURE_APP_INSIGHTS_CONNECTION_STRING') 16 | -------------------------------------------------------------------------------- /src/Digdir.Library.Utils.AspNet/FusionCacheFilter.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | 3 | namespace Digdir.Library.Utils.AspNet; 4 | 5 | public sealed class FusionCacheFilter : OpenTelemetry.BaseProcessor 6 | { 7 | public override void OnEnd(Activity activity) 8 | { 9 | if (!activity.Source.Name.StartsWith(Constants.FusionCache, StringComparison.OrdinalIgnoreCase)) 10 | { 11 | base.OnEnd(activity); 12 | return; 13 | } 14 | 15 | var isError = activity.Tags.Any(t => t is { Key: Constants.OtelStatusCode, Value: Constants.Error }); 16 | if (!isError) 17 | { 18 | activity.ActivityTraceFlags &= ~ActivityTraceFlags.Recorded; 19 | return; 20 | } 21 | 22 | base.OnEnd(activity); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/k6/tests/serviceowner/performance/createDialogWithThresholds.js: -------------------------------------------------------------------------------- 1 | import { randomItem } from '../../../common/k6-utils.js'; 2 | import { createDialog } from '../../performancetest_common/createDialog.js'; 3 | import { serviceOwners, endUsers } from '../../performancetest_common/readTestdata.js'; 4 | export let options = { 5 | summaryTrendStats: ['avg', 'min', 'med', 'max', 'p(95)', 'p(99)', 'p(99.5)', 'p(99.9)', 'count'], 6 | vus: 1, 7 | duration: "30s", 8 | thresholds: { 9 | "http_req_duration{name:create dialog}": ["p(95)<300"], 10 | "http_reqs{name:create dialog}": [] 11 | } 12 | } 13 | 14 | const traceCalls = (__ENV.traceCalls ?? 'false') === 'true'; 15 | 16 | export default function () { 17 | createDialog(serviceOwners[0], randomItem(endUsers), traceCalls); 18 | } 19 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Janitor/ConsoleUser.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Claims; 2 | using Digdir.Domain.Dialogporten.Application.Common.Authorization; 3 | using Digdir.Domain.Dialogporten.Application.Common.Extensions; 4 | using Digdir.Domain.Dialogporten.Application.Externals.Presentation; 5 | 6 | namespace Digdir.Domain.Dialogporten.Janitor; 7 | 8 | public sealed class ConsoleUser : IUser 9 | { 10 | public ClaimsPrincipal GetPrincipal() 11 | { 12 | var claims = new[] 13 | { 14 | new Claim(ClaimsPrincipalExtensions.ScopeClaim, AuthorizationScope.ServiceOwnerAdminScope), 15 | new Claim(ClaimsPrincipalExtensions.AltinnOrgClaim, "digdir") 16 | }; 17 | var identity = new ClaimsIdentity(claims); 18 | return new ClaimsPrincipal(identity); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Digdir.Library.Entity.Abstractions/Features/Creatable/CreatableExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Digdir.Library.Entity.Abstractions.Features.Creatable; 2 | 3 | /// 4 | /// Provides extension methods for . 5 | /// 6 | public static class CreatableExtensions 7 | { 8 | /// 9 | /// Sets properties related to . 10 | /// 11 | /// The to update. 12 | /// The creation time in UTC. 13 | public static void Create(this ICreatableEntity creatable, DateTimeOffset utcNow) 14 | { 15 | creatable.CreatedAt = creatable.CreatedAt == default 16 | ? utcNow 17 | : creatable.CreatedAt; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.azure/applications/web-api-so/staging.bicepparam: -------------------------------------------------------------------------------- 1 | using './main.bicep' 2 | 3 | param environment = 'staging' 4 | param location = 'norwayeast' 5 | param apimIp = '51.13.86.131' 6 | param imageTag = readEnvironmentVariable('IMAGE_TAG') 7 | param revisionSuffix = readEnvironmentVariable('REVISION_SUFFIX') 8 | 9 | param resources = { 10 | cpu: 1 11 | memory: '2Gi' 12 | } 13 | 14 | param otelTraceSamplerRatio = '1' 15 | 16 | // secrets 17 | param environmentKeyVaultName = readEnvironmentVariable('AZURE_ENVIRONMENT_KEY_VAULT_NAME') 18 | param containerAppEnvironmentName = readEnvironmentVariable('AZURE_CONTAINER_APP_ENVIRONMENT_NAME') 19 | param appInsightConnectionString = readEnvironmentVariable('AZURE_APP_INSIGHTS_CONNECTION_STRING') 20 | param appConfigurationName = readEnvironmentVariable('AZURE_APP_CONFIGURATION_NAME') 21 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Domain/Dialogs/Entities/DialogSearchTag.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Library.Entity.Abstractions.Features.Creatable; 2 | using Digdir.Library.Entity.Abstractions.Features.Identifiable; 3 | using Digdir.Library.Entity.Abstractions.Features.Immutable; 4 | 5 | namespace Digdir.Domain.Dialogporten.Domain.Dialogs.Entities; 6 | 7 | public sealed class DialogSearchTag : IImmutableEntity, IIdentifiableEntity, ICreatableEntity 8 | { 9 | private string _value = null!; 10 | public Guid Id { get; set; } 11 | 12 | public string Value 13 | { 14 | get => _value; 15 | set => _value = value.Trim().ToLowerInvariant(); 16 | } 17 | 18 | public DateTimeOffset CreatedAt { get; set; } 19 | 20 | public Guid DialogId { get; set; } 21 | public DialogEntity Dialog { get; set; } = null!; 22 | } 23 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Infrastructure/Persistence/Sql/Dialog/Search/Function.SeedDialogSearchQueueFull.sql: -------------------------------------------------------------------------------- 1 | -- Enqueues every dialog for rebuilding the search index; handy when bootstrapping a new environment. 2 | CREATE OR REPLACE FUNCTION search."SeedDialogSearchQueueFull"(reset_existing boolean DEFAULT false) 3 | RETURNS int 4 | LANGUAGE plpgsql AS $$ 5 | DECLARE n int; 6 | BEGIN 7 | IF reset_existing THEN 8 | -- Resetting keeps existing queue entries but clears transient error state. 9 | UPDATE search."DialogSearchRebuildQueue" 10 | SET "Status" = 0, "UpdatedAt" = now(); 11 | END IF; 12 | 13 | INSERT INTO search."DialogSearchRebuildQueue" ("DialogId") 14 | SELECT d."Id" FROM "Dialog" d 15 | ON CONFLICT ("DialogId") DO NOTHING; 16 | 17 | GET DIAGNOSTICS n = ROW_COUNT; 18 | RETURN n; 19 | END; $$; 20 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Externals/ICloudEventBus.cs: -------------------------------------------------------------------------------- 1 | namespace Digdir.Domain.Dialogporten.Application.Externals; 2 | 3 | public interface ICloudEventBus 4 | { 5 | Task Publish(CloudEvent cloudEvent, CancellationToken cancellationToken); 6 | } 7 | 8 | public sealed class CloudEvent 9 | { 10 | public string SpecVersion { get; set; } = "1.0"; 11 | public Guid Id { get; set; } 12 | public string Type { get; set; } = null!; 13 | public DateTimeOffset Time { get; set; } 14 | public string Resource { get; set; } = null!; 15 | public string ResourceInstance { get; set; } = null!; 16 | public string? Subject { get; set; } 17 | public string? AlternativeSubject { get; set; } 18 | public string Source { get; set; } = null!; 19 | public Dictionary? Data { get; set; } 20 | } 21 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/UpdateFormSavedActivityTime/UpdateFormSavedActivityTimeCommandValidator.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Domain.Dialogporten.Application.Common; 2 | using Digdir.Domain.Dialogporten.Application.Common.Extensions.FluentValidation; 3 | using FluentValidation; 4 | 5 | namespace Digdir.Domain.Dialogporten.Application.Features.V1.ServiceOwner.Dialogs.Commands.UpdateFormSavedActivityTime; 6 | 7 | internal sealed class UpdateFormSavedActivityTimeCommandValidator : AbstractValidator 8 | { 9 | public UpdateFormSavedActivityTimeCommandValidator(IClock clock) 10 | { 11 | RuleFor(x => x.DialogId).NotEmpty(); 12 | RuleFor(x => x.ActivityId).NotEmpty(); 13 | RuleFor(x => x.NewCreatedAt).IsInPast(clock); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /release-please-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": { 3 | ".": { 4 | "changelog-path": "CHANGELOG.md", 5 | "release-type": "simple", 6 | "draft": false, 7 | "prerelease": false 8 | } 9 | }, 10 | "release-type": "simple", 11 | "changelog-sections": [ 12 | { 13 | "type": "feat", 14 | "hidden": false, 15 | "section": "Features" 16 | }, 17 | { 18 | "type": "feature", 19 | "hidden": false, 20 | "section": "Features" 21 | }, 22 | { 23 | "type": "fix", 24 | "hidden": false, 25 | "section": "Bug Fixes" 26 | }, 27 | { 28 | "type": "chore", 29 | "hidden": false, 30 | "section": "Miscellaneous Chores" 31 | } 32 | ], 33 | "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json" 34 | } -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.WebApi/Endpoints/V1/WellKnown/Jwks/Get/GetJwksEndpointSummary.cs: -------------------------------------------------------------------------------- 1 | using FastEndpoints; 2 | 3 | namespace Digdir.Domain.Dialogporten.WebApi.Endpoints.V1.WellKnown.Jwks.Get; 4 | 5 | public sealed class GetJwksEndpointSummary : Summary 6 | { 7 | public GetJwksEndpointSummary() 8 | { 9 | Summary = "Gets the JSON Web Key Set (JWKS) containing the public keys used to verify dialog token signatures"; 10 | Description = """ 11 | This endpoint can be used by client integrations supporting automatic discovery of "OAuth 2.0 Authorization Server" metadata, enabling verification of dialog tokens issued by Dialogporten. 12 | """; 13 | Responses[StatusCodes.Status200OK] = "The OAuth 2.0 Authorization Server Metadata"; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/Digdir.Domain.Dialogporten.Application.Integration.Tests/Common/TestClock.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Domain.Dialogporten.Application.Common; 2 | 3 | namespace Digdir.Domain.Dialogporten.Application.Integration.Tests.Common; 4 | 5 | public sealed class TestClock : IClock 6 | { 7 | private DateTimeOffset? _override; 8 | 9 | public DateTimeOffset UtcNowOffset => _override ?? DateTimeOffset.UtcNow; 10 | public DateTimeOffset NowOffset => UtcNowOffset.ToOffset(TimeSpan.FromHours(1)); 11 | 12 | public DateTime UtcNow => UtcNowOffset.UtcDateTime; 13 | public DateTime Now => NowOffset.DateTime; 14 | 15 | public void OverrideUtc(DateTimeOffset dateTimeOffset) => _override = dateTimeOffset; 16 | public void OverrideUtc(TimeSpan skew) => _override = UtcNowOffset.Add(skew); 17 | public void Reset() => _override = null; 18 | } 19 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/ServiceOwnerContext/Queries/GetServiceOwnerLabels/ServiceOwnerLabelDto.cs: -------------------------------------------------------------------------------- 1 | namespace Digdir.Domain.Dialogporten.Application.Features.V1.ServiceOwner.ServiceOwnerContext.Queries.GetServiceOwnerLabels; 2 | 3 | public sealed class ServiceOwnerLabelResultDto 4 | { 5 | /// 6 | /// A list of labels. 7 | /// 8 | public List Labels { get; set; } = []; 9 | 10 | /// 11 | /// The unique identifier for the service owner context revision in UUIDv4 format. 12 | /// 13 | public Guid Revision { get; set; } 14 | } 15 | 16 | public sealed class ServiceOwnerLabelDto 17 | { 18 | /// 19 | /// A label value. 20 | /// 21 | public string Value { get; set; } = null!; 22 | } 23 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Common/Pagination/Continuation/ContinuationToken.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | using System.Reflection; 3 | 4 | namespace Digdir.Domain.Dialogporten.Application.Common.Pagination.Continuation; 5 | 6 | public sealed class ContinuationToken 7 | { 8 | private static readonly MemberInfo ValueMemberInfo = typeof(ContinuationToken).GetProperty(nameof(Value))!; 9 | private readonly Type _type; 10 | public string Key { get; } 11 | public object? Value { get; } 12 | 13 | public ContinuationToken(string key, object? value, Type type) 14 | { 15 | Key = key; 16 | Value = value; 17 | _type = type; 18 | } 19 | 20 | public Expression GetValueExpression() => Expression.Convert(Expression.MakeMemberAccess(Expression.Constant(this), ValueMemberInfo), _type); 21 | } 22 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Features/V1/Common/Localizations/LocalizationDto.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Domain.Dialogporten.Domain.Localizations; 2 | 3 | namespace Digdir.Domain.Dialogporten.Application.Features.V1.Common.Localizations; 4 | 5 | public sealed class LocalizationDto 6 | { 7 | private readonly string _languageCode = null!; 8 | 9 | /// 10 | /// The localized text (or URL if a front-channel embed). 11 | /// 12 | public required string Value { get; init; } 13 | 14 | /// 15 | /// The language code of the localization in ISO 639-1 format. 16 | /// 17 | /// nb 18 | public required string LanguageCode 19 | { 20 | get => _languageCode; 21 | init => _languageCode = Localization.NormalizeCultureCode(value)!; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/Digdir.Domain.Dialogporten.Infrastructure.Unit.Tests/Digdir.Domain.Dialogporten.Infrastructure.Unit.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | true 5 | false 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /tests/k6/tests/graphql/performance/graphqlSearchWithThresholds.js: -------------------------------------------------------------------------------- 1 | import { _default, _setup } from "./graphqlCommonFunctions.js"; 2 | 3 | const label = "graphql-getall-dialogs-for-party"; 4 | const queryType = "getAllDialogsForParty" 5 | 6 | const numberOfEndUsers = 200; // Remove when altinn-testtools bulk get of endusers/tokens is fast 7 | export const options = { 8 | setupTimeout: '10m', 9 | vus: 1, 10 | duration: "30s", 11 | summaryTrendStats: ['avg', 'min', 'med', 'max', 'p(95)', 'p(99)', 'p(99.5)', 'p(99.9)', 'count'], 12 | thresholds: {} 13 | }; 14 | 15 | 16 | options.thresholds[`http_req_duration{name:${label}}`] = ["p(95)<500"]; 17 | options.thresholds[`http_reqs{name:${label}}`] = []; 18 | 19 | 20 | export function setup() { return _setup(label, queryType, numberOfEndUsers); } 21 | export default function (data) { _default(data); } -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Create/Validators/CreateDialogDialogAttachmentUrlDtoValidator.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Domain.Dialogporten.Application.Common.Extensions.FluentValidation; 2 | using Digdir.Domain.Dialogporten.Domain.Common; 3 | using FluentValidation; 4 | 5 | namespace Digdir.Domain.Dialogporten.Application.Features.V1.ServiceOwner.Dialogs.Commands.Create.Validators; 6 | 7 | internal sealed class CreateDialogDialogAttachmentUrlDtoValidator : AbstractValidator 8 | { 9 | public CreateDialogDialogAttachmentUrlDtoValidator() 10 | { 11 | RuleFor(x => x.Url) 12 | .NotNull() 13 | .IsValidHttpsUrl() 14 | .MaximumLength(Constants.DefaultMaxUriLength); 15 | 16 | RuleFor(x => x.ConsumerType) 17 | .IsInEnum(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Update/Validators/UpdateDialogDialogAttachmentUrlDtoValidator.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Domain.Dialogporten.Application.Common.Extensions.FluentValidation; 2 | using Digdir.Domain.Dialogporten.Domain.Common; 3 | using FluentValidation; 4 | 5 | namespace Digdir.Domain.Dialogporten.Application.Features.V1.ServiceOwner.Dialogs.Commands.Update.Validators; 6 | 7 | internal sealed class UpdateDialogDialogAttachmentUrlDtoValidator : AbstractValidator 8 | { 9 | public UpdateDialogDialogAttachmentUrlDtoValidator() 10 | { 11 | RuleFor(x => x.Url) 12 | .NotNull() 13 | .IsValidHttpsUrl() 14 | .MaximumLength(Constants.DefaultMaxUriLength); 15 | 16 | RuleFor(x => x.ConsumerType) 17 | .IsInEnum(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Digdir.Library.Dialogporten.WebApiClient/Infrastructure/IInternalDialogportenApi.cs: -------------------------------------------------------------------------------- 1 | using Refit; 2 | 3 | namespace Altinn.ApiClients.Dialogporten.Infrastructure; 4 | 5 | internal interface IInternalDialogportenApi 6 | { 7 | [Get("/api/v1/.well-known/jwks.json")] 8 | Task GetJwks(CancellationToken cancellationToken); 9 | } 10 | 11 | internal sealed class DialogportenJwks 12 | { 13 | public required List Keys { get; init; } 14 | 15 | internal sealed class JsonWebKey 16 | { 17 | public required string Kty { get; init; } 18 | public required string Use { get; init; } 19 | public required string Kid { get; init; } 20 | public required string Crv { get; init; } 21 | public required string X { get; init; } 22 | public required string Alg { get; init; } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Update/UpdateDialogDataLoader.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Domain.Dialogporten.Application.Common.Behaviours.DataLoader; 2 | using Digdir.Domain.Dialogporten.Application.Features.V1.ServiceOwner.Common.HorizontalDataLoaders; 3 | using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities; 4 | 5 | namespace Digdir.Domain.Dialogporten.Application.Features.V1.ServiceOwner.Dialogs.Commands.Update; 6 | 7 | internal sealed class UpdateDialogDataLoader(FullDialogAggregateDataLoader fullDialogDataLoader) : TypedDataLoader 8 | { 9 | public override Task Load(UpdateDialogCommand request, CancellationToken cancellationToken) => 10 | fullDialogDataLoader.LoadDialogEntity(request.Id, cancellationToken); 11 | } 12 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Domain/DialogServiceOwnerContexts/Entities/DialogServiceOwnerLabel.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Library.Entity.Abstractions.Features.Creatable; 2 | using Digdir.Library.Entity.Abstractions.Features.Immutable; 3 | 4 | namespace Digdir.Domain.Dialogporten.Domain.DialogServiceOwnerContexts.Entities; 5 | 6 | public sealed class DialogServiceOwnerLabel : IImmutableEntity, ICreatableEntity 7 | { 8 | public const int MaxNumberOfLabels = 20; 9 | 10 | private string _value = null!; 11 | 12 | public string Value 13 | { 14 | get => _value; 15 | set => _value = value.Trim().ToLowerInvariant(); 16 | } 17 | 18 | public DateTimeOffset CreatedAt { get; set; } 19 | 20 | public Guid DialogServiceOwnerContextId { get; set; } 21 | public DialogServiceOwnerContext DialogServiceOwnerContext { get; set; } = null!; 22 | } 23 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Queries/Get/GetDialogDataLoader.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Domain.Dialogporten.Application.Common.Behaviours.DataLoader; 2 | using Digdir.Domain.Dialogporten.Application.Features.V1.ServiceOwner.Common.HorizontalDataLoaders; 3 | using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities; 4 | 5 | namespace Digdir.Domain.Dialogporten.Application.Features.V1.ServiceOwner.Dialogs.Queries.Get; 6 | 7 | internal sealed class GetDialogDataLoader(FullDialogAggregateDataLoader fullDialogAggregateDataLoader) 8 | : TypedDataLoader 9 | { 10 | public override Task Load(GetDialogQuery request, CancellationToken cancellationToken) => 11 | fullDialogAggregateDataLoader.LoadDialogEntity(request.DialogId, cancellationToken); 12 | } 13 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Infrastructure/Persistence/Sql/Dialog/Search/View.VDialogDocument.sql: -------------------------------------------------------------------------------- 1 | -- Produces weighted tsvectors per dialog so upserts remain a simple INSERT ... SELECT. 2 | CREATE OR REPLACE VIEW search."VDialogDocument" AS 3 | SELECT d."Id" AS "DialogId", 4 | ( 5 | SELECT 6 | string_agg( 7 | setweight( 8 | to_tsvector(COALESCE(isomap."TsConfigName", 'simple')::regconfig, c."Value"), -- Fall back to simple when the language map lacks a match. 9 | c."Weight"::"char" 10 | )::text, 11 | ' ' 12 | )::tsvector 13 | FROM search."VDialogContent" c 14 | LEFT JOIN search."Iso639TsVectorMap" isomap 15 | ON c."LanguageCode" = isomap."IsoCode" 16 | WHERE c."DialogId" = d."Id" 17 | ) AS "Document" 18 | FROM "Dialog" d; 19 | -------------------------------------------------------------------------------- /.azure/applications/service/yt01.bicepparam: -------------------------------------------------------------------------------- 1 | using './main.bicep' 2 | 3 | param environment = 'yt01' 4 | param location = 'norwayeast' 5 | param imageTag = readEnvironmentVariable('IMAGE_TAG') 6 | param revisionSuffix = readEnvironmentVariable('REVISION_SUFFIX') 7 | param environmentKeyVaultName = readEnvironmentVariable('AZURE_ENVIRONMENT_KEY_VAULT_NAME') 8 | param appConfigurationName = readEnvironmentVariable('AZURE_APP_CONFIGURATION_NAME') 9 | param containerAppEnvironmentName = readEnvironmentVariable('AZURE_CONTAINER_APP_ENVIRONMENT_NAME') 10 | param serviceBusNamespaceName = readEnvironmentVariable('AZURE_SERVICE_BUS_NAMESPACE_NAME') 11 | param resources = { 12 | cpu: 2 13 | memory: '4Gi' 14 | } 15 | 16 | param otelTraceSamplerRatio = '1' 17 | 18 | // secrets 19 | param appInsightConnectionString = readEnvironmentVariable('AZURE_APP_INSIGHTS_CONNECTION_STRING') 20 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Common/Extensions/OptionExtensions/OptionsBuilderFluentValidationExtensions.cs: -------------------------------------------------------------------------------- 1 | using FluentValidation; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Microsoft.Extensions.Options; 4 | 5 | namespace Digdir.Domain.Dialogporten.Application.Common.Extensions.OptionExtensions; 6 | 7 | public static class OptionsBuilderFluentValidationExtensions 8 | { 9 | public static OptionsBuilder ValidateFluently(this OptionsBuilder optionsBuilder) 10 | where TOptions : class 11 | { 12 | optionsBuilder.Services.AddTransient>(x => 13 | new FluentValidationOptions( 14 | optionsBuilder.Name, 15 | x.GetRequiredService>>())); 16 | return optionsBuilder; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Infrastructure/Persistence/Configurations/Dialogs/DialogSearchTagConfiguration.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities; 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 4 | using static Digdir.Domain.Dialogporten.Domain.Common.Constants; 5 | 6 | namespace Digdir.Domain.Dialogporten.Infrastructure.Persistence.Configurations.Dialogs; 7 | 8 | internal sealed class DialogSearchTagConfiguration : IEntityTypeConfiguration 9 | { 10 | public void Configure(EntityTypeBuilder builder) 11 | { 12 | builder.Property(x => x.Value) 13 | .HasMaxLength(MaxSearchTagLength); 14 | 15 | builder.HasIndex(x => x.Value) 16 | .HasMethod(Constants.Gin) 17 | .HasOperators(Constants.GinTrgmOps); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/GetActivity/ActivityDto.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Domain.Dialogporten.Application.Features.V1.Common.Localizations; 2 | using Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.Common.Actors; 3 | using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Activities; 4 | 5 | namespace Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.Dialogs.Queries.GetActivity; 6 | 7 | public sealed class ActivityDto 8 | { 9 | public Guid Id { get; set; } 10 | public DateTimeOffset? CreatedAt { get; set; } 11 | public Uri? ExtendedType { get; set; } 12 | 13 | public DialogActivityType.Values Type { get; set; } 14 | 15 | public Guid? TransmissionId { get; set; } 16 | 17 | public ActorDto PerformedBy { get; set; } = null!; 18 | public List Description { get; set; } = []; 19 | } 20 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Domain/Common/DomainFailure.cs: -------------------------------------------------------------------------------- 1 | namespace Digdir.Domain.Dialogporten.Domain.Common; 2 | 3 | public sealed class DomainFailure 4 | { 5 | public string PropertyName { get; init; } 6 | public string ErrorMessage { get; init; } 7 | 8 | public DomainFailure(string propertyName, string error) 9 | { 10 | PropertyName = propertyName; 11 | ErrorMessage = error; 12 | } 13 | 14 | public static DomainFailure EntityExists(string propertyName, string entityName, IEnumerable keys) 15 | => new(propertyName, $"Entity '{entityName}' with the following key(s) already exists: ({string.Join(", ", keys)})."); 16 | 17 | public static DomainFailure EntityExists(IEnumerable keys) 18 | { 19 | var entityName = typeof(T).Name; 20 | return EntityExists(entityName, entityName, keys); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.GraphQL/EndUser/MutationTypes/MappingProfile.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.EndUserContext.Commands.BulkSetSystemLabels; 3 | using Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.EndUserContext.Commands.SetSystemLabel; 4 | 5 | namespace Digdir.Domain.Dialogporten.GraphQL.EndUser.MutationTypes; 6 | 7 | public sealed class MappingProfile : Profile 8 | { 9 | public MappingProfile() 10 | { 11 | CreateMap(); 12 | 13 | CreateMap(); 14 | 15 | CreateMap(); 16 | 17 | CreateMap() 18 | .ForMember(dest => dest.Dto, opt => opt.MapFrom(src => src)); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Create/Validators/CreateDialogTransmissionAttachmentUrlDtoValidator.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Domain.Dialogporten.Application.Common.Extensions.FluentValidation; 2 | using Digdir.Domain.Dialogporten.Domain.Common; 3 | using FluentValidation; 4 | 5 | namespace Digdir.Domain.Dialogporten.Application.Features.V1.ServiceOwner.Dialogs.Commands.Create.Validators; 6 | 7 | internal sealed class CreateDialogTransmissionAttachmentUrlDtoValidator : AbstractValidator 8 | { 9 | public CreateDialogTransmissionAttachmentUrlDtoValidator() 10 | { 11 | RuleFor(x => x.Url) 12 | .NotNull() 13 | .IsValidHttpsUrl() 14 | .MaximumLength(Constants.DefaultMaxUriLength); 15 | 16 | RuleFor(x => x.ConsumerType) 17 | .IsInEnum(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Update/Validators/UpdateDialogTransmissionAttachmentUrlDtoValidator.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Domain.Dialogporten.Application.Common.Extensions.FluentValidation; 2 | using Digdir.Domain.Dialogporten.Domain.Common; 3 | using FluentValidation; 4 | 5 | namespace Digdir.Domain.Dialogporten.Application.Features.V1.ServiceOwner.Dialogs.Commands.Update.Validators; 6 | 7 | internal sealed class UpdateDialogTransmissionAttachmentUrlDtoValidator : AbstractValidator 8 | { 9 | public UpdateDialogTransmissionAttachmentUrlDtoValidator() 10 | { 11 | RuleFor(x => x.Url) 12 | .NotNull() 13 | .IsValidHttpsUrl() 14 | .MaximumLength(Constants.DefaultMaxUriLength); 15 | 16 | RuleFor(x => x.ConsumerType) 17 | .IsInEnum(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.github/workflows/workflow-get-current-version.yml: -------------------------------------------------------------------------------- 1 | name: "Get current version" 2 | # might use previous tag as a version instead of the current version in file 3 | # https://github.com/WyriHaximus/github-action-get-previous-tag 4 | on: 5 | workflow_call: 6 | outputs: 7 | version: 8 | description: "Version" 9 | value: ${{ jobs.get-current-version.outputs.version }} 10 | 11 | permissions: 12 | contents: read 13 | 14 | jobs: 15 | get-current-version: 16 | name: Filter 17 | runs-on: ubuntu-latest 18 | outputs: 19 | version: ${{ steps.set-current-version.outputs.version }} 20 | steps: 21 | - name: "Checkout GitHub Action" 22 | uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 23 | - name: Set current version 24 | id: set-current-version 25 | run: echo "version=$(cat version.txt)" >> $GITHUB_OUTPUT 26 | -------------------------------------------------------------------------------- /.azure/applications/graphql/yt01.bicepparam: -------------------------------------------------------------------------------- 1 | using './main.bicep' 2 | 3 | param environment = 'yt01' 4 | param location = 'norwayeast' 5 | param apimIp = '51.13.85.197' 6 | param imageTag = readEnvironmentVariable('IMAGE_TAG') 7 | param revisionSuffix = readEnvironmentVariable('REVISION_SUFFIX') 8 | param resources = { 9 | cpu: 2 10 | memory: '4Gi' 11 | } 12 | 13 | param otelTraceSamplerRatio = '1' 14 | 15 | // Use dedicated workload profile 16 | param workloadProfileName = 'Dedicated-D8' 17 | 18 | // secrets 19 | param environmentKeyVaultName = readEnvironmentVariable('AZURE_ENVIRONMENT_KEY_VAULT_NAME') 20 | param containerAppEnvironmentName = readEnvironmentVariable('AZURE_CONTAINER_APP_ENVIRONMENT_NAME') 21 | param appInsightConnectionString = readEnvironmentVariable('AZURE_APP_INSIGHTS_CONNECTION_STRING') 22 | param appConfigurationName = readEnvironmentVariable('AZURE_APP_CONFIGURATION_NAME') 23 | -------------------------------------------------------------------------------- /.azure/applications/web-api-eu/yt01.bicepparam: -------------------------------------------------------------------------------- 1 | using './main.bicep' 2 | 3 | param environment = 'yt01' 4 | param location = 'norwayeast' 5 | param apimIp = '51.13.85.197' 6 | param imageTag = readEnvironmentVariable('IMAGE_TAG') 7 | param revisionSuffix = readEnvironmentVariable('REVISION_SUFFIX') 8 | param resources = { 9 | cpu: 2 10 | memory: '4Gi' 11 | } 12 | 13 | param otelTraceSamplerRatio = '1' 14 | 15 | // Use dedicated workload profile 16 | param workloadProfileName = 'Dedicated-D8' 17 | 18 | // secrets 19 | param environmentKeyVaultName = readEnvironmentVariable('AZURE_ENVIRONMENT_KEY_VAULT_NAME') 20 | param containerAppEnvironmentName = readEnvironmentVariable('AZURE_CONTAINER_APP_ENVIRONMENT_NAME') 21 | param appInsightConnectionString = readEnvironmentVariable('AZURE_APP_INSIGHTS_CONNECTION_STRING') 22 | param appConfigurationName = readEnvironmentVariable('AZURE_APP_CONFIGURATION_NAME') 23 | -------------------------------------------------------------------------------- /.azure/applications/web-api-so/yt01.bicepparam: -------------------------------------------------------------------------------- 1 | using './main.bicep' 2 | 3 | param environment = 'yt01' 4 | param location = 'norwayeast' 5 | param apimIp = '51.13.85.197' 6 | param imageTag = readEnvironmentVariable('IMAGE_TAG') 7 | param revisionSuffix = readEnvironmentVariable('REVISION_SUFFIX') 8 | param resources = { 9 | cpu: 2 10 | memory: '4Gi' 11 | } 12 | 13 | param otelTraceSamplerRatio = '1' 14 | 15 | // Use dedicated workload profile 16 | param workloadProfileName = 'Dedicated-D8' 17 | 18 | // secrets 19 | param environmentKeyVaultName = readEnvironmentVariable('AZURE_ENVIRONMENT_KEY_VAULT_NAME') 20 | param containerAppEnvironmentName = readEnvironmentVariable('AZURE_CONTAINER_APP_ENVIRONMENT_NAME') 21 | param appInsightConnectionString = readEnvironmentVariable('AZURE_APP_INSIGHTS_CONNECTION_STRING') 22 | param appConfigurationName = readEnvironmentVariable('AZURE_APP_CONFIGURATION_NAME') 23 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Infrastructure/Persistence/Sql/Dialog/Search/View.VDialogDocument.V2.sql: -------------------------------------------------------------------------------- 1 | -- Produces weighted tsvectors per dialog so upserts remain a simple INSERT ... SELECT. 2 | CREATE OR REPLACE VIEW search."VDialogDocument" AS 3 | SELECT d."Id" AS "DialogId", 4 | ( 5 | SELECT 6 | string_agg( 7 | setweight( 8 | to_tsvector(COALESCE(isomap."TsConfigName", 'simple')::regconfig, c."Value"), -- Fall back to simple when the language map lacks a match. 9 | c."Weight"::"char" 10 | )::text, 11 | ' ' 12 | )::tsvector 13 | FROM search."VDialogContent" c 14 | LEFT JOIN search."Iso639TsVectorMap" isomap 15 | ON c."LanguageCode" = isomap."IsoCode" 16 | WHERE c."DialogId" = d."Id" 17 | ) AS "Document", 18 | d."Party" AS "Party" 19 | FROM "Dialog" d; 20 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.WebApi/Endpoints/V1/Common/Headers/HttpResponseHeaderExamples.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Domain.Dialogporten.WebApi.Common; 2 | using FastEndpoints; 3 | 4 | namespace Digdir.Domain.Dialogporten.WebApi.Endpoints.V1.Common.Headers; 5 | 6 | public static class HttpResponseHeaderExamples 7 | { 8 | public static ResponseHeader NewDialogETagHeader(int statusCode) 9 | => new(statusCode, Constants.ETag) 10 | { 11 | Description = "The new UUID ETag of the dialog", 12 | Example = "123e4567-e89b-12d3-a456-426614174000" 13 | }; 14 | 15 | public static ResponseHeader NewServiceOwnerContextETagHeader(int statusCode) 16 | => new(statusCode, Constants.ETag) 17 | { 18 | Description = "The new UUID ETag of the ServiceOwner Context", 19 | Example = "123e4567-e89b-12d3-a456-426614174000" 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /.azure/applications/service/prod.bicepparam: -------------------------------------------------------------------------------- 1 | using './main.bicep' 2 | 3 | param environment = 'prod' 4 | param location = 'norwayeast' 5 | param imageTag = readEnvironmentVariable('IMAGE_TAG') 6 | param revisionSuffix = readEnvironmentVariable('REVISION_SUFFIX') 7 | param environmentKeyVaultName = readEnvironmentVariable('AZURE_ENVIRONMENT_KEY_VAULT_NAME') 8 | param appConfigurationName = readEnvironmentVariable('AZURE_APP_CONFIGURATION_NAME') 9 | param containerAppEnvironmentName = readEnvironmentVariable('AZURE_CONTAINER_APP_ENVIRONMENT_NAME') 10 | param serviceBusNamespaceName = readEnvironmentVariable('AZURE_SERVICE_BUS_NAMESPACE_NAME') 11 | param minReplicas = 2 12 | 13 | param resources = { 14 | cpu: 2 15 | memory: '4Gi' 16 | } 17 | 18 | param otelTraceSamplerRatio = '0.2' 19 | 20 | // secrets 21 | param appInsightConnectionString = readEnvironmentVariable('AZURE_APP_INSIGHTS_CONNECTION_STRING') 22 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Common/ITransactionTime.cs: -------------------------------------------------------------------------------- 1 | namespace Digdir.Domain.Dialogporten.Application.Common; 2 | 3 | /// 4 | /// Abstraction around the time that is to be used for all time 5 | /// related DB and event fields within the same transaction. 6 | /// 7 | public interface ITransactionTime 8 | { 9 | DateTimeOffset Value { get; } 10 | } 11 | 12 | /// 13 | /// This will wait to the last possible moment to get the 14 | /// current time and use that as the transaction time. 15 | /// 16 | internal sealed class TransactionTime : ITransactionTime 17 | { 18 | private readonly Lazy _lazyValue; 19 | public DateTimeOffset Value => _lazyValue.Value; 20 | public TransactionTime(IClock clock) 21 | { 22 | ArgumentNullException.ThrowIfNull(clock); 23 | _lazyValue = new(() => clock.UtcNow); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Common/Actors/ActorDto.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Domain.Dialogporten.Domain.Actors; 2 | 3 | namespace Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.Common.Actors; 4 | 5 | public sealed class ActorDto 6 | { 7 | /// 8 | /// The type of actor; either the service owner, or someone representing the party. 9 | /// 10 | public ActorType.Values ActorType { get; set; } 11 | 12 | /// 13 | /// The name of the actor. 14 | /// 15 | /// Ola Nordmann 16 | public string? ActorName { get; set; } 17 | 18 | /// 19 | /// The identifier (national identity number or organization number) of the actor. 20 | /// 21 | /// urn:altinn:person:identifier-no:12018212345 22 | public string? ActorId { get; set; } 23 | } 24 | 25 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Common/Actors/ActorDto.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Domain.Dialogporten.Domain.Actors; 2 | 3 | namespace Digdir.Domain.Dialogporten.Application.Features.V1.ServiceOwner.Common.Actors; 4 | 5 | public sealed class ActorDto 6 | { 7 | /// 8 | /// The type of actor; either the service owner, or someone representing the party. 9 | /// 10 | public ActorType.Values ActorType { get; set; } 11 | 12 | /// 13 | /// The name of the actor. 14 | /// 15 | /// Ola Nordmann 16 | public string? ActorName { get; set; } 17 | 18 | /// 19 | /// The identifier (national identity number or organization number) of the actor. 20 | /// 21 | /// urn:altinn:person:identifier-no:12018212345 22 | public string? ActorId { get; set; } 23 | } 24 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Application/Features/V1/Common/ValidationErrorStrings.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Domain.Dialogporten.Domain.DialogEndUserContexts.Entities; 2 | using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Transmissions; 3 | 4 | namespace Digdir.Domain.Dialogporten.Application.Features.V1.Common; 5 | 6 | internal static class ValidationErrorStrings 7 | { 8 | internal const string PropertyNameMustBeLessThanOrEqualToComparisonProperty = 9 | "'{PropertyName}' must be less than or equal to '{ComparisonProperty}'."; 10 | 11 | internal static readonly string SentLabelNotAllowed = 12 | $"Cannot manually add or remove system label {SystemLabel.Values.Sent}. " + 13 | $"It is added automatically when a " + 14 | $"transmissions of type '{DialogTransmissionType.Values.Submission} or " + 15 | $"'{DialogTransmissionType.Values.Correction}' is added to the dialog."; 16 | } 17 | -------------------------------------------------------------------------------- /tests/k6/tests/enduser/all-tests.js: -------------------------------------------------------------------------------- 1 | // This file is generated, see "scripts" directory 2 | import { default as DialogGetAcceptLanguage } from './DialogGetAcceptLanguage.js'; 3 | import { default as dialogBulkSystemLabels } from './dialogBulkSystemLabels.js'; 4 | import { default as dialogDetails } from './dialogDetails.js'; 5 | import { default as dialogSearch } from './dialogSearch.js'; 6 | import { default as dialogSystemLabelLog } from './dialogSystemLabelLog.js'; 7 | import { default as dialogSystemUser } from './dialogSystemUser.js'; 8 | import { default as dialogVisibleFrom } from './dialogVisibleFrom.js'; 9 | import { default as parties } from './parties.js'; 10 | 11 | export default function() { 12 | DialogGetAcceptLanguage(); 13 | dialogBulkSystemLabels(); 14 | dialogDetails(); 15 | dialogSearch(); 16 | dialogSystemLabelLog(); 17 | dialogSystemUser(); 18 | dialogVisibleFrom(); 19 | parties(); 20 | } 21 | -------------------------------------------------------------------------------- /.github/workflows/ci-cd-pull-request-title.yml: -------------------------------------------------------------------------------- 1 | # Checks the title of a PR to ensure it follows the correct format 2 | name: "PR Title Checker" 3 | on: 4 | pull_request_target: 5 | types: [opened, edited, reopened, synchronize] 6 | branches: 7 | - main 8 | 9 | concurrency: 10 | group: ${{ github.workflow }}-${{ github.ref }} 11 | 12 | jobs: 13 | validate: 14 | permissions: 15 | contents: read 16 | pull-requests: read 17 | name: Validate PR Title 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 21 | with: 22 | egress-policy: audit 23 | 24 | - uses: thehanimo/pr-title-checker@7fbfe05602bdd86f926d3fb3bccb6f3aed43bc70 # v1.4.3 25 | with: 26 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 27 | configuration_path: ".github/pr-title-checker-config.json" 28 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Infrastructure/Persistence/Sql/Dialog/Search/Function.ClaimDialogsStandard.sql: -------------------------------------------------------------------------------- 1 | -- Claims pending dialogs in FIFO order so parallel workers share the queue deterministically. 2 | CREATE OR REPLACE FUNCTION search."ClaimDialogsStandard"(batch_size int) 3 | RETURNS uuid[] LANGUAGE sql AS $$ 4 | WITH to_claim AS ( 5 | SELECT q."DialogId" 6 | FROM search."DialogSearchRebuildQueue" q 7 | WHERE q."Status" = 0 8 | ORDER BY q."DialogId" -- Stable order avoids the same worker repeatedly claiming the tail. 9 | FOR UPDATE OF q SKIP LOCKED 10 | LIMIT batch_size 11 | ), 12 | mark_processing AS ( 13 | UPDATE search."DialogSearchRebuildQueue" q 14 | SET "Status" = 1, "UpdatedAt" = now() 15 | WHERE q."DialogId" IN (SELECT "DialogId" FROM to_claim) 16 | RETURNING q."DialogId" 17 | ) 18 | SELECT coalesce(array_agg("DialogId"), '{}') FROM mark_processing; 19 | $$; 20 | -------------------------------------------------------------------------------- /src/Digdir.Domain.Dialogporten.Domain/Dialogs/Entities/Actions/DialogApiAction.cs: -------------------------------------------------------------------------------- 1 | using Digdir.Library.Entity.Abstractions; 2 | using Digdir.Library.Entity.Abstractions.Features.Aggregate; 3 | 4 | namespace Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Actions; 5 | 6 | public sealed class DialogApiAction : IEntity 7 | { 8 | public Guid Id { get; set; } 9 | public DateTimeOffset CreatedAt { get; set; } 10 | public DateTimeOffset UpdatedAt { get; set; } 11 | 12 | public string Action { get; set; } = null!; 13 | public string? AuthorizationAttribute { get; set; } 14 | public string? Name { get; set; } 15 | 16 | // === Dependent relationships === 17 | public Guid DialogId { get; set; } 18 | public DialogEntity Dialog { get; set; } = null!; 19 | 20 | // === Principal relationships === 21 | [AggregateChild] 22 | public List Endpoints { get; set; } = []; 23 | } 24 | -------------------------------------------------------------------------------- /.azure/bicepconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://aka.ms/bicep/config for more information on Bicep configuration options 3 | // Press CTRL+SPACE/CMD+SPACE at any location to see Intellisense suggestions 4 | "analyzers": { 5 | "core": { 6 | "rules": { 7 | "no-unused-params": { 8 | "level": "error" 9 | }, 10 | "no-unused-vars": { 11 | "level": "error" 12 | }, 13 | "no-hardcoded-env-urls": { 14 | "level": "error" 15 | }, 16 | "secure-secrets-in-params": { 17 | "level": "error" 18 | }, 19 | "no-unnecessary-dependson": { 20 | "level": "error" 21 | }, 22 | "outputs-should-not-contain-secrets": { 23 | "level": "error" 24 | } 25 | } 26 | } 27 | }, 28 | "experimentalFeaturesEnabled": { 29 | "compileTimeImports": true, 30 | "userDefinedFunctions": true 31 | } 32 | } --------------------------------------------------------------------------------