Skip to content

Commit 1aa7eeb

Browse files
authored
feat: Add EU endpoints for seen log (#607)
* DB Migration, changed our minds on the record name * EU endpoint for SeenLog (SO in next PR) * Rename string hasher class * Refactoring username/pid, fethcing both name and pid in one function
1 parent d216c90 commit 1aa7eeb

33 files changed

+2021
-73
lines changed

docs/swagger/V1/swagger.verified.json

+176-8
Original file line numberDiff line numberDiff line change
@@ -1486,6 +1486,126 @@
14861486
]
14871487
}
14881488
},
1489+
"/api/v1/enduser/dialogs/{dialogId}/seenlog": {
1490+
"get": {
1491+
"tags": [
1492+
"Enduser"
1493+
],
1494+
"summary": "Gets a single dialog seen log record",
1495+
"description": "Gets a single dialog seen log record. For more information see the documentation (link TBD).",
1496+
"operationId": "SearchDialogSeenLog",
1497+
"parameters": [
1498+
{
1499+
"name": "dialogId",
1500+
"in": "path",
1501+
"required": true,
1502+
"schema": {
1503+
"type": "string",
1504+
"format": "guid"
1505+
}
1506+
}
1507+
],
1508+
"responses": {
1509+
"401": {
1510+
"description": "Missing or invalid authentication token. Requires a Maskinporten-token with the scope \"digdir:dialogporten\"."
1511+
},
1512+
"403": {
1513+
"description": "Forbidden"
1514+
},
1515+
"200": {
1516+
"description": "Successfully returned the dialog seen log record.",
1517+
"content": {
1518+
"application/json": {
1519+
"schema": {
1520+
"type": "array",
1521+
"items": {
1522+
"$ref": "#/components/schemas/SearchDialogSeenLogDto"
1523+
}
1524+
}
1525+
}
1526+
}
1527+
},
1528+
"404": {
1529+
"description": "The given dialog ID or dialog element ID was not found or was already deleted.",
1530+
"content": {
1531+
"application/problem+json": {
1532+
"schema": {
1533+
"$ref": "#/components/schemas/ProblemDetails"
1534+
}
1535+
}
1536+
}
1537+
}
1538+
},
1539+
"security": [
1540+
{
1541+
"JWTBearerAuth": []
1542+
}
1543+
]
1544+
}
1545+
},
1546+
"/api/v1/enduser/dialogs/{dialogId}/seenlog/{seenLogId}": {
1547+
"get": {
1548+
"tags": [
1549+
"Enduser"
1550+
],
1551+
"summary": "Gets a single dialog seen log record",
1552+
"description": "Gets a single dialog seen log record. For more information see the documentation (link TBD).",
1553+
"operationId": "GetDialogSeenLog",
1554+
"parameters": [
1555+
{
1556+
"name": "dialogId",
1557+
"in": "path",
1558+
"required": true,
1559+
"schema": {
1560+
"type": "string",
1561+
"format": "guid"
1562+
}
1563+
},
1564+
{
1565+
"name": "seenLogId",
1566+
"in": "path",
1567+
"required": true,
1568+
"schema": {
1569+
"type": "string",
1570+
"format": "guid"
1571+
}
1572+
}
1573+
],
1574+
"responses": {
1575+
"401": {
1576+
"description": "Missing or invalid authentication token. Requires a Maskinporten-token with the scope \"digdir:dialogporten\"."
1577+
},
1578+
"403": {
1579+
"description": "Forbidden"
1580+
},
1581+
"200": {
1582+
"description": "Successfully returned the dialog seen log record.",
1583+
"content": {
1584+
"application/json": {
1585+
"schema": {
1586+
"$ref": "#/components/schemas/GetDialogSeenLogDto"
1587+
}
1588+
}
1589+
}
1590+
},
1591+
"404": {
1592+
"description": "The given dialog ID or dialog element ID was not found or was already deleted.",
1593+
"content": {
1594+
"application/problem+json": {
1595+
"schema": {
1596+
"$ref": "#/components/schemas/ProblemDetails"
1597+
}
1598+
}
1599+
}
1600+
}
1601+
},
1602+
"security": [
1603+
{
1604+
"JWTBearerAuth": []
1605+
}
1606+
]
1607+
}
1608+
},
14891609
"/api/v1/enduser/dialogs": {
14901610
"get": {
14911611
"tags": [
@@ -2606,7 +2726,7 @@
26062726
"seenSinceLastUpdate": {
26072727
"type": "array",
26082728
"items": {
2609-
"$ref": "#/components/schemas/SearchDialogDialogSeenRecordDtoSO"
2729+
"$ref": "#/components/schemas/SearchDialogDialogSeenLogDtoSO"
26102730
}
26112731
}
26122732
}
@@ -2626,7 +2746,7 @@
26262746
}
26272747
}
26282748
},
2629-
"SearchDialogDialogSeenRecordDtoSO": {
2749+
"SearchDialogDialogSeenLogDtoSO": {
26302750
"type": "object",
26312751
"additionalProperties": false,
26322752
"properties": {
@@ -2756,7 +2876,7 @@
27562876
"seenSinceLastUpdate": {
27572877
"type": "array",
27582878
"items": {
2759-
"$ref": "#/components/schemas/GetDialogDialogSeenRecordDtoSO"
2879+
"$ref": "#/components/schemas/GetDialogDialogSeenLogDtoSO"
27602880
}
27612881
}
27622882
}
@@ -2998,7 +3118,7 @@
29983118
}
29993119
}
30003120
},
3001-
"GetDialogDialogSeenRecordDtoSO": {
3121+
"GetDialogDialogSeenLogDtoSO": {
30023122
"type": "object",
30033123
"additionalProperties": false,
30043124
"properties": {
@@ -3450,6 +3570,54 @@
34503570
}
34513571
}
34523572
},
3573+
"SearchDialogSeenLogDto": {
3574+
"type": "object",
3575+
"additionalProperties": false,
3576+
"properties": {
3577+
"id": {
3578+
"type": "string",
3579+
"format": "guid"
3580+
},
3581+
"seenAt": {
3582+
"type": "string",
3583+
"format": "date-time"
3584+
},
3585+
"endUserIdHash": {
3586+
"type": "string"
3587+
},
3588+
"endUserName": {
3589+
"type": "string",
3590+
"nullable": true
3591+
},
3592+
"isCurrentEndUser": {
3593+
"type": "boolean"
3594+
}
3595+
}
3596+
},
3597+
"GetDialogSeenLogDto": {
3598+
"type": "object",
3599+
"additionalProperties": false,
3600+
"properties": {
3601+
"id": {
3602+
"type": "string",
3603+
"format": "guid"
3604+
},
3605+
"seenAt": {
3606+
"type": "string",
3607+
"format": "date-time"
3608+
},
3609+
"endUserIdHash": {
3610+
"type": "string"
3611+
},
3612+
"endUserName": {
3613+
"type": "string",
3614+
"nullable": true
3615+
},
3616+
"isCurrentEndUser": {
3617+
"type": "boolean"
3618+
}
3619+
}
3620+
},
34533621
"PaginatedListOfSearchDialogDto": {
34543622
"type": "object",
34553623
"additionalProperties": false,
@@ -3536,7 +3704,7 @@
35363704
"seenSinceLastUpdate": {
35373705
"type": "array",
35383706
"items": {
3539-
"$ref": "#/components/schemas/SearchDialogDialogSeenRecordDto"
3707+
"$ref": "#/components/schemas/SearchDialogDialogSeenLogDto"
35403708
}
35413709
}
35423710
}
@@ -3602,7 +3770,7 @@
36023770
}
36033771
}
36043772
},
3605-
"SearchDialogDialogSeenRecordDto": {
3773+
"SearchDialogDialogSeenLogDto": {
36063774
"type": "object",
36073775
"additionalProperties": false,
36083776
"properties": {
@@ -3723,7 +3891,7 @@
37233891
"seenSinceLastUpdate": {
37243892
"type": "array",
37253893
"items": {
3726-
"$ref": "#/components/schemas/GetDialogDialogSeenRecordDto"
3894+
"$ref": "#/components/schemas/GetDialogDialogSeenLogDto"
37273895
}
37283896
}
37293897
}
@@ -3967,7 +4135,7 @@
39674135
}
39684136
}
39694137
},
3970-
"GetDialogDialogSeenRecordDto": {
4138+
"GetDialogDialogSeenLogDto": {
39714139
"type": "object",
39724140
"additionalProperties": false,
39734141
"properties": {

src/Digdir.Domain.Dialogporten.Application/ApplicationExtensions.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public static IServiceCollection AddApplication(this IServiceCollection services
3939
.AddScoped<IDialogTokenGenerator, DialogTokenGenerator>()
4040

4141
// Transient
42-
.AddTransient<IStringHasher, RandomSaltStringHasher>()
42+
.AddTransient<IStringHasher, PersistentRandomSaltStringHasher>()
4343
.AddTransient<IUserOrganizationRegistry, UserOrganizationRegistry>()
4444
.AddTransient<IUserResourceRegistry, UserResourceRegistry>()
4545
.AddTransient<IUserNameRegistry, UserNameRegistry>()

src/Digdir.Domain.Dialogporten.Application/Common/MappingUtils.cs src/Digdir.Domain.Dialogporten.Application/Common/IStringHasher.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ internal interface IStringHasher
1010
string? Hash(string? personIdentifier);
1111
}
1212

13-
internal class RandomSaltStringHasher : IStringHasher
13+
internal class PersistentRandomSaltStringHasher : IStringHasher
1414
{
1515
private const int SaltSize = 16;
1616
private readonly Lazy<byte[]> _lazySalt = new(() => RandomNumberGenerator.GetBytes(SaltSize));

src/Digdir.Domain.Dialogporten.Application/Common/IUserNameRegistry.cs

+20-5
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.Diagnostics;
12
using System.Diagnostics.CodeAnalysis;
23
using Digdir.Domain.Dialogporten.Application.Common.Extensions;
34
using Digdir.Domain.Dialogporten.Application.Externals;
@@ -8,9 +9,11 @@ namespace Digdir.Domain.Dialogporten.Application.Common;
89
public interface IUserNameRegistry
910
{
1011
bool TryGetCurrentUserPid([NotNullWhen(true)] out string? userPid);
11-
Task<string?> GetCurrentUserName(string personalIdentificationNumber, CancellationToken cancellationToken);
12+
Task<UserInformation?> GetUserInformation(CancellationToken cancellationToken);
1213
}
1314

15+
public record UserInformation(string UserPid, string? UserName);
16+
1417
public class UserNameRegistry : IUserNameRegistry
1518
{
1619
private readonly IUser _user;
@@ -24,12 +27,22 @@ public UserNameRegistry(IUser user, INameRegistry nameRegistry)
2427

2528
public bool TryGetCurrentUserPid([NotNullWhen(true)] out string? userPid) => _user.TryGetPid(out userPid);
2629

27-
public async Task<string?> GetCurrentUserName(string personalIdentificationNumber, CancellationToken cancellationToken) =>
28-
await _nameRegistry.GetName(personalIdentificationNumber, cancellationToken);
30+
public async Task<UserInformation?> GetUserInformation(CancellationToken cancellationToken)
31+
{
32+
if (!TryGetCurrentUserPid(out var userPid))
33+
{
34+
return null;
35+
}
36+
37+
var userName = await _nameRegistry.GetName(userPid, cancellationToken);
38+
return new(userPid, userName);
39+
}
2940
}
3041

3142
internal sealed class LocalDevelopmentUserNameRegistryDecorator : IUserNameRegistry
3243
{
44+
private const string LocalDevelopmentUserPid = "Local Development User";
45+
3346
private readonly IUserNameRegistry _userNameRegistry;
3447

3548
public LocalDevelopmentUserNameRegistryDecorator(IUserNameRegistry userNameRegistry)
@@ -40,6 +53,8 @@ public LocalDevelopmentUserNameRegistryDecorator(IUserNameRegistry userNameRegis
4053
public bool TryGetCurrentUserPid([NotNullWhen(true)] out string? userPid) =>
4154
_userNameRegistry.TryGetCurrentUserPid(out userPid);
4255

43-
public async Task<string?> GetCurrentUserName(string personalIdentificationNumber, CancellationToken cancellationToken)
44-
=> await Task.FromResult("Local Development User");
56+
public Task<UserInformation?> GetUserInformation(CancellationToken cancellationToken)
57+
=> _userNameRegistry.TryGetCurrentUserPid(out var userPid)
58+
? Task.FromResult<UserInformation?>(new UserInformation(userPid!, LocalDevelopmentUserPid))
59+
: throw new UnreachableException();
4560
}

src/Digdir.Domain.Dialogporten.Application/Externals/IDialogDbContext.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public interface IDialogDbContext
2424

2525
DbSet<OutboxMessage> OutboxMessages { get; }
2626
DbSet<OutboxMessageConsumer> OutboxMessageConsumers { get; }
27-
DbSet<DialogSeenRecord> DialogSeenLog { get; }
27+
DbSet<DialogSeenLog> DialogSeenLog { get; }
2828

2929
/// <summary>
3030
/// Validate a property on the <typeparamref name="TEntity"/> using a lambda
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
namespace Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.DialogSeenLogs.Queries.Get;
2+
3+
public class GetDialogSeenLogDto
4+
{
5+
public Guid Id { get; set; }
6+
public DateTimeOffset SeenAt { get; set; }
7+
8+
public string EndUserIdHash { get; set; } = null!;
9+
10+
public string? EndUserName { get; set; }
11+
12+
public bool IsCurrentEndUser { get; set; }
13+
}

0 commit comments

Comments
 (0)