Skip to content

Commit 2dbcc69

Browse files
authored
added public filter and cache (#333)
* added public filter and cache * fix based on pr comments
1 parent d2970e8 commit 2dbcc69

9 files changed

Lines changed: 162 additions & 57 deletions

File tree

PrismaDotnetApi/PrismaApi.Application/Services/AssessmentService.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,16 +66,19 @@ public async Task<List<AssessmentOutgoingDto>> GetAllAsync(UserOutgoingDto user,
6666
{
6767
var assessments = new List<AssessmentOutgoingDto>();
6868
var projectIdsToGetFromDb = new HashSet<Guid>();
69-
foreach (var role in user.ProjectRoles)
69+
70+
var projectIds = _cache.GetAccessibleProjectIds(user);
71+
72+
foreach (var projectId in projectIds)
7073
{
71-
var cachedAssessments = _cache.GetCacheItemAsAssessment(role.ProjectId, user);
74+
var cachedAssessments = _cache.GetCacheItemAsAssessment(projectId, user);
7275
if (cachedAssessments != null)
7376
{
7477
assessments.AddRange(cachedAssessments);
7578
}
7679
else
7780
{
78-
projectIdsToGetFromDb.Add(role.ProjectId);
81+
projectIdsToGetFromDb.Add(projectId);
7982
}
8083
}
8184

PrismaDotnetApi/PrismaApi.Application/Services/BoardNodeService.cs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
namespace PrismaApi.Application.Services;
1111

12-
public class BoardNodeService: IBoardNodeService
12+
public class BoardNodeService : IBoardNodeService
1313
{
1414
private readonly IBoardNodeRepository _boardNodeRepository;
1515
private readonly IMemoryCache _cache;
@@ -51,16 +51,19 @@ public async Task<List<BoardNodeOutgoingDto>> GetAllAsync(UserOutgoingDto user,
5151
{
5252
var boardNodes = new List<BoardNodeOutgoingDto>();
5353
var projectIdsToGetFromDb = new HashSet<Guid>();
54-
foreach (var role in user.ProjectRoles)
54+
55+
var projectIds = _cache.GetAccessibleProjectIds(user);
56+
57+
foreach (var projectId in projectIds)
5558
{
56-
var cachedBoardNodes = _cache.GetCacheItemAsBoardNodes(role.ProjectId, user);
59+
var cachedBoardNodes = _cache.GetCacheItemAsBoardNodes(projectId, user);
5760
if (cachedBoardNodes != null)
5861
{
5962
boardNodes.AddRange(cachedBoardNodes);
6063
}
6164
else
6265
{
63-
projectIdsToGetFromDb.Add(role.ProjectId);
66+
projectIdsToGetFromDb.Add(projectId);
6467
}
6568
}
6669

PrismaDotnetApi/PrismaApi.Application/Services/EdgeService.cs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
namespace PrismaApi.Application.Services;
1111

12-
public class EdgeService: IEdgeService
12+
public class EdgeService : IEdgeService
1313
{
1414
private readonly IEdgeRepository _edgeRepository;
1515
private readonly IMemoryCache _cache;
@@ -53,16 +53,19 @@ public async Task<List<EdgeOutgoingDto>> GetAllAsync(UserOutgoingDto user, Cance
5353
{
5454
var edges = new List<EdgeOutgoingDto>();
5555
var projectIdsToGetFromDb = new HashSet<Guid>();
56-
foreach (var role in user.ProjectRoles)
56+
57+
var projectIds = _cache.GetAccessibleProjectIds(user);
58+
59+
foreach (var projectId in projectIds)
5760
{
58-
var cachedEdges = _cache.GetCacheItemAsEdges(role.ProjectId, user);
61+
var cachedEdges = _cache.GetCacheItemAsEdges(projectId, user);
5962
if (cachedEdges != null)
6063
{
6164
edges.AddRange(cachedEdges);
6265
}
6366
else
6467
{
65-
projectIdsToGetFromDb.Add(role.ProjectId);
68+
projectIdsToGetFromDb.Add(projectId);
6669
}
6770
}
6871

PrismaDotnetApi/PrismaApi.Application/Services/IssueService.cs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public async Task<List<IssueOutgoingDto>> CreateAsync(List<IssueIncomingDto> dto
3939
{
4040
if (entity.Type == IssueType.Uncertainty.ToString() && entity.Uncertainty?.Outcomes.Count > 0)
4141
{
42-
_discreteTableRuleEventHandler.EnqueueIssuesForRebuild([entity.Id]);
42+
_discreteTableRuleEventHandler.EnqueueIssuesForRebuild([entity.Id]);
4343
}
4444
}
4545
return entities.ToOutgoingDtos();
@@ -71,16 +71,19 @@ public async Task<List<IssueOutgoingDto>> GetAllAsync(UserOutgoingDto user, Canc
7171
// refactor to get all projects that the user has access to, then combine them all after getting them from the cache or database
7272
var issues = new List<IssueOutgoingDto>();
7373
var projectIdsToGetFromDb = new HashSet<Guid>();
74-
foreach (var role in user.ProjectRoles)
74+
75+
var projectIds = _cache.GetAccessibleProjectIds(user);
76+
77+
foreach (var projectId in projectIds)
7578
{
76-
var cachedIssues = _cache.GetCacheItemAsIssues(role.ProjectId, user);
79+
var cachedIssues = _cache.GetCacheItemAsIssues(projectId, user);
7780
if (cachedIssues != null)
7881
{
7982
issues.AddRange(cachedIssues);
8083
}
8184
else
8285
{
83-
projectIdsToGetFromDb.Add(role.ProjectId);
86+
projectIdsToGetFromDb.Add(projectId);
8487
}
8588
}
8689

PrismaDotnetApi/PrismaApi.Application/Services/NodeService.cs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
namespace PrismaApi.Application.Services;
1111

12-
public class NodeService: INodeService
12+
public class NodeService : INodeService
1313
{
1414
private readonly INodeRepository _nodeRepository;
1515
private readonly IMemoryCache _cache;
@@ -44,16 +44,19 @@ public async Task<List<NodeOutgoingDto>> GetAllAsync(UserOutgoingDto user, Cance
4444
{
4545
var nodes = new List<NodeOutgoingDto>();
4646
var projectIdsToGetFromDb = new HashSet<Guid>();
47-
foreach (var role in user.ProjectRoles)
47+
48+
var projectIds = _cache.GetAccessibleProjectIds(user);
49+
50+
foreach (var projectId in projectIds)
4851
{
49-
var cachedNodes = _cache.GetCacheItemAsNodes(role.ProjectId, user);
52+
var cachedNodes = _cache.GetCacheItemAsNodes(projectId, user);
5053
if (cachedNodes != null)
5154
{
5255
nodes.AddRange(cachedNodes);
5356
}
5457
else
5558
{
56-
projectIdsToGetFromDb.Add(role.ProjectId);
59+
projectIdsToGetFromDb.Add(projectId);
5760
}
5861
}
5962

PrismaDotnetApi/PrismaApi.Application/Services/ProjectService.cs

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -85,18 +85,23 @@ public async Task DeleteAsync(List<Guid> ids, UserOutgoingDto user, Cancellation
8585
public async Task<List<ProjectOutgoingDto>> GetAsync(List<Guid> ids, UserOutgoingDto user, CancellationToken ct = default)
8686
{
8787
var projects = await _projectRepository.GetByIdsAsync(ids, withTracking: false, filterPredicate: UserFilter(user), ct: ct);
88-
return projects.ToOutgoingDtos();
88+
var dtos = projects.ToOutgoingDtos();
89+
RegisterPublicProjectsInCache(dtos);
90+
return dtos;
8991
}
9092

9193
public async Task<List<ProjectOutgoingDto>> GetAllAsync(UserOutgoingDto user, CancellationToken ct = default)
9294
{
9395
var projects = await _projectRepository.GetAllAsync(withTracking: false, filterPredicate: UserFilter(user), ct: ct);
94-
return projects.ToOutgoingDtos();
96+
var dtos = projects.ToOutgoingDtos();
97+
RegisterPublicProjectsInCache(dtos);
98+
return dtos;
9599
}
96100

97101
public async Task<List<PopulatedProjectDto>> GetPopulatedAsync(List<Guid> ids, UserOutgoingDto user, CancellationToken ct = default)
98102
{
99103
var projects = await _projectRepository.GetByIdsAsync(ids, withTracking: false, filterPredicate: UserFilter(user), ct: ct);
104+
100105
return projects.ToPopulatedDtos();
101106
}
102107

@@ -113,7 +118,7 @@ public async Task<InfluenceDiagramDto> GetInfluenceDiagramAsync(Guid projectId,
113118
{
114119
return cachedDiagram;
115120
}
116-
121+
117122
var issueEntities = await _issueRepository.GetIssuesInInfluenceDiagram(projectId, IssuesUserFilter(user), ct);
118123
var edgeEntities = await _edgeRepository.GetEdgesInInfluenceDiagram(projectId, EdgesUserFilter(user), ct);
119124
var diagram = new InfluenceDiagramDto
@@ -124,15 +129,28 @@ public async Task<InfluenceDiagramDto> GetInfluenceDiagramAsync(Guid projectId,
124129
};
125130

126131
_cache.AddCacheItem(new CacheItem { CacheKey = CacheKeys.GetInfluenceDiagramKey(projectId) }, CacheConstants.DefaultMediumQueryCacheInTimeSpan, diagram);
127-
return diagram;
132+
return diagram;
128133
}
129134

130135
private static Expression<Func<Project, bool>> UserFilter(UserOutgoingDto user)
131-
=> e => e.ProjectRoles.Any(p => p.UserId == user.Id);
136+
=> e => e.Public || e.ProjectRoles.Any(p => p.UserId == user.Id);
132137

133138
private static Expression<Func<Issue, bool>> IssuesUserFilter(UserOutgoingDto user)
134-
=> e => e.Project!.ProjectRoles.Any(p => p.UserId == user.Id);
139+
=> e => e.Project!.Public || e.Project!.ProjectRoles.Any(p => p.UserId == user.Id);
135140

136141
private static Expression<Func<Edge, bool>> EdgesUserFilter(UserOutgoingDto user)
137-
=> e => e.HeadNode!.Issue!.Project!.ProjectRoles.Any(p => p.UserId == user.Id) && e.TailNode!.Issue!.Project!.ProjectRoles.Any(p => p.UserId == user.Id);
142+
=> e => (e.HeadNode!.Issue!.Project!.Public || e.HeadNode!.Issue!.Project!.ProjectRoles.Any(p => p.UserId == user.Id)) && (e.TailNode!.Issue!.Project!.Public || e.TailNode!.Issue!.Project!.ProjectRoles.Any(p => p.UserId == user.Id));
143+
144+
private void RegisterPublicProjectsInCache(List<ProjectOutgoingDto> projects)
145+
{
146+
var publicProjectIds = projects.Where(p => p.Public).Select(p => p.Id).ToList();
147+
if (publicProjectIds.Count == 0) return;
148+
149+
var existing = new HashSet<Guid>(_cache.GetPublicProjectIds());
150+
var previousCount = existing.Count;
151+
existing.UnionWith(publicProjectIds);
152+
153+
if (existing.Count > previousCount)
154+
_cache.AddCacheItem(new CacheItem { CacheKey = CacheKeys.PublicProjectIdsKey }, null, existing);
155+
}
138156
}

PrismaDotnetApi/PrismaApi.Infrastructure/Caching/CacheKeys.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,5 @@ public static class CacheKeys
99
public static string GetAssessmentKey(Guid projectId) => $"Assessment_Project_{projectId}";
1010
public static string GetBoardNodesInProjectKey(Guid projectId) => $"BoardNodes_Project_{projectId}";
1111
public static string GetUserKey(string key) => $"user_{key.ToLower()}"; // id internal, name in public
12-
}
12+
public const string PublicProjectIdsKey = "Public_Project_Ids";
13+
}

PrismaDotnetApi/PrismaApi.Infrastructure/Caching/MemoryCacheExtensions.cs

Lines changed: 53 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,27 +35,30 @@ public static class MemoryCacheExtensions
3535
public static InfluenceDiagramDto? GetCacheItemAsInfluenceDiagram(this IMemoryCache cache, Guid projectId, UserOutgoingDto user)
3636
{
3737
// check that the user has access to the project before returning cached diagram
38-
if (!user.HasAccessToProject(projectId))
38+
39+
if (!cache.HasAccessToProject(user, projectId))
3940
{
4041
return null;
4142
}
42-
return cache.GetCacheItem<InfluenceDiagramDto>(CacheKeys.GetInfluenceDiagramKey(projectId));
43+
return cache.GetCacheItem<InfluenceDiagramDto>(CacheKeys.GetInfluenceDiagramKey(projectId));
4344
}
4445

4546
public static List<IssueOutgoingDto>? GetCacheItemAsIssues(this IMemoryCache cache, Guid projectId, UserOutgoingDto user)
4647
{
4748
// check that the user has access to the project before returning cached issues
48-
if (!user.HasAccessToProject(projectId))
49+
50+
if (!cache.HasAccessToProject(user, projectId))
4951
{
5052
return null;
5153
}
5254
return cache.GetCacheItem<List<IssueOutgoingDto>>(CacheKeys.GetIssuesInProjectKey(projectId));
5355
}
5456

55-
public static List<EdgeOutgoingDto>? GetCacheItemAsEdges(this IMemoryCache cache, Guid projectId, UserOutgoingDto user)
57+
public static List<EdgeOutgoingDto>? GetCacheItemAsEdges(this IMemoryCache cache, Guid projectId, UserOutgoingDto user)
5658
{
5759
// check that the user has access to the project before returning cached edges
58-
if (!user.HasAccessToProject(projectId))
60+
61+
if (!cache.HasAccessToProject(user, projectId))
5962
{
6063
return null;
6164
}
@@ -65,7 +68,8 @@ public static class MemoryCacheExtensions
6568
public static List<NodeOutgoingDto>? GetCacheItemAsNodes(this IMemoryCache cache, Guid projectId, UserOutgoingDto user)
6669
{
6770
// check that the user has access to the project before returning cached nodes
68-
if (!user.HasAccessToProject(projectId))
71+
72+
if (!cache.HasAccessToProject(user, projectId))
6973
{
7074
return null;
7175
}
@@ -75,7 +79,7 @@ public static class MemoryCacheExtensions
7579
public static List<BoardNodeOutgoingDto>? GetCacheItemAsBoardNodes(this IMemoryCache cache, Guid projectId, UserOutgoingDto user)
7680
{
7781
// check that the user has access to the project before returning cached board nodes
78-
if (!user.HasAccessToProject(projectId))
82+
if (!cache.HasAccessToProject(user, projectId))
7983
{
8084
return null;
8185
}
@@ -85,13 +89,45 @@ public static class MemoryCacheExtensions
8589
public static List<AssessmentOutgoingDto>? GetCacheItemAsAssessment(this IMemoryCache cache, Guid projectId, UserOutgoingDto user)
8690
{
8791
// check that the user has access to the project before returning cached assessment
88-
if (!user.HasAccessToProject(projectId))
92+
if (!cache.HasAccessToProject(user, projectId))
8993
{
9094
return null;
9195
}
9296
return cache.GetCacheItem<List<AssessmentOutgoingDto>>(CacheKeys.GetAssessmentKey(projectId));
9397
}
9498

99+
100+
101+
public static HashSet<Guid> GetPublicProjectIds(this IMemoryCache cache)
102+
{
103+
// check that the user has access to the public projects before returning cached public project ids
104+
return cache.GetCacheItem<HashSet<Guid>>(CacheKeys.PublicProjectIdsKey) ?? new HashSet<Guid>();
105+
}
106+
107+
public static HashSet<Guid> GetAccessibleProjectIds(this IMemoryCache cache, UserOutgoingDto user)
108+
{
109+
var projectIds = user.ProjectRoles.Select(r => r.ProjectId).ToHashSet();
110+
projectIds.UnionWith(cache.GetPublicProjectIds());
111+
return projectIds;
112+
}
113+
114+
public static void UpdatePublicProjectIds(this IMemoryCache cache, HashSet<Guid> idsToRemove, HashSet<Guid> idsToAdd)
115+
{
116+
cacheLock.Wait();
117+
try
118+
{
119+
var publicProjectIds = new HashSet<Guid>(cache.GetPublicProjectIds());
120+
publicProjectIds.ExceptWith(idsToRemove);
121+
publicProjectIds.UnionWith(idsToAdd);
122+
cache.Set(CacheKeys.PublicProjectIdsKey, publicProjectIds, CacheEntryOptions);
123+
}
124+
finally
125+
{
126+
_ = cacheLock.Release();
127+
}
128+
}
129+
130+
95131
public static void AddCacheItem(this IMemoryCache cache, CacheItem key, TimeSpan? duration,
96132
object? value)
97133
{
@@ -104,6 +140,7 @@ public static void AddCacheItem(this IMemoryCache cache, CacheItem key, TimeSpan
104140
cacheLock.Wait();
105141
try
106142
{
143+
107144
if (duration.HasValue)
108145
{
109146
_ = cache.Set(key.CacheKey, value, duration.Value);
@@ -191,6 +228,12 @@ public static double GetApproximateCacheSizeInMB(this IMemoryCache cache)
191228
return Math.Round(totalBytes / (1024.0 * 1024.0), 4);
192229
}
193230

194-
private static bool HasAccessToProject(this UserOutgoingDto user, Guid projectId)
195-
=> user.ProjectRoles.Any(pr => pr.ProjectId == projectId);
231+
private static bool HasAccessToProject(this IMemoryCache cache, UserOutgoingDto user, Guid projectId)
232+
{
233+
if (user.ProjectRoles.Any(pr => pr.ProjectId == projectId))
234+
return true;
235+
236+
var publicIds = cache.GetCacheItem<HashSet<Guid>>(CacheKeys.PublicProjectIdsKey);
237+
return publicIds?.Contains(projectId) == true;
238+
}
196239
}

0 commit comments

Comments
 (0)