diff --git a/app/lib/account/consent_backend.dart b/app/lib/account/consent_backend.dart index a79cc518ce..09e41fde54 100644 --- a/app/lib/account/consent_backend.dart +++ b/app/lib/account/consent_backend.dart @@ -6,6 +6,7 @@ import 'package:_pub_shared/data/account_api.dart' as api; import 'package:clock/clock.dart'; import 'package:gcloud/service_scope.dart' as ss; import 'package:logging/logging.dart'; +import 'package:pub_dev/shared/redis_cache.dart'; import 'package:retry/retry.dart'; import '../account/agent.dart'; @@ -118,19 +119,31 @@ class ConsentBackend { kind: kind, args: args, ); - final query = _db.query()..filter('dedupId =', dedupId); - final list = await query.run().toList(); - if (list.isNotEmpty) { - final old = list.first; - if (old.isExpired()) { + Consent? existing; + + final dedupCacheEntry = cache.consentDedupLookup(dedupId); + final cachedConsentId = await dedupCacheEntry.get(); + if (cachedConsentId != null) { + final key = _db.emptyKey.append(Consent, id: cachedConsentId); + existing = await _db.lookupOrNull(key); + } + if (existing == null) { + final query = _db.query() + ..filter('dedupId =', dedupId) + ..limit(1); + final list = await query.run().toList(); + existing = list.singleOrNull; + } + if (existing != null) { + if (existing.isExpired()) { // expired entries should be deleted - await _delete(old, (a) => a.onExpire(old)); - } else if (old.shouldNotify()) { + await _delete(existing, (a) => a.onExpire(existing!)); + } else if (existing.shouldNotify()) { // non-expired entries just re-send the notification - return await _sendNotification(activeAgent.displayId, old); + return await _sendNotification(activeAgent.displayId, existing); } else { return api.InviteStatus( - emailSent: false, nextNotification: old.nextNotification); + emailSent: false, nextNotification: existing.nextNotification); } } // Create a new entry. @@ -144,6 +157,7 @@ class ConsentBackend { consent, auditLogRecord, ]); + await dedupCacheEntry.set(consent.consentId); return await _sendNotification(activeAgent.displayId, consent); }); } diff --git a/app/lib/shared/redis_cache.dart b/app/lib/shared/redis_cache.dart index ba4f0437d7..222201c0d8 100644 --- a/app/lib/shared/redis_cache.dart +++ b/app/lib/shared/redis_cache.dart @@ -21,7 +21,7 @@ import '../../../service/rate_limit/models.dart'; import '../../../service/security_advisories/models.dart'; import '../../dartdoc/models.dart'; import '../../shared/env_config.dart'; -import '../account/models.dart' show LikeData, SessionData; +import '../account/models.dart' show Consent, LikeData, SessionData; import '../package/models.dart' show PackageView; import '../publisher/models.dart' show PublisherPage; import '../scorecard/models.dart' show ScoreCardData; @@ -58,6 +58,12 @@ class CachePatterns { decode: (d) => SessionData.fromJson(d as Map), ))[sessionId]; + /// Cache for [Consent] mapping the `dedupId` to the `consentId`. + Entry consentDedupLookup(String dedupId) => _cache + .withPrefix('consent-dedup-lookup/') + .withTTL(Duration(minutes: 5)) + .withCodec(utf8)[dedupId]; + Entry uiPackagePage(String package, String? version) => _cache .withPrefix('ui-packagepage/') .withTTL(Duration(minutes: 10))