Skip to content

Commit 58513c8

Browse files
authored
Hotfix/3.0.1 (#528)
* Refactor persistence to store interfaces; thread-safe one-time database initialization (#525) * fix: ensure one-time database initialization with concurrency handling Signed-off-by: Kevin <kevin.dinh@lissi.id> * refactor: replace domain repositories with store interfaces and update DI service lifetimes Signed-off-by: Kevin <kevin.dinh@lissi.id> --------- Signed-off-by: Kevin <kevin.dinh@lissi.id> * chore(changelog): changelog for 3.0.1 Signed-off-by: Kevin <kevin.dinh@lissi.id> * chore(versioning): bump the version to 3.0.1 Signed-off-by: Kevin <kevin.dinh@lissi.id> --------- Signed-off-by: Kevin <kevin.dinh@lissi.id>
1 parent b217b28 commit 58513c8

40 files changed

Lines changed: 965 additions & 816 deletions

CHANGELOG.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
### [Unreleased]
99

10+
### [3.0.1] - 2026.02.27
11+
12+
- Fix Database concurrency issues
13+
1014
### [3.0.0] - 2026.02.27
1115

1216
#### Added
1317

1418
- Support for OID4VC Stack
1519
- Support SQLite database
1620

17-
1821
#### Deprecated
1922

2023
- Hyperledger Aries Stack
21-
22-

Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
<Product>Wallet Framework for .NET</Product>
1010
<RepositoryUrl>https://github.com/openwallet-foundation-labs/wallet-framework-dotnet.git</RepositoryUrl>
1111
<RepositoryType>git</RepositoryType>
12-
<Version>3.0.0</Version>
12+
<Version>3.0.1</Version>
1313
</PropertyGroup>
1414
<!-- Common compile parameters -->
1515
<PropertyGroup>

src/WalletFramework.MdocVc/Persistence/FindMdocCredentialsWithDocType.cs

Lines changed: 0 additions & 15 deletions
This file was deleted.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using LanguageExt;
2+
using WalletFramework.Core.Credentials;
3+
using WalletFramework.MdocLib;
4+
5+
namespace WalletFramework.MdocVc.Persistence;
6+
7+
public interface IMdocCredentialStore
8+
{
9+
Task<Unit> Add(MdocCredential credential);
10+
11+
Task<Option<MdocCredential>> Get(CredentialId id);
12+
13+
Task<IReadOnlyList<MdocCredential>> List();
14+
15+
Task<IReadOnlyList<MdocCredential>> ListByDocType(DocType docType);
16+
17+
Task<Unit> Update(MdocCredential credential);
18+
19+
Task<Unit> Delete(CredentialId id);
20+
}
Lines changed: 26 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,58 @@
11
using LanguageExt;
22
using WalletFramework.Core.Credentials;
3-
using WalletFramework.Storage;
3+
using WalletFramework.MdocLib;
44
using WalletFramework.Storage.Repositories;
55

66
namespace WalletFramework.MdocVc.Persistence;
77

88
public class MdocCredentialRepository(IRepository<MdocCredentialRecord> repository)
9-
: IDomainRepository<MdocCredential, MdocCredentialRecord, CredentialId>
9+
: IMdocCredentialStore
1010
{
11-
public async Task<Unit> Add(MdocCredential domainModel)
11+
public async Task<Unit> Add(MdocCredential credential)
1212
{
13-
var record = new MdocCredentialRecord(domainModel);
13+
var record = new MdocCredentialRecord(credential);
1414
await repository.Add(record);
1515
return Unit.Default;
1616
}
1717

18-
public async Task<Unit> AddMany(IEnumerable<MdocCredential> domainModels)
19-
{
20-
var records = domainModels.Select(d => new MdocCredentialRecord(d));
21-
await repository.AddMany(records);
22-
return Unit.Default;
23-
}
24-
25-
public virtual async Task<Unit> Delete(CredentialId id)
18+
public async Task<Option<MdocCredential>> Get(CredentialId id)
2619
{
2720
var guid = Guid.Parse(id.AsString());
28-
await repository.RemoveById(guid);
29-
return Unit.Default;
21+
var record = await repository.GetById(guid);
22+
return record.Map(item => item.ToDomainModel());
3023
}
3124

32-
public virtual async Task<Unit> Delete(MdocCredential domainModel)
25+
public async Task<IReadOnlyList<MdocCredential>> List()
3326
{
34-
var record = new MdocCredentialRecord(domainModel);
35-
await repository.Remove(record);
36-
return Unit.Default;
27+
var records = await repository.ListAll();
28+
return MapRecords(records);
3729
}
3830

39-
public async Task<Option<List<MdocCredential>>> Find(ISearchConfig<MdocCredentialRecord> config)
31+
public async Task<IReadOnlyList<MdocCredential>> ListByDocType(DocType docType)
4032
{
41-
var records = await repository.Find(config.ToPredicate());
42-
return
43-
from credentialRecords in records
44-
let credentials = credentialRecords.Select(r => r.ToDomainModel())
45-
select credentials.ToList();
33+
var docTypeValue = docType.AsString();
34+
var records = await repository.Find(record => record.DocType == docTypeValue);
35+
return MapRecords(records);
4636
}
4737

48-
public async Task<Option<MdocCredential>> GetById(CredentialId id)
38+
public async Task<Unit> Update(MdocCredential credential)
4939
{
50-
var guid = Guid.Parse(id.AsString());
51-
var record = await repository.GetById(guid);
52-
return record.Map(r => r.ToDomainModel());
40+
var record = new MdocCredentialRecord(credential);
41+
await repository.Update(record);
42+
return Unit.Default;
5343
}
5444

55-
public async Task<Option<List<MdocCredential>>> ListAll()
45+
public async Task<Unit> Delete(CredentialId id)
5646
{
57-
var records = await repository.ListAll();
58-
return
59-
from credentialRecords in records
60-
let credentials = credentialRecords.Select(r => r.ToDomainModel())
61-
select credentials.ToList();
47+
var guid = Guid.Parse(id.AsString());
48+
await repository.RemoveById(guid);
49+
return Unit.Default;
6250
}
6351

64-
public virtual async Task<Unit> Update(MdocCredential domainModel)
52+
private static IReadOnlyList<MdocCredential> MapRecords(Option<IReadOnlyList<MdocCredentialRecord>> records)
6553
{
66-
var record = new MdocCredentialRecord(domainModel);
67-
await repository.Update(record);
68-
return Unit.Default;
54+
return records.Match<IReadOnlyList<MdocCredential>>(
55+
credentialRecords => credentialRecords.Select(record => record.ToDomainModel()).ToList(),
56+
() => []);
6957
}
7058
}

src/WalletFramework.Oid4Vc/CredentialSet/CredentialSetService.cs

Lines changed: 49 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -7,52 +7,73 @@
77
namespace WalletFramework.Oid4Vc.CredentialSet;
88

99
public class CredentialSetService(
10-
IDomainRepository<CredentialDataSet, CredentialDataSetRecord, CredentialSetId> credentialSetRepository,
10+
ICredentialDataSetStore credentialDataSetStore,
11+
IStorageSession storageSession,
1112
IStatusListService statusListService) : ICredentialSetService
1213
{
1314
public async Task<CredentialDataSet> RefreshCredentialSetState(CredentialDataSet credentialDataSet)
15+
{
16+
var (refreshedCredentialDataSet, hasChanged) = await RefreshState(credentialDataSet);
17+
if (hasChanged)
18+
{
19+
await storageSession.Commit();
20+
}
21+
22+
return refreshedCredentialDataSet;
23+
}
24+
25+
public async Task RefreshCredentialSetStates()
26+
{
27+
var credentialSetRecords = await credentialDataSetStore.List();
28+
var hasChanges = false;
29+
30+
foreach (var credentialSetRecord in credentialSetRecords)
31+
{
32+
var (_, recordHasChanged) = await RefreshState(credentialSetRecord);
33+
hasChanges = hasChanges || recordHasChanged;
34+
}
35+
36+
if (hasChanges)
37+
{
38+
await storageSession.Commit();
39+
}
40+
}
41+
42+
private async Task<(CredentialDataSet CredentialDataSet, bool HasChanged)> RefreshState(
43+
CredentialDataSet credentialDataSet)
1444
{
1545
var oldState = credentialDataSet.State;
1646

1747
if (credentialDataSet.DeletedAt.IsSome)
18-
return credentialDataSet;
48+
{
49+
return (credentialDataSet, false);
50+
}
1951

20-
credentialDataSet.ExpiresAt.IfSome(expiresAt =>
52+
credentialDataSet.ExpiresAt.IfSome(expiresAt =>
2153
{
2254
if (expiresAt < DateTime.UtcNow)
2355
{
2456
credentialDataSet = credentialDataSet with { State = CredentialState.Expired };
2557
}
2658
});
2759

28-
await credentialDataSet.StatusListEntry.IfSomeAsync(
29-
async statusList =>
30-
{
31-
await statusListService.GetState(statusList).IfSomeAsync(
32-
state =>
33-
{
34-
if (state == CredentialState.Revoked)
35-
credentialDataSet = credentialDataSet with { State = CredentialState.Revoked };
36-
});
37-
});
38-
39-
if (oldState != credentialDataSet.State)
40-
await credentialSetRepository.Update(credentialDataSet);
41-
42-
return credentialDataSet;
43-
}
44-
45-
public async Task RefreshCredentialSetStates()
46-
{
47-
var credentialSetRecords = await credentialSetRepository.ListAll();
48-
49-
await credentialSetRecords.IfSomeAsync(
50-
async records =>
60+
await credentialDataSet.StatusListEntry.IfSomeAsync(async statusList =>
61+
{
62+
await statusListService.GetState(statusList).IfSomeAsync(state =>
5163
{
52-
foreach (var credentialSetRecord in records)
64+
if (state == CredentialState.Revoked)
5365
{
54-
await RefreshCredentialSetState(credentialSetRecord);
66+
credentialDataSet = credentialDataSet with { State = CredentialState.Revoked };
5567
}
5668
});
69+
});
70+
71+
var hasChanged = oldState != credentialDataSet.State;
72+
if (hasChanged)
73+
{
74+
await credentialDataSetStore.Save(credentialDataSet);
75+
}
76+
77+
return (credentialDataSet, hasChanged);
5778
}
5879
}
Lines changed: 21 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,57 @@
11
using LanguageExt;
22
using WalletFramework.Core.Credentials;
33
using WalletFramework.Oid4Vc.CredentialSet.Models;
4-
using WalletFramework.Storage;
54
using WalletFramework.Storage.Repositories;
65

76
namespace WalletFramework.Oid4Vc.CredentialSet.Persistence;
87

98
public class CredentialDataSetRepository(IRepository<CredentialDataSetRecord> repository)
10-
: IDomainRepository<CredentialDataSet, CredentialDataSetRecord, CredentialSetId>
9+
: ICredentialDataSetStore
1110
{
12-
public virtual async Task<Unit> Add(CredentialDataSet domainModel)
11+
public async Task<Unit> AddMany(IEnumerable<CredentialDataSet> credentialDataSets)
1312
{
14-
return await (await GetById(domainModel.CredentialSetId)).Match(
15-
async credential => await Update(domainModel),
16-
async () =>
17-
{
18-
var record = new CredentialDataSetRecord(domainModel);
19-
return await repository.Add(record);
20-
});
21-
}
22-
23-
public virtual async Task<Unit> AddMany(IEnumerable<CredentialDataSet> domainModels)
24-
{
25-
var records = domainModels.Select(domain => new CredentialDataSetRecord(domain));
13+
var records = credentialDataSets.Select(dataSet => new CredentialDataSetRecord(dataSet));
2614
await repository.AddMany(records);
2715
return Unit.Default;
2816
}
2917

30-
public virtual async Task<Unit> Delete(CredentialSetId id)
18+
public async Task<Unit> Delete(CredentialSetId id)
3119
{
3220
var guid = Guid.Parse(id.AsString());
3321
await repository.RemoveById(guid);
3422
return Unit.Default;
3523
}
3624

37-
public virtual async Task<Option<List<CredentialDataSet>>> Find(ISearchConfig<CredentialDataSetRecord> config)
38-
{
39-
var records = await repository.Find(config.ToPredicate());
40-
return from rs in records select rs.Select(r => r.ToDomainModel()).ToList();
41-
}
42-
43-
public virtual async Task<Option<CredentialDataSet>> GetById(CredentialSetId id)
25+
public async Task<Option<CredentialDataSet>> Get(CredentialSetId id)
4426
{
4527
var guid = Guid.Parse(id.AsString());
4628
var record = await repository.GetById(guid);
47-
return record.Map(r => r.ToDomainModel());
29+
return record.Map(item => item.ToDomainModel());
4830
}
4931

50-
public virtual async Task<Option<List<CredentialDataSet>>> ListAll()
32+
public async Task<IReadOnlyList<CredentialDataSet>> List()
5133
{
5234
var records = await repository.ListAll();
53-
return from rs in records select rs.Select(r => r.ToDomainModel()).ToList();
35+
return records.Match<IReadOnlyList<CredentialDataSet>>(
36+
dataSetRecords => dataSetRecords.Select(record => record.ToDomainModel()).ToList(),
37+
() => []);
5438
}
5539

56-
public virtual async Task<Unit> Update(CredentialDataSet domainModel)
40+
public async Task<Unit> Save(CredentialDataSet credentialDataSet)
5741
{
58-
var record = new CredentialDataSetRecord(domainModel);
59-
await repository.Update(record);
60-
return Unit.Default;
42+
return await (await Get(credentialDataSet.CredentialSetId)).Match(
43+
async _ => await UpdateStoredRecord(credentialDataSet),
44+
async () =>
45+
{
46+
var record = new CredentialDataSetRecord(credentialDataSet);
47+
return await repository.Add(record);
48+
});
6149
}
6250

63-
public virtual async Task<Unit> Delete(CredentialDataSet domainModel)
51+
private async Task<Unit> UpdateStoredRecord(CredentialDataSet credentialDataSet)
6452
{
65-
var record = new CredentialDataSetRecord(domainModel);
66-
await repository.Remove(record);
53+
var record = new CredentialDataSetRecord(credentialDataSet);
54+
await repository.Update(record);
6755
return Unit.Default;
6856
}
6957
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
using LanguageExt;
2+
using WalletFramework.Core.Credentials;
3+
using WalletFramework.Oid4Vc.CredentialSet.Models;
4+
5+
namespace WalletFramework.Oid4Vc.CredentialSet.Persistence;
6+
7+
public interface ICredentialDataSetStore
8+
{
9+
Task<Unit> AddMany(IEnumerable<CredentialDataSet> credentialDataSets);
10+
11+
Task<Unit> Delete(CredentialSetId id);
12+
13+
Task<Option<CredentialDataSet>> Get(CredentialSetId id);
14+
15+
Task<IReadOnlyList<CredentialDataSet>> List();
16+
17+
Task<Unit> Save(CredentialDataSet credentialDataSet);
18+
}

0 commit comments

Comments
 (0)