Skip to content

Commit 7ba28fc

Browse files
authored
Updated LegacyFileController with new method that takes multiple file… (#784)
* Updated LegacyFileController with new method that takes multiple filereferences and returns multiple files * update based on automated comments * ensuring distinctids
1 parent ae3047c commit 7ba28fc

File tree

8 files changed

+261
-10
lines changed

8 files changed

+261
-10
lines changed

src/Altinn.Broker.API/Controllers/LegacyFileController.cs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ CancellationToken cancellationToken
6565
{
6666
return Problem("Content-length header is required");
6767
}
68+
6869
var commandResult = await handler.Process(new UploadFileRequest()
6970
{
7071
FileTransferId = fileTransferId,
@@ -79,6 +80,38 @@ CancellationToken cancellationToken
7980
);
8081
}
8182

83+
[HttpPost]
84+
[Route("overviews")]
85+
public async Task<ActionResult<List<LegacyFileOverviewExt>>> GetFileOverviews(
86+
[FromBody] List<Guid> fileTransferIds,
87+
[FromQuery] string onBehalfOfConsumer,
88+
[FromServices] GetFileTransferOverviewHandler handler,
89+
CancellationToken cancellationToken)
90+
{
91+
if (fileTransferIds is null || fileTransferIds.Count == 0)
92+
{
93+
return Problem("At least one fileTransferId must be provided.", statusCode: 400);
94+
}
95+
if (string.IsNullOrWhiteSpace(onBehalfOfConsumer))
96+
{
97+
return Problem("Query parameter 'onBehalfOfConsumer' is required.", statusCode: 400);
98+
}
99+
100+
var distinctIds = fileTransferIds.Distinct().ToList();
101+
logger.LogInformation("Legacy - Getting file overviews for {Count}, starting with {FirstId}", distinctIds.Count, distinctIds[0]);
102+
var queryResult = await handler.ProcessMultiple(new GetFileTransferOverviewRequest()
103+
{
104+
FileTransferIds = distinctIds,
105+
IsLegacy = true,
106+
OnBehalfOfConsumer = onBehalfOfConsumer
107+
}, HttpContext.User, cancellationToken);
108+
return queryResult.Match(
109+
result => Ok(LegacyFileStatusOverviewExtMapper.MapToExternalModels(result.FileTransfers)),
110+
Problem
111+
);
112+
}
113+
114+
82115
/// <summary>
83116
/// Get information about the file and its current status
84117
/// </summary>

src/Altinn.Broker.API/Mappers/LegacyFileStatusOverviewExtMapper.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ namespace Altinn.Broker.Mappers;
99

1010
internal static class LegacyFileStatusOverviewExtMapper
1111
{
12+
internal static List<LegacyFileOverviewExt> MapToExternalModels(IReadOnlyList<FileTransferEntity> fileTransfers)
13+
{
14+
return [.. fileTransfers.Select(MapToExternalModel)];
15+
}
16+
1217
internal static LegacyFileOverviewExt MapToExternalModel(FileTransferEntity fileTransfer)
1318
{
1419
return new LegacyFileOverviewExt()

src/Altinn.Broker.Application/GetFileTransferOverview/GetFileTransferOverviewHandler.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,33 @@ public async Task<OneOf<GetFileTransferOverviewResponse, Error>> Process(GetFile
3939
FileTransfer = fileTransfer
4040
};
4141
}
42+
43+
public async Task<OneOf<GetFileTransferOverviewsResponse, Error>> ProcessMultiple(GetFileTransferOverviewRequest request, ClaimsPrincipal? user, CancellationToken cancellationToken)
44+
{
45+
var ids = request.FileTransferIds ?? [];
46+
logger.LogInformation("Retrieving file overview for {Transfers} file transfers. Legacy: {Legacy}", ids.Count, request.IsLegacy);
47+
var fileTransfers = await TransactionWithRetriesPolicy.Execute(
48+
async (cancellationToken) => await fileTransferRepository.GetFileTransfers(ids, cancellationToken),
49+
logger,
50+
cancellationToken);
51+
if (fileTransfers is null)
52+
{
53+
return Errors.FileTransferNotFound;
54+
}
55+
var accessChecks = await Task.WhenAll(
56+
fileTransfers.Select(ft => authorizationService.CheckAccessAsSenderOrRecipient(user, ft, request.IsLegacy, cancellationToken)));
57+
if (accessChecks.Any(allowed => !allowed))
58+
{
59+
return Errors.NoAccessToResource;
60+
}
61+
if (request.IsLegacy && !string.IsNullOrWhiteSpace(request.OnBehalfOfConsumer)
62+
&& fileTransfers.Any(ft => !ft.IsSenderOrRecipient(request.OnBehalfOfConsumer!)))
63+
{
64+
return Errors.NoAccessToResource;
65+
}
66+
return new GetFileTransferOverviewsResponse()
67+
{
68+
FileTransfers = fileTransfers
69+
};
70+
}
4271
}

src/Altinn.Broker.Application/GetFileTransferOverview/GetFileTransferOverviewRequest.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ namespace Altinn.Broker.Application.GetFileTransferOverview;
44

55
public class GetFileTransferOverviewRequest
66
{
7+
public List<Guid>? FileTransferIds { get; set; }
78
public Guid FileTransferId { get; set; }
89
public bool IsLegacy { get; set; }
910
public string? OnBehalfOfConsumer { get; set; }
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
using Altinn.Broker.Core.Domain;
2+
3+
namespace Altinn.Broker.Application.GetFileTransferOverview;
4+
5+
public class GetFileTransferOverviewsResponse
6+
{
7+
public required IReadOnlyList<FileTransferEntity> FileTransfers { get; set; }
8+
}

src/Altinn.Broker.Core/Repositories/IFileTransferRepository.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ Task<Guid> AddFileTransfer(
1717
bool useVirusScan,
1818
CancellationToken cancellationToken);
1919
Task<Domain.FileTransferEntity?> GetFileTransfer(Guid fileTransferId, CancellationToken cancellationToken);
20+
Task<IReadOnlyList<FileTransferEntity>> GetFileTransfers(IReadOnlyCollection<Guid> fileTransferIds, CancellationToken cancellationToken);
2021
Task<List<Guid>> GetFileTransfersAssociatedWithActor(FileTransferSearchEntity fileTransferSearch, CancellationToken cancellationToken);
2122
Task<List<Guid>> GetFileTransfersForRecipientWithRecipientStatus(FileTransferSearchEntity fileTransferSearch, CancellationToken cancellationToken);
2223
Task<List<Guid>> LegacyGetFilesForRecipientsWithRecipientStatus(LegacyFileSearchEntity fileTransferSearch, CancellationToken cancellationToken);

src/Altinn.Broker.Persistence/Repositories/FileTransferRepository.cs

Lines changed: 120 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,54 @@ namespace Altinn.Broker.Persistence.Repositories;
1515

1616
public class FileTransferRepository(NpgsqlDataSource dataSource, IActorRepository actorRepository, ExecuteDBCommandWithRetries commandExecutor) : IFileTransferRepository
1717
{
18-
public async Task<FileTransferEntity?> GetFileTransfer(Guid fileTransferId, CancellationToken cancellationToken)
19-
{
20-
await using var command = dataSource.CreateCommand(
21-
@"
18+
#region constants
19+
const string overviewCommandMultiple = @"
20+
SELECT
21+
f.file_transfer_id_pk,
22+
f.resource_id,
23+
f.filename,
24+
f.checksum,
25+
f.sender_actor_id_fk,
26+
f.external_file_transfer_reference,
27+
f.created,
28+
f.file_location,
29+
f.file_transfer_size,
30+
f.expiration_time,
31+
f.hangfire_job_id,
32+
f.use_virus_scan,
33+
sender.actor_external_id as senderActorExternalReference,
34+
fs_latest.file_transfer_status_description_id_fk,
35+
fs_latest.file_transfer_status_date,
36+
fs_latest.file_transfer_status_detailed_description
37+
FROM
38+
broker.file_transfer f
39+
INNER JOIN
40+
broker.actor sender ON sender.actor_id_pk = f.sender_actor_id_fk
41+
LEFT JOIN
42+
(
43+
SELECT
44+
fs.file_transfer_id_fk,
45+
fs.file_transfer_status_description_id_fk,
46+
fs.file_transfer_status_date,
47+
fs.file_transfer_status_detailed_description
48+
FROM
49+
broker.file_transfer_status fs
50+
INNER JOIN
51+
(
52+
SELECT
53+
file_transfer_id_fk,
54+
MAX(file_transfer_status_date) as max_date
55+
FROM
56+
broker.file_transfer_status
57+
GROUP BY
58+
file_transfer_id_fk
59+
) fs_max ON fs.file_transfer_id_fk = fs_max.file_transfer_id_fk AND fs.file_transfer_status_date = fs_max.max_date
60+
WHERE
61+
fs.file_transfer_id_fk IN (select unnest(@fileTransferIds))
62+
) fs_latest ON f.file_transfer_id_pk = fs_latest.file_transfer_id_fk
63+
WHERE
64+
f.file_transfer_id_pk IN (select unnest(@fileTransferIds));";
65+
const string overviewCommandSingle = @"
2266
SELECT
2367
f.file_transfer_id_pk,
2468
f.resource_id,
@@ -63,16 +107,82 @@ GROUP BY
63107
fs.file_transfer_id_fk = @fileTransferId
64108
) fs_latest ON f.file_transfer_id_pk = fs_latest.file_transfer_id_fk
65109
WHERE
66-
f.file_transfer_id_pk = @fileTransferId;");
110+
f.file_transfer_id_pk = @fileTransferId;";
111+
#endregion
112+
public async Task<IReadOnlyList<FileTransferEntity>> GetFileTransfers(IReadOnlyCollection<Guid> fileTransferIds, CancellationToken cancellationToken)
113+
{
114+
if (fileTransferIds is null || fileTransferIds.Count == 0)
115+
{
116+
return new List<FileTransferEntity>(0);
117+
}
67118

119+
await using var command = dataSource.CreateCommand(overviewCommandMultiple);
120+
121+
var parameter = new NpgsqlParameter("@fileTransferIds", NpgsqlDbType.Array | NpgsqlDbType.Uuid)
122+
{
123+
Value = fileTransferIds.ToArray()
124+
};
125+
126+
command.Parameters.Add(parameter);
127+
return await commandExecutor.ExecuteWithRetry(async (ct) =>
128+
{
129+
List<FileTransferEntity> fileTransferEntities = new List<FileTransferEntity>();
130+
FileTransferEntity? fileTransfer = null;
131+
132+
await using var reader = await command.ExecuteReaderAsync(ct);
133+
while (await reader.ReadAsync(ct))
134+
{
135+
var fileTransferId = reader.GetGuid(reader.GetOrdinal("file_transfer_id_pk"));
136+
fileTransfer = new FileTransferEntity
137+
{
138+
FileTransferId = fileTransferId,
139+
ResourceId = reader.GetString(reader.GetOrdinal("resource_id")),
140+
FileName = reader.GetString(reader.GetOrdinal("filename")),
141+
Checksum = reader.IsDBNull(reader.GetOrdinal("checksum")) ? null : reader.GetString(reader.GetOrdinal("checksum")),
142+
SendersFileTransferReference = reader.GetString(reader.GetOrdinal("external_file_transfer_reference")),
143+
HangfireJobId = reader.IsDBNull(reader.GetOrdinal("hangfire_job_id")) ? null : reader.GetString(reader.GetOrdinal("hangfire_job_id")),
144+
FileTransferStatusEntity = new FileTransferStatusEntity()
145+
{
146+
FileTransferId = fileTransferId,
147+
Status = (FileTransferStatus)reader.GetInt32(reader.GetOrdinal("file_transfer_status_description_id_fk")),
148+
Date = reader.GetDateTime(reader.GetOrdinal("file_transfer_status_date")),
149+
DetailedStatus = reader.IsDBNull(reader.GetOrdinal("file_transfer_status_detailed_description")) ? null : reader.GetString(reader.GetOrdinal("file_transfer_status_detailed_description"))
150+
},
151+
FileTransferStatusChanged = reader.GetDateTime(reader.GetOrdinal("file_transfer_status_date")),
152+
Created = reader.GetDateTime(reader.GetOrdinal("created")),
153+
ExpirationTime = reader.GetDateTime(reader.GetOrdinal("expiration_time")),
154+
FileLocation = reader.IsDBNull(reader.GetOrdinal("file_location")) ? null : reader.GetString(reader.GetOrdinal("file_location")),
155+
FileTransferSize = reader.IsDBNull(reader.GetOrdinal("file_transfer_size")) ? 0 : reader.GetInt64(reader.GetOrdinal("file_transfer_size")),
156+
Sender = new ActorEntity()
157+
{
158+
ActorId = reader.GetInt64(reader.GetOrdinal("sender_actor_id_fk")),
159+
ActorExternalId = reader.GetString(reader.GetOrdinal("senderActorExternalReference"))
160+
},
161+
RecipientCurrentStatuses = await GetLatestRecipientFileTransferStatuses(fileTransferId, ct),
162+
PropertyList = await GetMetadata(fileTransferId, ct),
163+
UseVirusScan = reader.GetBoolean(reader.GetOrdinal("use_virus_scan"))
164+
};
165+
166+
fileTransferEntities.Add(fileTransfer);
167+
EnrichLogs(fileTransfer);
168+
}
169+
170+
return fileTransferEntities;
171+
}, cancellationToken);
172+
}
173+
174+
public async Task<FileTransferEntity?> GetFileTransfer(Guid fileTransferId, CancellationToken cancellationToken)
175+
{
176+
await using var command = dataSource.CreateCommand(overviewCommandSingle);
177+
68178
command.Parameters.AddWithValue("@fileTransferId", fileTransferId);
69-
70-
return await commandExecutor.ExecuteWithRetry(async (ct) =>
179+
180+
return await commandExecutor.ExecuteWithRetry(async (ct) =>
71181
{
72182
FileTransferEntity? fileTransfer = null;
73-
183+
74184
await using var reader = await command.ExecuteReaderAsync(ct);
75-
185+
76186
if (await reader.ReadAsync(ct))
77187
{
78188
fileTransfer = new FileTransferEntity
@@ -104,7 +214,7 @@ GROUP BY
104214
PropertyList = await GetMetadata(fileTransferId, ct),
105215
UseVirusScan = reader.GetBoolean(reader.GetOrdinal("use_virus_scan"))
106216
};
107-
217+
108218
EnrichLogs(fileTransfer);
109219
}
110220

tests/Altinn.Broker.Tests/LegacyFileControllerTests.cs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,70 @@ public async Task GetFileOverview_SentByA3Sender_Success()
317317
Assert.Equal(fileTransferId, fileData.FileId.ToString());
318318
}
319319

320+
[Fact]
321+
public async Task GetFileOverview_MultipleSentByA3Sender_Success()
322+
{
323+
// Arrange
324+
var file = FileTransferInitializeExtTestFactory.BasicFileTransfer();
325+
var initializeFileResponse = await _senderClient.PostAsJsonAsync("broker/api/v1/filetransfer", FileTransferInitializeExtTestFactory.BasicFileTransfer());
326+
Assert.True(initializeFileResponse.IsSuccessStatusCode, await initializeFileResponse.Content.ReadAsStringAsync());
327+
var initializeFileResponse2 = await _senderClient.PostAsJsonAsync("broker/api/v1/filetransfer", FileTransferInitializeExtTestFactory.BasicFileTransfer());
328+
Assert.True(initializeFileResponse2.IsSuccessStatusCode, await initializeFileResponse.Content.ReadAsStringAsync());
329+
var initializeFileResponse3 = await _senderClient.PostAsJsonAsync("broker/api/v1/filetransfer", FileTransferInitializeExtTestFactory.BasicFileTransfer());
330+
Assert.True(initializeFileResponse3.IsSuccessStatusCode, await initializeFileResponse.Content.ReadAsStringAsync());
331+
var initializeFileResponse4 = await _senderClient.PostAsJsonAsync("broker/api/v1/filetransfer", FileTransferInitializeExtTestFactory.BasicFileTransfer());
332+
Assert.True(initializeFileResponse4.IsSuccessStatusCode, await initializeFileResponse.Content.ReadAsStringAsync());
333+
var fileTransferResponse = await initializeFileResponse.Content.ReadFromJsonAsync<FileTransferInitializeResponseExt>();
334+
var fileTransferResponse2 = await initializeFileResponse2.Content.ReadFromJsonAsync<FileTransferInitializeResponseExt>();
335+
var fileTransferResponse3 = await initializeFileResponse3.Content.ReadFromJsonAsync<FileTransferInitializeResponseExt>();
336+
var fileTransferResponse4 = await initializeFileResponse4.Content.ReadFromJsonAsync<FileTransferInitializeResponseExt>();
337+
Assert.NotNull(fileTransferResponse);
338+
Assert.NotNull(fileTransferResponse2);
339+
Assert.NotNull(fileTransferResponse3);
340+
Assert.NotNull(fileTransferResponse4);
341+
var fileTransferId = fileTransferResponse.FileTransferId.ToString();
342+
var fileTransferId2 = fileTransferResponse2.FileTransferId.ToString();
343+
var fileTransferId3 = fileTransferResponse3.FileTransferId.ToString();
344+
var fileTransferId4 = fileTransferResponse4.FileTransferId.ToString();
345+
var initializedFile = await _senderClient.GetFromJsonAsync<FileTransferOverviewExt>($"broker/api/v1/filetransfer/{fileTransferId}", _responseSerializerOptions);
346+
Assert.NotNull(initializedFile);
347+
var uploadedFileBytes = Encoding.UTF8.GetBytes("This is the contents of the uploaded file");
348+
using (var content = new ByteArrayContent(uploadedFileBytes))
349+
using (var content2 = new ByteArrayContent(uploadedFileBytes))
350+
using (var content3 = new ByteArrayContent(uploadedFileBytes))
351+
using (var content4 = new ByteArrayContent(uploadedFileBytes))
352+
{
353+
content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
354+
content2.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
355+
content3.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
356+
content4.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
357+
var uploadResponse = await _senderClient.PostAsync($"broker/api/v1/filetransfer/{fileTransferId}/upload", content);
358+
var uploadResponse2 = await _senderClient.PostAsync($"broker/api/v1/filetransfer/{fileTransferId2}/upload", content2);
359+
var uploadResponse3 = await _senderClient.PostAsync($"broker/api/v1/filetransfer/{fileTransferId3}/upload", content3);
360+
var uploadResponse4 = await _senderClient.PostAsync($"broker/api/v1/filetransfer/{fileTransferId4}/upload", content4);
361+
Assert.True(uploadResponse.IsSuccessStatusCode, await uploadResponse.Content.ReadAsStringAsync());
362+
Assert.True(uploadResponse2.IsSuccessStatusCode, await uploadResponse.Content.ReadAsStringAsync());
363+
Assert.True(uploadResponse3.IsSuccessStatusCode, await uploadResponse.Content.ReadAsStringAsync());
364+
Assert.True(uploadResponse4.IsSuccessStatusCode, await uploadResponse.Content.ReadAsStringAsync());
365+
}
366+
367+
List<Guid> ids = [fileTransferResponse.FileTransferId, fileTransferResponse2.FileTransferId, fileTransferResponse3.FileTransferId, fileTransferResponse4.FileTransferId];
368+
369+
// Act
370+
var getResponse = await _legacyClient.PostAsJsonAsync($"broker/api/v1/legacy/file/overviews?onBehalfOfConsumer={file.Recipients[0]}", ids);
371+
Assert.Equal(HttpStatusCode.OK, getResponse.StatusCode);
372+
var fileData = await getResponse.Content.ReadFromJsonAsync<List<LegacyFileOverviewExt>>(_responseSerializerOptions);
373+
374+
// Assert
375+
Assert.Equal(HttpStatusCode.OK, getResponse.StatusCode);
376+
Assert.NotNull(fileData);
377+
var resultIds = fileData.Select(fd => fd.FileId.ToString());
378+
Assert.Contains(fileTransferId, resultIds);
379+
Assert.Contains(fileTransferId2, resultIds);
380+
Assert.Contains(fileTransferId3, resultIds);
381+
Assert.Contains(fileTransferId4, resultIds);
382+
}
383+
320384
[Fact]
321385
public async Task GetFileOverview_2FilesInitiated_1Published_StandardRequestRetrievesOnlyPublished_Success()
322386
{

0 commit comments

Comments
 (0)