Skip to content

Commit 5f6c208

Browse files
authored
Cleanup ModerationCase entities after 3 years. (#8885)
1 parent 0226ac0 commit 5f6c208

File tree

3 files changed

+53
-0
lines changed

3 files changed

+53
-0
lines changed

app/lib/admin/backend.dart

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -779,6 +779,27 @@ class AdminBackend {
779779
return refCase;
780780
}
781781

782+
/// Scans datastore and deletes [ModerationCase] entities opened more than 3 years ago.
783+
Future<void> deleteModerationCases() async {
784+
final before = clock.ago(days: 3 * 365).toUtc();
785+
786+
/// Querying the cases that were opened before the threshold,
787+
/// as the resolved timestamp may be null for ongoing cases.
788+
final query = _db.query<ModerationCase>()
789+
..filter('opened <', before)
790+
..order('opened');
791+
await for (final mc in query.run()) {
792+
// sanity check that both timestamps are before the threshold
793+
if (mc.opened.isAfter(before)) {
794+
continue;
795+
}
796+
// delete the entity
797+
_logger.info('Deleting ModerationCase: ${mc.caseId}');
798+
await _db.commit(deletes: [mc.key]);
799+
_logger.info('Deleted ModerationCase: ${mc.caseId}');
800+
}
801+
}
802+
782803
/// Scans datastore and deletes moderated subjects where the last action
783804
/// was more than 3 years ago.
784805
Future<void> deleteModeratedSubjects({

app/lib/tool/neat_task/pub_dev_tasks.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,13 @@ List<NeatPeriodicTaskScheduler> createPeriodicTaskSchedulers({
136136
task: () async => await apiExporter.synchronizeExportedApi(),
137137
),
138138

139+
// Deletes ModerationCase entities.
140+
_daily(
141+
name: 'delete-moderation-cases',
142+
isRuntimeVersioned: false,
143+
task: () async => adminBackend.deleteModerationCases(),
144+
),
145+
139146
// Deletes admin-deleted entities.
140147
_weekly(
141148
name: 'delete-admin-deleted-entities',

app/test/admin/moderation_case_resolve_test.dart

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import 'package:_pub_shared/data/account_api.dart';
66
import 'package:_pub_shared/data/admin_api.dart';
7+
import 'package:clock/clock.dart';
78
import 'package:pub_dev/admin/backend.dart';
89
import 'package:pub_dev/admin/models.dart';
910
import 'package:pub_dev/shared/datastore.dart';
@@ -71,6 +72,15 @@ void main() {
7172
return mc!.status!;
7273
}
7374

75+
Future<void> _verifyCaseExistence(String caseId, bool exists) async {
76+
final mc = await adminBackend.lookupModerationCase(caseId);
77+
if (exists) {
78+
expect(mc, isNotNull);
79+
} else {
80+
expect(mc, isNull);
81+
}
82+
}
83+
7484
testWithProfile('notification: no action', fn: () async {
7585
final mc = await _prepare(apply: null);
7686
expect(await _close(mc.caseId), 'no-action');
@@ -82,13 +92,28 @@ void main() {
8292
'SHOUT Deleting object from public bucket: "packages/oxygen-2.0.0-dev.tar.gz".',
8393
], fn: () async {
8494
final mc = await _prepare(apply: true);
95+
96+
// cleanup doesn't remove case prematurely
97+
await _verifyCaseExistence(mc.caseId, true);
98+
await adminBackend.deleteModerationCases();
99+
await _verifyCaseExistence(mc.caseId, true);
100+
101+
// close case
85102
expect(
86103
await _close(
87104
mc.caseId,
88105
reason: 'The package violated our policy.',
89106
),
90107
'moderation-applied',
91108
);
109+
110+
// cleanup does remove case after the threshold is reached
111+
await _verifyCaseExistence(mc.caseId, true);
112+
final futureClock = Clock.fixed(clock.now().add(Duration(days: 365 * 3)));
113+
await withClock(futureClock, () async {
114+
await adminBackend.deleteModerationCases();
115+
});
116+
await _verifyCaseExistence(mc.caseId, false);
92117
});
93118

94119
testWithProfile('appeal no action: revert', expectedLogMessages: [

0 commit comments

Comments
 (0)