Skip to content

Commit 0379ca8

Browse files
committed
New experiment
1 parent 351ad4f commit 0379ca8

File tree

5 files changed

+171
-2
lines changed

5 files changed

+171
-2
lines changed

src/Altinn.App.Core/Features/Correspondence/CorrespondenceClient.cs

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
1+
using System;
12
using System.Diagnostics;
23
using System.Net;
34
using System.Net.Http.Headers;
5+
using System.Net.Http.Json;
6+
using System.Net.Mail;
47
using System.Text.Json;
58
using Altinn.App.Core.Configuration;
69
using Altinn.App.Core.Constants;
710
using Altinn.App.Core.Features.Correspondence.Exceptions;
811
using Altinn.App.Core.Features.Correspondence.Models;
12+
using Altinn.App.Core.Features.Correspondence.Models.Response;
913
using Altinn.App.Core.Models;
14+
using Altinn.App.Core.Models.Notifications.Email;
1015
using Microsoft.AspNetCore.Mvc;
1116
using Microsoft.Extensions.Logging;
1217
using Microsoft.Extensions.Options;
@@ -50,6 +55,70 @@ public async Task<SendCorrespondenceResponse> Send(
5055

5156
try
5257
{
58+
if (
59+
payload.CorrespondenceRequest.Content.Attachments?.Count > 0
60+
&& payload.CorrespondenceRequest.Content.Attachments.All(a => a is CorrespondenceStreamedAttachment)
61+
)
62+
{
63+
var pollingJobs = new List<Task>();
64+
var premadeAttachments = new List<Guid>();
65+
foreach (
66+
CorrespondenceStreamedAttachment attachment in payload.CorrespondenceRequest.Content.Attachments
67+
)
68+
{
69+
var initializeAttachmentPayload = new AttachmentPayload
70+
{
71+
DisplayName = attachment.Filename,
72+
FileName = attachment.Filename,
73+
IsEncrypted = attachment.IsEncrypted ?? false,
74+
ResourceId = payload.CorrespondenceRequest.ResourceId,
75+
SendersReference = payload.CorrespondenceRequest.SendersReference + " - " + attachment.Filename,
76+
};
77+
var initializeAttachmentRequest = await AuthenticatedHttpRequestFactory(
78+
method: HttpMethod.Post,
79+
uri: GetUri("attachment"),
80+
content: new StringContent(
81+
JsonSerializer.Serialize(initializeAttachmentPayload),
82+
new MediaTypeHeaderValue("application/json")
83+
),
84+
payload: payload
85+
);
86+
var initializeAttachmentResponse = await HandleServerCommunication<SendCorrespondenceResponse>(
87+
initializeAttachmentRequest,
88+
cancellationToken
89+
);
90+
var initializeAttachmentContent = initializeAttachmentRequest.Content;
91+
if (initializeAttachmentContent is null)
92+
{
93+
throw new CorrespondenceRequestException(
94+
"Attachment initialization request did not return content.",
95+
null,
96+
HttpStatusCode.InternalServerError,
97+
"No content returned from attachment initialization"
98+
);
99+
}
100+
var attachmentId = await initializeAttachmentContent.ReadAsStringAsync(cancellationToken);
101+
attachmentId = attachmentId.Trim('"');
102+
var attachmentDataContent = new StreamContent(attachment.Data);
103+
attachmentDataContent.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
104+
var uploadAttachmentRequest = await AuthenticatedHttpRequestFactory(
105+
method: HttpMethod.Post,
106+
uri: GetUri("attachment/upload"),
107+
content: attachmentDataContent,
108+
payload: payload
109+
);
110+
var uploadAttachmentResponse = await HandleServerCommunication<SendCorrespondenceResponse>(
111+
uploadAttachmentRequest,
112+
cancellationToken
113+
);
114+
if (Guid.TryParse(attachmentId, out var guidId))
115+
{
116+
pollingJobs.Add(PollAttachmentStatus(guidId, payload, cancellationToken));
117+
}
118+
}
119+
await Task.WhenAll(pollingJobs);
120+
payload.CorrespondenceRequest.ExistingAttachments = premadeAttachments;
121+
}
53122
using MultipartFormDataContent content = payload.CorrespondenceRequest.Serialise();
54123
using HttpRequestMessage request = await AuthenticatedHttpRequestFactory(
55124
method: HttpMethod.Post,
@@ -88,6 +157,47 @@ public async Task<SendCorrespondenceResponse> Send(
88157
}
89158
}
90159

160+
/// <inheritdoc />
161+
private async Task PollAttachmentStatus(
162+
Guid attachmentId,
163+
CorrespondencePayloadBase payload,
164+
CancellationToken cancellationToken
165+
)
166+
{
167+
const int maxAttempts = 3;
168+
const int delaySeconds = 5;
169+
170+
for (int attempt = 1; attempt <= maxAttempts; attempt++)
171+
{
172+
await Task.Delay(TimeSpan.FromSeconds(delaySeconds), cancellationToken);
173+
174+
var statusResponse = await AuthenticatedHttpRequestFactory(
175+
method: HttpMethod.Get,
176+
uri: GetUri($"attachment/{attachmentId}"),
177+
content: null,
178+
payload: payload
179+
);
180+
var pollContent = statusResponse.Content;
181+
182+
if (pollContent is null)
183+
{
184+
break;
185+
}
186+
187+
var statusContent = await pollContent.ReadFromJsonAsync<AttachmentOverview>(cancellationToken);
188+
if (statusContent?.Status == "Published")
189+
{
190+
return;
191+
}
192+
}
193+
throw new CorrespondenceRequestException(
194+
$"Failure when uploading attachment. Attachment was not published in time.",
195+
null,
196+
HttpStatusCode.InternalServerError,
197+
"Polling failed"
198+
);
199+
}
200+
91201
/// <inheritdoc/>
92202
public async Task<GetCorrespondenceStatusResponse> GetStatus(
93203
GetCorrespondenceStatusPayload payload,
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
namespace Altinn.App.Core.Features.Correspondence.Models;
2+
3+
/// <summary>
4+
/// Represents the payload for sending an attachment.
5+
/// </summary>
6+
internal class AttachmentPayload
7+
{
8+
/// <summary>
9+
/// Gets or sets the Resource Id for the correspondence service.
10+
/// </summary>
11+
public required string ResourceId { get; set; }
12+
13+
/// <summary>
14+
/// The name of the attachment file.
15+
/// </summary>
16+
public string? FileName { get; set; }
17+
18+
/// <summary>
19+
/// A logical name for the file, which will be shown in Altinn Inbox.
20+
/// </summary>
21+
public string? DisplayName { get; set; }
22+
23+
/// <summary>
24+
/// A value indicating whether the attachment is encrypted or not.
25+
/// </summary>
26+
public required bool IsEncrypted { get; set; }
27+
28+
/// <summary>
29+
/// A reference value given to the attachment by the creator.
30+
/// </summary>
31+
public required string SendersReference { get; set; }
32+
}

src/Altinn.App.Core/Features/Correspondence/Models/CorrespondenceRequest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ public sealed record CorrespondenceRequest : MultipartCorrespondenceItem
308308
/// <summary>
309309
/// Existing attachments that should be added to the correspondence.
310310
/// </summary>
311-
public IReadOnlyList<Guid>? ExistingAttachments { get; init; }
311+
public IReadOnlyList<Guid>? ExistingAttachments { get; set; }
312312

313313
/// <summary>
314314
/// Serialises the entire <see cref="CorrespondenceRequest"/> object to a provided <see cref="MultipartFormDataContent"/> instance.
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
namespace Altinn.App.Core.Features.Correspondence.Models.Response;
2+
3+
/// <summary>
4+
/// Status of an attachment
5+
/// </summary>
6+
internal class AttachmentOverview
7+
{
8+
/// <summary>
9+
/// Unique Id for this attachment
10+
/// </summary>
11+
public required Guid AttachmentId { get; set; }
12+
13+
/// <summary>
14+
/// Current attachment status
15+
/// </summary>
16+
public required string Status { get; set; }
17+
18+
/// <summary>
19+
/// Current attachment status text description
20+
/// </summary>
21+
public required string StatusText { get; set; }
22+
23+
/// <summary>
24+
/// Timestamp for when the Current Attachment Status was changed
25+
/// </summary>
26+
public required DateTimeOffset StatusChanged { get; set; }
27+
}

test/Altinn.App.Core.Tests/PublicApiTests.PublicApi_ShouldNotChange_Unintentionally.verified.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1009,7 +1009,7 @@ namespace Altinn.App.Core.Features.Correspondence.Models
10091009
public required System.DateTimeOffset? AllowSystemDeleteAfter { get; init; }
10101010
public required Altinn.App.Core.Features.Correspondence.Models.CorrespondenceContent Content { get; init; }
10111011
public System.DateTimeOffset? DueDateTime { get; init; }
1012-
public System.Collections.Generic.IReadOnlyList<System.Guid>? ExistingAttachments { get; init; }
1012+
public System.Collections.Generic.IReadOnlyList<System.Guid>? ExistingAttachments { get; set; }
10131013
public System.Collections.Generic.IReadOnlyList<Altinn.App.Core.Features.Correspondence.Models.CorrespondenceExternalReference>? ExternalReferences { get; init; }
10141014
public bool? IgnoreReservation { get; init; }
10151015
public bool? IsConfirmationNeeded { get; init; }

0 commit comments

Comments
 (0)