diff --git a/.azure/applications/api/main.bicep b/.azure/applications/api/main.bicep index 40f9a454..3845496e 100644 --- a/.azure/applications/api/main.bicep +++ b/.azure/applications/api/main.bicep @@ -17,6 +17,8 @@ param keyVaultUrl string param namePrefix string @secure() param storageAccountName string +@secure() +param sblBridgeBaseUrl string @minLength(3) param maskinporten_environment string param correspondenceBaseUrl string @@ -99,6 +101,7 @@ module containerApp '../../modules/containerApp/main.bicep' = { contactReservationRegistryBaseUrl: contactReservationRegistryBaseUrl idportenIssuer: idportenIssuer dialogportenIssuer: dialogportenIssuer + sblBridgeBaseUrl: sblBridgeBaseUrl maskinporten_token_exchange_environment: maskinporten_token_exchange_environment } } diff --git a/.azure/applications/api/params.bicepparam b/.azure/applications/api/params.bicepparam index fab0412f..557f9ad6 100644 --- a/.azure/applications/api/params.bicepparam +++ b/.azure/applications/api/params.bicepparam @@ -5,6 +5,7 @@ param location = 'norwayeast' param imageTag = readEnvironmentVariable('IMAGE_TAG') param platform_base_url = readEnvironmentVariable('PLATFORM_BASE_URL') param correspondenceBaseUrl = readEnvironmentVariable('CORRESPONDENCE_BASE_URL') +param sblBridgeBaseUrl = readEnvironmentVariable('SBL_BRIDGE_BASE_URL') param contactReservationRegistryBaseUrl = readEnvironmentVariable('CONTACT_RESERVATION_REGISTRY_BASE_URL') param environment = readEnvironmentVariable('ENVIRONMENT') param maskinporten_environment = 'test' diff --git a/.azure/modules/containerApp/main.bicep b/.azure/modules/containerApp/main.bicep index 96c26894..a9c20f5f 100644 --- a/.azure/modules/containerApp/main.bicep +++ b/.azure/modules/containerApp/main.bicep @@ -11,6 +11,9 @@ param idportenIssuer string param dialogportenIssuer string param maskinporten_token_exchange_environment string +@secure() +param sblBridgeBaseUrl string + @secure() param subscription_id string @secure() @@ -78,6 +81,7 @@ var containerAppEnvVarsdefault = [ { name: 'GeneralSettings__CorrespondenceBaseUrl', value: correspondenceBaseUrl } { name: 'GeneralSettings__ContactReservationRegistryBaseUrl', value: contactReservationRegistryBaseUrl} { name: 'GeneralSettings__SlackUrl', secretRef: 'slack-url' } + { name: 'GeneralSettings__AltinnSblBridgeBaseUrl', value: sblBridgeBaseUrl } { name: 'DialogportenSettings__Issuer', value: dialogportenIssuer } { name: 'IdportenSettings__Issuer', value: idportenIssuer } { name: 'IdportenSettings__ClientId', secretRef: 'idporten-client-id' } diff --git a/.github/actions/deploy-to-environment/action.yml b/.github/actions/deploy-to-environment/action.yml index 2c73c039..7a7cf768 100644 --- a/.github/actions/deploy-to-environment/action.yml +++ b/.github/actions/deploy-to-environment/action.yml @@ -66,6 +66,9 @@ inputs: SLACK_URL: description: "Slack URL" required: true + SBL_BRIDGE_BASE_URL: + description: "SBL Bridge Base URL" + required: true MASKINPORTEN_TOKEN_EXCHANGE_ENVIRONMENT: description: "Maskinporten Token Exchange Environment" required: false @@ -126,4 +129,5 @@ runs: IDPORTEN_ISSUER: ${{ inputs.IDPORTEN_ISSUER }} PLATFORM_BASE_URL: ${{ inputs.PLATFORM_BASE_URL }} STORAGE_ACCOUNT_NAME: ${{ inputs.AZURE_STORAGE_ACCOUNT_NAME }} + SBL_BRIDGE_BASE_URL: ${{ inputs.SBL_BRIDGE_BASE_URL }} MASKINPORTEN_TOKEN_EXCHANGE_ENVIRONMENT: ${{ inputs.MASKINPORTEN_TOKEN_EXCHANGE_ENVIRONMENT }} diff --git a/.github/actions/release-version/action.yml b/.github/actions/release-version/action.yml index 971b3546..7f826073 100644 --- a/.github/actions/release-version/action.yml +++ b/.github/actions/release-version/action.yml @@ -42,6 +42,9 @@ inputs: IDPORTEN_ISSUER: description: "Issuer for Maskinporten integration used for IDPorten auth" required: true + SBL_BRIDGE_BASE_URL: + description: "Base url for SBL Bridge" + required: true MASKINPORTEN_TOKEN_EXCHANGE_ENVIRONMENT: description: "Environment for Maskinporten token exchange" required: false @@ -77,6 +80,7 @@ runs: CONTACT_RESERVATION_REGISTRY_BASE_URL: ${{ inputs.CONTACT_RESERVATION_REGISTRY_BASE_URL }} DIALOGPORTEN_ISSUER: ${{ inputs.DIALOGPORTEN_ISSUER }} IDPORTEN_ISSUER: ${{ inputs.IDPORTEN_ISSUER }} + SBL_BRIDGE_BASE_URL: ${{ inputs.SBL_BRIDGE_BASE_URL }} MASKINPORTEN_TOKEN_EXCHANGE_ENVIRONMENT: ${{ inputs.MASKINPORTEN_TOKEN_EXCHANGE_ENVIRONMENT }} with: scope: subscription diff --git a/.github/workflows/ci-cd.yaml b/.github/workflows/ci-cd.yaml index 92a75767..770fd1d7 100644 --- a/.github/workflows/ci-cd.yaml +++ b/.github/workflows/ci-cd.yaml @@ -78,7 +78,48 @@ jobs: PLATFORM_BASE_URL: ${{ secrets.PLATFORM_BASE_URL }} PLATFORM_SUBSCRIPTION_KEY: ${{ secrets.PLATFORM_SUBSCRIPTION_KEY }} SLACK_URL: ${{ secrets.SLACK_URL }} + SBL_BRIDGE_BASE_URL: ${{ secrets.SBL_BRIDGE_BASE_URL }} + + deploy-at22: + name: deploy at22 + runs-on: ubuntu-latest + environment: test + if: always() && !failure() && !cancelled() + needs: [get-version, publish, test] + permissions: + id-token: write + contents: read + packages: write + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Deploy to environment + uses: ./.github/actions/deploy-to-environment + with: + environment: at22 + imageTag: ${{ needs.get-version.outputs.imageTag }} + ACCESS_MANAGEMENT_SUBSCRIPTION_KEY: ${{ secrets.ACCESS_MANAGEMENT_SUBSCRIPTION_KEY }} + AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} + AZURE_ENVIRONMENT_KEY_VAULT_NAME: ${{ secrets.AZURE_ENVIRONMENT_KEY_VAULT_NAME }} + AZURE_NAME_PREFIX: ${{ secrets.AZURE_NAME_PREFIX }} + AZURE_STORAGE_ACCOUNT_NAME: ${{ secrets.AZURE_STORAGE_ACCOUNT_NAME }} + AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} + AZURE_TEST_ACCESS_CLIENT_ID: ${{ secrets.AZURE_TEST_ACCESS_CLIENT_ID }} + CORRESPONDENCE_BASE_URL: ${{ secrets.CORRESPONDENCE_BASE_URL }} + DIALOGPORTEN_ISSUER: ${{ secrets.DIALOGPORTEN_ISSUER }} + IDPORTEN_CLIENT_ID: ${{ secrets.IDPORTEN_CLIENT_ID }} + IDPORTEN_CLIENT_SECRET: ${{ secrets.IDPORTEN_CLIENT_SECRET }} + IDPORTEN_ISSUER: ${{ secrets.IDPORTEN_ISSUER }} + MASKINPORTEN_CLIENT_ID: ${{ secrets.MASKINPORTEN_CLIENT_ID }} + MASKINPORTEN_JWK: ${{ secrets.MASKINPORTEN_JWK }} + PLATFORM_BASE_URL: ${{ secrets.PLATFORM_BASE_URL }} + PLATFORM_SUBSCRIPTION_KEY: ${{ secrets.PLATFORM_SUBSCRIPTION_KEY }} + SLACK_URL: ${{ secrets.SLACK_URL }} + SBL_BRIDGE_BASE_URL: ${{ secrets.SBL_BRIDGE_BASE_URL }} MASKINPORTEN_TOKEN_EXCHANGE_ENVIRONMENT: ${{ secrets.MASKINPORTEN_TOKEN_EXCHANGE_ENVIRONMENT }} + CONTACT_RESERVATION_REGISTRY_BASE_URL: ${{ secrets.CONTACT_RESERVATION_REGISTRY_BASE_URL }} deploy-staging: name: Internal staging @@ -121,6 +162,7 @@ jobs: PLATFORM_BASE_URL: ${{ secrets.PLATFORM_BASE_URL }} PLATFORM_SUBSCRIPTION_KEY: ${{ secrets.PLATFORM_SUBSCRIPTION_KEY }} SLACK_URL: ${{ secrets.SLACK_URL }} + SBL_BRIDGE_BASE_URL: ${{ secrets.SBL_BRIDGE_BASE_URL }} MASKINPORTEN_TOKEN_EXCHANGE_ENVIRONMENT: ${{ secrets.MASKINPORTEN_TOKEN_EXCHANGE_ENVIRONMENT }} deploy-production: @@ -164,6 +206,7 @@ jobs: PLATFORM_BASE_URL: ${{ secrets.PLATFORM_BASE_URL }} PLATFORM_SUBSCRIPTION_KEY: ${{ secrets.PLATFORM_SUBSCRIPTION_KEY }} SLACK_URL: ${{ secrets.SLACK_URL }} + SBL_BRIDGE_BASE_URL: ${{ secrets.SBL_BRIDGE_BASE_URL }} MASKINPORTEN_TOKEN_EXCHANGE_ENVIRONMENT: ${{ secrets.MASKINPORTEN_TOKEN_EXCHANGE_ENVIRONMENT }} release-to-git: diff --git a/.github/workflows/deploy-to-environment.yml b/.github/workflows/deploy-to-environment.yml index 807a32f6..5687b3f3 100644 --- a/.github/workflows/deploy-to-environment.yml +++ b/.github/workflows/deploy-to-environment.yml @@ -85,4 +85,5 @@ jobs: PLATFORM_BASE_URL: ${{ secrets.PLATFORM_BASE_URL }} PLATFORM_SUBSCRIPTION_KEY: ${{ secrets.PLATFORM_SUBSCRIPTION_KEY }} SLACK_URL: ${{ secrets.SLACK_URL }} - MASKINPORTEN_TOKEN_EXCHANGE_ENVIRONMENT: ${{ secrets.MASKINPORTEN_TOKEN_EXCHANGE_ENVIRONMENT }} \ No newline at end of file + SBL_BRIDGE_BASE_URL: ${{ secrets.SBL_BRIDGE_BASE_URL }} + MASKINPORTEN_TOKEN_EXCHANGE_ENVIRONMENT: ${{ secrets.MASKINPORTEN_TOKEN_EXCHANGE_ENVIRONMENT }} diff --git a/.github/workflows/publish-branch.yml b/.github/workflows/publish-branch.yml index 1ba86ca9..ec91ab69 100644 --- a/.github/workflows/publish-branch.yml +++ b/.github/workflows/publish-branch.yml @@ -40,5 +40,6 @@ jobs: STORAGE_ACCOUNT_NAME: ${{ secrets.AZURE_STORAGE_ACCOUNT_NAME }} DIALOGPORTEN_ISSUER: ${{ secrets.DIALOGPORTEN_ISSUER }} IDPORTEN_ISSUER: ${{ secrets.IDPORTEN_ISSUER }} + SBL_BRIDGE_BASE_URL: ${{ secrets.SBL_BRIDGE_BASE_URL }} MASKINPORTEN_TOKEN_EXCHANGE_ENVIRONMENT: ${{ secrets.MASKINPORTEN_TOKEN_EXCHANGE_ENVIRONMENT }} \ No newline at end of file diff --git a/src/Altinn.Correspondence.API/appsettings.Development.json b/src/Altinn.Correspondence.API/appsettings.Development.json index 0466c753..f4581340 100644 --- a/src/Altinn.Correspondence.API/appsettings.Development.json +++ b/src/Altinn.Correspondence.API/appsettings.Development.json @@ -34,6 +34,7 @@ "GeneralSettings": { "SlackUrl": "", "CorrespondenceBaseUrl": "https://localhost:7241/", + "AltinnSblBridgeBaseUrl": "", "ContactReservationRegistryBaseUrl": "https://test.kontaktregisteret.no" } } \ No newline at end of file diff --git a/src/Altinn.Correspondence.API/appsettings.json b/src/Altinn.Correspondence.API/appsettings.json index 46bdb452..49625b5f 100644 --- a/src/Altinn.Correspondence.API/appsettings.json +++ b/src/Altinn.Correspondence.API/appsettings.json @@ -7,4 +7,4 @@ } }, "AllowedHosts": "*" -} +} \ No newline at end of file diff --git a/src/Altinn.Correspondence.Application/DependencyInjection.cs b/src/Altinn.Correspondence.Application/DependencyInjection.cs index 3ddea6eb..0b2fbfa2 100644 --- a/src/Altinn.Correspondence.Application/DependencyInjection.cs +++ b/src/Altinn.Correspondence.Application/DependencyInjection.cs @@ -11,6 +11,7 @@ using Altinn.Correspondence.Application.InitializeAttachment; using Altinn.Correspondence.Application.InitializeCorrespondence; using Altinn.Correspondence.Application.InitializeCorrespondences; +using Altinn.Correspondence.Application.ProcessLegacyParty; using Altinn.Correspondence.Application.PublishCorrespondence; using Altinn.Correspondence.Application.PurgeAttachment; using Altinn.Correspondence.Application.PurgeCorrespondence; @@ -47,6 +48,7 @@ public static void AddApplicationHandlers(this IServiceCollection services) // Integrations services.AddScoped(); services.AddScoped(); + services.AddScoped(); // Helpers services.AddScoped(); diff --git a/src/Altinn.Correspondence.Application/ProcessLegacyParty/ProcessLegacyPartyHandler.cs b/src/Altinn.Correspondence.Application/ProcessLegacyParty/ProcessLegacyPartyHandler.cs new file mode 100644 index 00000000..8580371f --- /dev/null +++ b/src/Altinn.Correspondence.Application/ProcessLegacyParty/ProcessLegacyPartyHandler.cs @@ -0,0 +1,35 @@ +using Altinn.Correspondence.Core.Repositories; +using Altinn.Correspondence.Core.Services; +using Microsoft.Extensions.Logging; +using OneOf; +using System.Security.Claims; + +namespace Altinn.Correspondence.Application.ProcessLegacyParty; + +public class ProcessLegacyPartyHandler( + ILogger logger, + IAltinnRegisterService altinnRegisterService, + IAltinnSblBridgeService sblBridgeService, + ILegacyPartyRepository legacyPartyRepository) +{ + public async Task Process(string recipient, ClaimsPrincipal? user, CancellationToken cancellationToken) + { + logger.LogInformation("Process legacy party {recipient}", recipient); + var partyId = await altinnRegisterService.LookUpPartyId(recipient, cancellationToken); + if (partyId is null) + { + throw new Exception("Failed to look up party in Altinn Register"); + } + var exists = await legacyPartyRepository.PartyAlreadyExists((int)partyId, cancellationToken); + if (!exists) + { + var success = await sblBridgeService.AddPartyToSblBridge((int)partyId, cancellationToken); + if (!success) + { + throw new Exception("Failed to send party to SBL"); + } + logger.Log(LogLevel.Information, "Party {partyId} added to SBL", partyId); + await legacyPartyRepository.AddLegacyPartyId((int)partyId, cancellationToken); + } + } +} diff --git a/src/Altinn.Correspondence.Application/PublishCorrespondence/PublishCorrespondenceHandler.cs b/src/Altinn.Correspondence.Application/PublishCorrespondence/PublishCorrespondenceHandler.cs index 994d85d7..3d6f2030 100644 --- a/src/Altinn.Correspondence.Application/PublishCorrespondence/PublishCorrespondenceHandler.cs +++ b/src/Altinn.Correspondence.Application/PublishCorrespondence/PublishCorrespondenceHandler.cs @@ -1,5 +1,6 @@ using Altinn.Correspondence.Application.CancelNotification; using Altinn.Correspondence.Application.Helpers; +using Altinn.Correspondence.Application.ProcessLegacyParty; using Altinn.Correspondence.Common.Helpers; using Altinn.Correspondence.Core.Models.Entities; using Altinn.Correspondence.Core.Models.Enums; @@ -93,6 +94,7 @@ public async Task> Process(Guid correspondenceId, ClaimsPrinc PartyUuid = partyUuid }; await correspondenceRepository.UpdatePublished(correspondenceId, status.StatusChanged, cancellationToken); + backgroundJobClient.Enqueue((handler) => handler.Process(correspondence.Recipient, null, cancellationToken)); backgroundJobClient.Enqueue((dialogportenService) => dialogportenService.CreateInformationActivity(correspondenceId, DialogportenActorType.ServiceOwner, DialogportenTextType.CorrespondencePublished)); } diff --git a/src/Altinn.Correspondence.Core/Models/Entities/LegacyPartyEntity.cs b/src/Altinn.Correspondence.Core/Models/Entities/LegacyPartyEntity.cs new file mode 100644 index 00000000..d875ed92 --- /dev/null +++ b/src/Altinn.Correspondence.Core/Models/Entities/LegacyPartyEntity.cs @@ -0,0 +1,13 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using Altinn.Correspondence.Core.Models.Enums; + +namespace Altinn.Correspondence.Core.Models.Entities +{ + public class LegacyPartyEntity + { + [Key] + public Guid Id { get; set; } + public int PartyId { get; set; } + } +} diff --git a/src/Altinn.Correspondence.Core/Options/GeneralSettings.cs b/src/Altinn.Correspondence.Core/Options/GeneralSettings.cs index 117419cb..4f255564 100644 --- a/src/Altinn.Correspondence.Core/Options/GeneralSettings.cs +++ b/src/Altinn.Correspondence.Core/Options/GeneralSettings.cs @@ -5,6 +5,8 @@ public class GeneralSettings public string SlackUrl { get; set; } = string.Empty; public string CorrespondenceBaseUrl { get; set; } = string.Empty; + + public string AltinnSblBridgeBaseUrl { get; set; } = string.Empty; public string RedisConnectionString { get; set; } = string.Empty; public string ContactReservationRegistryBaseUrl { get; set; } = string.Empty; } \ No newline at end of file diff --git a/src/Altinn.Correspondence.Core/Repositories/ILegacyPartyRepository.cs b/src/Altinn.Correspondence.Core/Repositories/ILegacyPartyRepository.cs new file mode 100644 index 00000000..4bc8051b --- /dev/null +++ b/src/Altinn.Correspondence.Core/Repositories/ILegacyPartyRepository.cs @@ -0,0 +1,10 @@ +using Altinn.Correspondence.Core.Models.Entities; + +namespace Altinn.Correspondence.Core.Repositories +{ + public interface ILegacyPartyRepository + { + Task AddLegacyPartyId(int id, CancellationToken cancellationToken); + Task PartyAlreadyExists(int id, CancellationToken cancellationToken); + } +} \ No newline at end of file diff --git a/src/Altinn.Correspondence.Core/Services/IAltinnRegisterService.cs b/src/Altinn.Correspondence.Core/Services/IAltinnRegisterService.cs index 36316a6f..cc369ff4 100644 --- a/src/Altinn.Correspondence.Core/Services/IAltinnRegisterService.cs +++ b/src/Altinn.Correspondence.Core/Services/IAltinnRegisterService.cs @@ -4,7 +4,7 @@ namespace Altinn.Correspondence.Core.Services; public interface IAltinnRegisterService { - Task LookUpPartyId(string identificationId, CancellationToken cancellationToken); + Task LookUpPartyId(string identificationId, CancellationToken cancellationToken); Task LookUpName(string identificationId, CancellationToken cancellationToken); Task LookUpPartyByPartyId(int partyId, CancellationToken cancellationToken); Task LookUpPartyById(string identificationId, CancellationToken cancellationToken); diff --git a/src/Altinn.Correspondence.Core/Services/IAltinnSblBridgeService.cs b/src/Altinn.Correspondence.Core/Services/IAltinnSblBridgeService.cs new file mode 100644 index 00000000..9fcf408d --- /dev/null +++ b/src/Altinn.Correspondence.Core/Services/IAltinnSblBridgeService.cs @@ -0,0 +1,8 @@ +using Altinn.Correspondence.Core.Models.Entities; + +namespace Altinn.Correspondence.Core.Services; +public interface IAltinnSblBridgeService +{ + Task AddPartyToSblBridge(int partyId, CancellationToken cancellationToken); + +} diff --git a/src/Altinn.Correspondence.Integrations/Altinn/Events/AltinnEventBus.cs b/src/Altinn.Correspondence.Integrations/Altinn/Events/AltinnEventBus.cs index e3eb3b03..55ea0f62 100644 --- a/src/Altinn.Correspondence.Integrations/Altinn/Events/AltinnEventBus.cs +++ b/src/Altinn.Correspondence.Integrations/Altinn/Events/AltinnEventBus.cs @@ -30,7 +30,7 @@ public async Task Publish(AltinnEventType type, string resourceId, string itemId string? partyId = null; if (recipientId != null) { - partyId = await _altinnRegisterService.LookUpPartyId(recipientId, cancellationToken); + partyId = (await _altinnRegisterService.LookUpPartyId(recipientId, cancellationToken))?.ToString(); } var cloudEvent = CreateCloudEvent(type, resourceId, itemId, partyId, recipientId, eventSource); diff --git a/src/Altinn.Correspondence.Integrations/Altinn/Register/AltinnRegisterDevService.cs b/src/Altinn.Correspondence.Integrations/Altinn/Register/AltinnRegisterDevService.cs index 5e26408b..93df8311 100644 --- a/src/Altinn.Correspondence.Integrations/Altinn/Register/AltinnRegisterDevService.cs +++ b/src/Altinn.Correspondence.Integrations/Altinn/Register/AltinnRegisterDevService.cs @@ -10,13 +10,13 @@ public class AltinnRegisterDevService : IAltinnRegisterService private const string _identificationIDPattern = @"^(?:\d{11}|\d{9}|0192:\d{9})$"; private static readonly Regex IdentificationIDRegex = new(_identificationIDPattern); private readonly int _digdirPartyId = 50952483; - public Task LookUpPartyId(string identificationId, CancellationToken cancellationToken) + public Task LookUpPartyId(string identificationId, CancellationToken cancellationToken) { if (IdentificationIDRegex.IsMatch(identificationId)) { - return Task.FromResult(_digdirPartyId.ToString()); + return Task.FromResult(_digdirPartyId); } - return Task.FromResult(null); + return Task.FromResult(null); } public Task LookUpName(string identificationId, CancellationToken cancellationToken) { diff --git a/src/Altinn.Correspondence.Integrations/Altinn/Register/AltinnRegisterService.cs b/src/Altinn.Correspondence.Integrations/Altinn/Register/AltinnRegisterService.cs index 7f944b04..0c37c945 100644 --- a/src/Altinn.Correspondence.Integrations/Altinn/Register/AltinnRegisterService.cs +++ b/src/Altinn.Correspondence.Integrations/Altinn/Register/AltinnRegisterService.cs @@ -21,10 +21,10 @@ public AltinnRegisterService(HttpClient httpClient, IOptions alti _logger = logger; } - public async Task LookUpPartyId(string identificationId, CancellationToken cancellationToken = default) + public async Task LookUpPartyId(string identificationId, CancellationToken cancellationToken = default) { var party = await LookUpPartyById(identificationId, cancellationToken); - return party?.PartyId.ToString(); + return party?.PartyId; } public async Task LookUpName(string identificationId, CancellationToken cancellationToken = default) diff --git a/src/Altinn.Correspondence.Integrations/Altinn/SblBridge/AltinnSblBridgeDevService.cs b/src/Altinn.Correspondence.Integrations/Altinn/SblBridge/AltinnSblBridgeDevService.cs new file mode 100644 index 00000000..ef8f3e15 --- /dev/null +++ b/src/Altinn.Correspondence.Integrations/Altinn/SblBridge/AltinnSblBridgeDevService.cs @@ -0,0 +1,20 @@ +using Altinn.Correspondence.Core.Services; + +namespace Altinn.Correspondence.Integrations.Altinn.SblBridge; +public class AltinnSblBridgeDevService : IAltinnSblBridgeService +{ + private readonly string _baseUrl; + public AltinnSblBridgeDevService(string baseUrl) + { + _baseUrl = baseUrl; + } + + public async Task AddPartyToSblBridge(int partyId, CancellationToken cancellationToken = default) + { + if (partyId <= 0) + { + return false; + } + return true; + } +} diff --git a/src/Altinn.Correspondence.Integrations/Altinn/SblBridge/AltinnSblBridgeService.cs b/src/Altinn.Correspondence.Integrations/Altinn/SblBridge/AltinnSblBridgeService.cs new file mode 100644 index 00000000..f0a18c0e --- /dev/null +++ b/src/Altinn.Correspondence.Integrations/Altinn/SblBridge/AltinnSblBridgeService.cs @@ -0,0 +1,33 @@ +using System.Net.Http.Headers; +using System.Text; +using Altinn.Correspondence.Core.Services; + + +namespace Altinn.Correspondence.Integrations.Altinn.SblBridge; +public class AltinnSblBridgeService : IAltinnSblBridgeService +{ + private readonly HttpClient _httpClient; + + public AltinnSblBridgeService(HttpClient httpClient) + { + _httpClient = httpClient; + _httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + } + + public async Task AddPartyToSblBridge(int partyId, CancellationToken cancellationToken = default) + { + if (partyId <= 0) + { + return false; + } + StringContent content = new StringContent(partyId.ToString(), Encoding.UTF8, "application/json"); + using var response = await _httpClient.PostAsync($"authorization/api/partieswithmessages", content, cancellationToken); + if (!response.IsSuccessStatusCode) + { + var statusCode = response.StatusCode; + var errorContent = await response.Content.ReadAsStringAsync(cancellationToken); + throw new Exception($"Error when adding party to SBL Bridge. Statuscode was: ${statusCode}, error was: ${errorContent}"); + } + return true; + } +} diff --git a/src/Altinn.Correspondence.Integrations/DependencyInjection.cs b/src/Altinn.Correspondence.Integrations/DependencyInjection.cs index cf80d4c2..e2ce1edb 100644 --- a/src/Altinn.Correspondence.Integrations/DependencyInjection.cs +++ b/src/Altinn.Correspondence.Integrations/DependencyInjection.cs @@ -11,6 +11,7 @@ using Altinn.Correspondence.Integrations.Altinn.Notifications; using Altinn.Correspondence.Integrations.Altinn.Register; using Altinn.Correspondence.Integrations.Altinn.ResourceRegistry; +using Altinn.Correspondence.Integrations.Altinn.SblBridge; using Altinn.Correspondence.Integrations.Dialogporten; using Altinn.Correspondence.Integrations.Slack; using Altinn.Correspondence.Repositories; @@ -37,7 +38,7 @@ public static void AddIntegrations(this IServiceCollection services, IConfigurat services.AddScoped(); services.AddScoped(); services.AddScoped(); - } + } else { var altinnOptions = new AltinnOptions(); @@ -54,11 +55,16 @@ public static void AddIntegrations(this IServiceCollection services, IConfigurat if (string.IsNullOrWhiteSpace(generalSettings.SlackUrl)) { services.AddSingleton(new SlackDevClient("")); - } + } else { services.AddSingleton(new SlackClient(generalSettings.SlackUrl)); } + if (string.IsNullOrWhiteSpace(generalSettings.AltinnSblBridgeBaseUrl)) + { + services.AddSingleton(new AltinnSblBridgeDevService("")); + } + else services.AddHttpClient(client => client.BaseAddress = new Uri(generalSettings.AltinnSblBridgeBaseUrl)); } public static void RegisterAltinnHttpClient(this IServiceCollection services, MaskinportenSettings maskinportenSettings, AltinnOptions altinnOptions) diff --git a/src/Altinn.Correspondence.Persistence/Data/ApplicationDbContext.cs b/src/Altinn.Correspondence.Persistence/Data/ApplicationDbContext.cs index 5ed01979..6ce83453 100644 --- a/src/Altinn.Correspondence.Persistence/Data/ApplicationDbContext.cs +++ b/src/Altinn.Correspondence.Persistence/Data/ApplicationDbContext.cs @@ -37,6 +37,7 @@ public ApplicationDbContext(DbContextOptions options) : base(options) public DbSet CorrespondenceReplyOptions { get; set; } public DbSet ExternalReferences { get; set; } public DbSet NotificationTemplates { get; set; } + public DbSet LegacyParties { get; set; } private bool IsAccessTokenValid() { diff --git a/src/Altinn.Correspondence.Persistence/DependencyInjection.cs b/src/Altinn.Correspondence.Persistence/DependencyInjection.cs index 90c6d8cd..b80d1e66 100644 --- a/src/Altinn.Correspondence.Persistence/DependencyInjection.cs +++ b/src/Altinn.Correspondence.Persistence/DependencyInjection.cs @@ -25,6 +25,7 @@ public static void AddPersistence(this IServiceCollection services, IConfigurati services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); } private static NpgsqlDataSource BuildAzureNpgsqlDataSource(IConfiguration config) diff --git a/src/Altinn.Correspondence.Persistence/Migrations/20241209072946_LegacyPartyId.Designer.cs b/src/Altinn.Correspondence.Persistence/Migrations/20241209072946_LegacyPartyId.Designer.cs new file mode 100644 index 00000000..3d93347c --- /dev/null +++ b/src/Altinn.Correspondence.Persistence/Migrations/20241209072946_LegacyPartyId.Designer.cs @@ -0,0 +1,524 @@ +// +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Altinn.Correspondence.Persistence.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20241209072946_LegacyPartyId")] + partial class LegacyPartyId + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("correspondence") + .HasAnnotation("ProductVersion", "8.0.6") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "hstore"); + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Altinn.Correspondence.Core.Models.Entities.AttachmentEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Checksum") + .HasColumnType("text"); + + b.Property("Created") + .HasColumnType("timestamp with time zone"); + + b.Property("DataLocationType") + .HasColumnType("integer"); + + b.Property("DataLocationUrl") + .HasColumnType("text"); + + b.Property("DataType") + .IsRequired() + .HasColumnType("text"); + + b.Property("FileName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("IsEncrypted") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ResourceId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Sender") + .IsRequired() + .HasColumnType("text"); + + b.Property("SendersReference") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("character varying(4096)"); + + b.HasKey("Id"); + + b.ToTable("Attachments", "correspondence"); + }); + + modelBuilder.Entity("Altinn.Correspondence.Core.Models.Entities.AttachmentStatusEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AttachmentId") + .HasColumnType("uuid"); + + b.Property("PartyUuid") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StatusChanged") + .HasColumnType("timestamp with time zone"); + + b.Property("StatusText") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("AttachmentId"); + + b.ToTable("AttachmentStatuses", "correspondence"); + }); + + modelBuilder.Entity("Altinn.Correspondence.Core.Models.Entities.CorrespondenceAttachmentEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AttachmentId") + .HasColumnType("uuid"); + + b.Property("CorrespondenceContentId") + .HasColumnType("uuid"); + + b.Property("Created") + .HasColumnType("timestamp with time zone"); + + b.Property("ExpirationTime") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("AttachmentId"); + + b.HasIndex("CorrespondenceContentId"); + + b.ToTable("CorrespondenceAttachments", "correspondence"); + }); + + modelBuilder.Entity("Altinn.Correspondence.Core.Models.Entities.CorrespondenceContentEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CorrespondenceId") + .HasColumnType("uuid"); + + b.Property("Language") + .IsRequired() + .HasColumnType("text"); + + b.Property("MessageBody") + .IsRequired() + .HasColumnType("text"); + + b.Property("MessageSummary") + .IsRequired() + .HasColumnType("text"); + + b.Property("MessageTitle") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("CorrespondenceId") + .IsUnique(); + + b.ToTable("CorrespondenceContents", "correspondence"); + }); + + modelBuilder.Entity("Altinn.Correspondence.Core.Models.Entities.CorrespondenceEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AllowSystemDeleteAfter") + .HasColumnType("timestamp with time zone"); + + b.Property("Altinn2CorrespondenceId") + .HasColumnType("integer"); + + b.Property("Created") + .HasColumnType("timestamp with time zone"); + + b.Property("DueDateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("IgnoreReservation") + .HasColumnType("boolean"); + + b.Property("IsConfirmationNeeded") + .HasColumnType("boolean"); + + b.Property("MessageSender") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property>("PropertyList") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("hstore"); + + b.Property("Published") + .HasColumnType("timestamp with time zone"); + + b.Property("Recipient") + .IsRequired() + .HasColumnType("text"); + + b.Property("RequestedPublishTime") + .HasColumnType("timestamp with time zone"); + + b.Property("ResourceId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Sender") + .IsRequired() + .HasColumnType("text"); + + b.Property("SendersReference") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("character varying(4096)"); + + b.HasKey("Id"); + + b.ToTable("Correspondences", "correspondence"); + }); + + modelBuilder.Entity("Altinn.Correspondence.Core.Models.Entities.CorrespondenceNotificationEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Altinn2NotificationId") + .HasColumnType("integer"); + + b.Property("CorrespondenceId") + .HasColumnType("uuid"); + + b.Property("Created") + .HasColumnType("timestamp with time zone"); + + b.Property("IsReminder") + .HasColumnType("boolean"); + + b.Property("NotificationAddress") + .HasColumnType("text"); + + b.Property("NotificationChannel") + .HasColumnType("integer"); + + b.Property("NotificationOrderId") + .HasColumnType("uuid"); + + b.Property("NotificationSent") + .HasColumnType("timestamp with time zone"); + + b.Property("NotificationTemplate") + .HasColumnType("integer"); + + b.Property("RequestedSendTime") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("CorrespondenceId"); + + b.ToTable("CorrespondenceNotifications", "correspondence"); + }); + + modelBuilder.Entity("Altinn.Correspondence.Core.Models.Entities.CorrespondenceReplyOptionEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CorrespondenceId") + .HasColumnType("uuid"); + + b.Property("LinkText") + .HasColumnType("text"); + + b.Property("LinkURL") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("CorrespondenceId"); + + b.ToTable("CorrespondenceReplyOptions", "correspondence"); + }); + + modelBuilder.Entity("Altinn.Correspondence.Core.Models.Entities.CorrespondenceStatusEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CorrespondenceId") + .HasColumnType("uuid"); + + b.Property("PartyUuid") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StatusChanged") + .HasColumnType("timestamp with time zone"); + + b.Property("StatusText") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("CorrespondenceId"); + + b.ToTable("CorrespondenceStatuses", "correspondence"); + }); + + modelBuilder.Entity("Altinn.Correspondence.Core.Models.Entities.ExternalReferenceEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CorrespondenceId") + .HasColumnType("uuid"); + + b.Property("ReferenceType") + .HasColumnType("integer"); + + b.Property("ReferenceValue") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("CorrespondenceId"); + + b.ToTable("ExternalReferences", "correspondence"); + }); + + modelBuilder.Entity("Altinn.Correspondence.Core.Models.Entities.LegacyPartyEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("PartyId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("LegacyParties", "correspondence"); + }); + + modelBuilder.Entity("Altinn.Correspondence.Core.Models.Entities.NotificationTemplateEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("EmailBody") + .IsRequired() + .HasColumnType("text"); + + b.Property("EmailSubject") + .IsRequired() + .HasColumnType("text"); + + b.Property("Language") + .HasColumnType("text"); + + b.Property("RecipientType") + .HasColumnType("integer"); + + b.Property("ReminderEmailBody") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReminderEmailSubject") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReminderSmsBody") + .IsRequired() + .HasColumnType("text"); + + b.Property("SmsBody") + .IsRequired() + .HasColumnType("text"); + + b.Property("Template") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("NotificationTemplates", "correspondence"); + }); + + modelBuilder.Entity("Altinn.Correspondence.Core.Models.Entities.AttachmentStatusEntity", b => + { + b.HasOne("Altinn.Correspondence.Core.Models.Entities.AttachmentEntity", "Attachment") + .WithMany("Statuses") + .HasForeignKey("AttachmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Attachment"); + }); + + modelBuilder.Entity("Altinn.Correspondence.Core.Models.Entities.CorrespondenceAttachmentEntity", b => + { + b.HasOne("Altinn.Correspondence.Core.Models.Entities.AttachmentEntity", "Attachment") + .WithMany("CorrespondenceAttachments") + .HasForeignKey("AttachmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Altinn.Correspondence.Core.Models.Entities.CorrespondenceContentEntity", "CorrespondenceContent") + .WithMany("Attachments") + .HasForeignKey("CorrespondenceContentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Attachment"); + + b.Navigation("CorrespondenceContent"); + }); + + modelBuilder.Entity("Altinn.Correspondence.Core.Models.Entities.CorrespondenceContentEntity", b => + { + b.HasOne("Altinn.Correspondence.Core.Models.Entities.CorrespondenceEntity", "Correspondence") + .WithOne("Content") + .HasForeignKey("Altinn.Correspondence.Core.Models.Entities.CorrespondenceContentEntity", "CorrespondenceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Correspondence"); + }); + + modelBuilder.Entity("Altinn.Correspondence.Core.Models.Entities.CorrespondenceNotificationEntity", b => + { + b.HasOne("Altinn.Correspondence.Core.Models.Entities.CorrespondenceEntity", "Correspondence") + .WithMany("Notifications") + .HasForeignKey("CorrespondenceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Correspondence"); + }); + + modelBuilder.Entity("Altinn.Correspondence.Core.Models.Entities.CorrespondenceReplyOptionEntity", b => + { + b.HasOne("Altinn.Correspondence.Core.Models.Entities.CorrespondenceEntity", "Correspondence") + .WithMany("ReplyOptions") + .HasForeignKey("CorrespondenceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Correspondence"); + }); + + modelBuilder.Entity("Altinn.Correspondence.Core.Models.Entities.CorrespondenceStatusEntity", b => + { + b.HasOne("Altinn.Correspondence.Core.Models.Entities.CorrespondenceEntity", "Correspondence") + .WithMany("Statuses") + .HasForeignKey("CorrespondenceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Correspondence"); + }); + + modelBuilder.Entity("Altinn.Correspondence.Core.Models.Entities.ExternalReferenceEntity", b => + { + b.HasOne("Altinn.Correspondence.Core.Models.Entities.CorrespondenceEntity", "Correspondence") + .WithMany("ExternalReferences") + .HasForeignKey("CorrespondenceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Correspondence"); + }); + + modelBuilder.Entity("Altinn.Correspondence.Core.Models.Entities.AttachmentEntity", b => + { + b.Navigation("CorrespondenceAttachments"); + + b.Navigation("Statuses"); + }); + + modelBuilder.Entity("Altinn.Correspondence.Core.Models.Entities.CorrespondenceContentEntity", b => + { + b.Navigation("Attachments"); + }); + + modelBuilder.Entity("Altinn.Correspondence.Core.Models.Entities.CorrespondenceEntity", b => + { + b.Navigation("Content"); + + b.Navigation("ExternalReferences"); + + b.Navigation("Notifications"); + + b.Navigation("ReplyOptions"); + + b.Navigation("Statuses"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Altinn.Correspondence.Persistence/Migrations/20241209072946_LegacyPartyId.cs b/src/Altinn.Correspondence.Persistence/Migrations/20241209072946_LegacyPartyId.cs new file mode 100644 index 00000000..7aab0243 --- /dev/null +++ b/src/Altinn.Correspondence.Persistence/Migrations/20241209072946_LegacyPartyId.cs @@ -0,0 +1,43 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Altinn.Correspondence.Persistence.Migrations +{ + /// + public partial class LegacyPartyId : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "LegacyParties", + schema: "correspondence", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + PartyId = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_LegacyParties", x => x.Id); + table.UniqueConstraint("UK_LegacyParties_PartyId", x => x.PartyId); + }); + + migrationBuilder.CreateIndex( + name: "IX_LegacyParties_PartyId", + schema: "correspondence", + table: "LegacyParties", + column: "PartyId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "LegacyParties", + schema: "correspondence"); + } + } +} diff --git a/src/Altinn.Correspondence.Persistence/Migrations/ApplicationDbContextModelSnapshot.cs b/src/Altinn.Correspondence.Persistence/Migrations/ApplicationDbContextModelSnapshot.cs index 23c02371..8ed290f5 100644 --- a/src/Altinn.Correspondence.Persistence/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/src/Altinn.Correspondence.Persistence/Migrations/ApplicationDbContextModelSnapshot.cs @@ -346,6 +346,20 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("ExternalReferences", "correspondence"); }); + modelBuilder.Entity("Altinn.Correspondence.Core.Models.Entities.LegacyPartyEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("PartyId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("LegacyParties", "correspondence"); + }); + modelBuilder.Entity("Altinn.Correspondence.Core.Models.Entities.NotificationTemplateEntity", b => { b.Property("Id") diff --git a/src/Altinn.Correspondence.Persistence/Repositories/LegacyPartyRepository.cs b/src/Altinn.Correspondence.Persistence/Repositories/LegacyPartyRepository.cs new file mode 100644 index 00000000..cff4b793 --- /dev/null +++ b/src/Altinn.Correspondence.Persistence/Repositories/LegacyPartyRepository.cs @@ -0,0 +1,22 @@ +using Altinn.Correspondence.Core.Models.Entities; +using Altinn.Correspondence.Core.Repositories; +using Microsoft.EntityFrameworkCore; + +namespace Altinn.Correspondence.Persistence.Repositories +{ + public class LegacyPartyRepository(ApplicationDbContext context) : ILegacyPartyRepository + { + private readonly ApplicationDbContext _context = context; + + public async Task AddLegacyPartyId(int id, CancellationToken cancellationToken) + { + await _context.LegacyParties.AddAsync(new LegacyPartyEntity { PartyId = id }, cancellationToken); + await _context.SaveChangesAsync(cancellationToken); + } + + public async Task PartyAlreadyExists(int partyId, CancellationToken cancellationToken) + { + return await _context.LegacyParties.AnyAsync(p => p.PartyId == partyId, cancellationToken); + } + } +} \ No newline at end of file