diff --git a/CACHING.md b/CACHING.md index 4e28daf1..d27c8c15 100644 --- a/CACHING.md +++ b/CACHING.md @@ -179,6 +179,8 @@ SqlSugar automatically generates cache keys based on: - Query parameters - Table names +For hot hackathon endpoints, explicitly defined cache keys are used via `.WithCache("...")` (for example `hackathon:details:{id}` and `hackathon:public-list`) to make cache intent easier to understand while preserving table-based invalidation. Public details keys also normalize the route token (`Guid` as `"D"` format, short code lowercased) so equivalent requests map to the same key. + **User-Specific Data**: Queries with `WHERE userId = {userId}` get unique cache keys per user, providing natural isolation. **Navigation Properties**: Queries using `.Includes()` for navigation properties should **NOT** use `.WithCache()` as SqlSugar's caching doesn't properly serialize/deserialize navigation properties. Load related entities separately or use explicit joins instead. diff --git a/HackOMania.Api/Endpoints/Organizers/Hackathon/Get/Endpoint.cs b/HackOMania.Api/Endpoints/Organizers/Hackathon/Get/Endpoint.cs index f043d686..dceb7bd7 100644 --- a/HackOMania.Api/Endpoints/Organizers/Hackathon/Get/Endpoint.cs +++ b/HackOMania.Api/Endpoints/Organizers/Hackathon/Get/Endpoint.cs @@ -22,9 +22,10 @@ public override void Configure() public override async Task HandleAsync(Request req, CancellationToken ct) { + var hackathonCacheKey = $"hackathon:details:{req.HackathonId}"; var hackathon = await sql.Queryable() .Where(h => h.Id == req.HackathonId) - .WithCache() + .WithCache(hackathonCacheKey) .FirstAsync(ct); if (hackathon is null) @@ -35,7 +36,7 @@ public override async Task HandleAsync(Request req, CancellationToken ct) var emailTemplates = await sql.Queryable() .Where(t => t.HackathonId == hackathon.Id) - .WithCache() + .WithCache($"hackathon:details:{hackathon.Id}:notification-templates") .ToListAsync(ct); var emailTemplateMap = emailTemplates .GroupBy(t => t.EventKey, StringComparer.OrdinalIgnoreCase) diff --git a/HackOMania.Api/Endpoints/Organizers/Hackathon/List/Endpoint.cs b/HackOMania.Api/Endpoints/Organizers/Hackathon/List/Endpoint.cs index 2df0a029..abf78e59 100644 --- a/HackOMania.Api/Endpoints/Organizers/Hackathon/List/Endpoint.cs +++ b/HackOMania.Api/Endpoints/Organizers/Hackathon/List/Endpoint.cs @@ -41,12 +41,12 @@ public override async Task HandleAsync(CancellationToken ct) ); } - var hackathons = await query.WithCache().ToListAsync(ct); + var hackathons = await query.WithCache($"hackathon:organizer-list:{userId.Value}").ToListAsync(ct); var hackathonIds = hackathons.Select(h => h.Id).ToList(); var templates = await sql.Queryable() .Where(t => hackathonIds.Contains(t.HackathonId)) - .WithCache() + .WithCache($"hackathon:organizer-list:{userId.Value}:notification-templates") .ToListAsync(ct); var templatesByHackathon = templates .GroupBy(t => t.HackathonId) diff --git a/HackOMania.Api/Endpoints/Participants/Hackathon/Get/Endpoint.cs b/HackOMania.Api/Endpoints/Participants/Hackathon/Get/Endpoint.cs index bd87d413..f760fe1a 100644 --- a/HackOMania.Api/Endpoints/Participants/Hackathon/Get/Endpoint.cs +++ b/HackOMania.Api/Endpoints/Participants/Hackathon/Get/Endpoint.cs @@ -19,12 +19,28 @@ public override void Configure() public override async Task HandleAsync(Request req, CancellationToken ct) { + var requestedToken = (req.HackathonIdOrShortCode ?? string.Empty).Trim(); + if (string.IsNullOrWhiteSpace(requestedToken)) + { + await Send.NotFoundAsync(ct); + return; + } + var isGuidToken = Guid.TryParse(requestedToken, out var parsedHackathonId); + var normalizedShortCode = requestedToken.ToLower(); + var cacheKeySegment = isGuidToken + ? parsedHackathonId.ToString("D") + : normalizedShortCode; + var hackathonCacheKey = $"hackathon:public-details:{cacheKeySegment}"; var hackathon = await sql.Queryable() .Where(h => - h.Id.ToString() == req.HackathonIdOrShortCode - || h.ShortCode == req.HackathonIdOrShortCode + (isGuidToken && h.Id == parsedHackathonId) + || ( + !isGuidToken + && h.ShortCode != null + && SqlFunc.ToLower(h.ShortCode) == normalizedShortCode + ) ) - .WithCache() + .WithCache(hackathonCacheKey) .FirstAsync(ct); if (hackathon is null || !hackathon.IsPublished) diff --git a/HackOMania.Api/Endpoints/Participants/Hackathon/List/Endpoint.cs b/HackOMania.Api/Endpoints/Participants/Hackathon/List/Endpoint.cs index 8cdd9a88..3a4409ed 100644 --- a/HackOMania.Api/Endpoints/Participants/Hackathon/List/Endpoint.cs +++ b/HackOMania.Api/Endpoints/Participants/Hackathon/List/Endpoint.cs @@ -21,7 +21,7 @@ public override async Task HandleAsync(CancellationToken ct) { var hackathons = await sql.Queryable() .Where(h => h.IsPublished) - .WithCache() + .WithCache("hackathon:public-list") .ToListAsync(ct); await Send.OkAsync(