Skip to content

Cleanup ModerationCase entities after 3 years. #8885

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions app/lib/admin/backend.dart
Original file line number Diff line number Diff line change
Expand Up @@ -779,6 +779,34 @@ class AdminBackend {
return refCase;
}

/// Scans datastore and deletes [ModerationCase] entities where the last
/// action was more than 3 years ago.
Future<void> deleteModerationCases({
@visibleForTesting DateTime? before,
}) async {
before ??= clock.ago(days: 3 * 366).toUtc(); // extra buffer days

/// Querying the cases that were opened before the threshold,
/// as the resolved timestamp may be null for ongoing cases.
final query = _db.query<ModerationCase>()
..filter('opened <', before)
..order('opened');
await for (final mc in query.run()) {
// sanity check that both timestamps are before the threshold
if (mc.opened.isAfter(before)) {
continue;
}
final resolved = mc.resolved;
if (resolved == null || resolved.isAfter(before)) {
continue;
}
// delete the entity
_logger.info('Deleting ModerationCase: ${mc.caseId}');
await _db.commit(deletes: [mc.key]);
_logger.info('Deleted ModerationCase: ${mc.caseId}');
}
}

/// Scans datastore and deletes moderated subjects where the last action
/// was more than 3 years ago.
Future<void> deleteModeratedSubjects({
Expand Down
7 changes: 7 additions & 0 deletions app/lib/tool/neat_task/pub_dev_tasks.dart
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,13 @@ List<NeatPeriodicTaskScheduler> createPeriodicTaskSchedulers({
task: () async => adminBackend.deleteAdminDeletedEntities(),
),

// Deletes ModerationCase entities.
_weekly(
name: 'delete-moderation-cases',
isRuntimeVersioned: false,
task: () async => adminBackend.deleteModerationCases(),
),

// Deletes moderated packages, versions, publishers and users.
_weekly(
name: 'delete-moderated-subjects',
Expand Down
22 changes: 22 additions & 0 deletions app/test/admin/moderation_case_resolve_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import 'package:_pub_shared/data/account_api.dart';
import 'package:_pub_shared/data/admin_api.dart';
import 'package:clock/clock.dart';
import 'package:pub_dev/admin/backend.dart';
import 'package:pub_dev/admin/models.dart';
import 'package:pub_dev/shared/datastore.dart';
Expand Down Expand Up @@ -71,6 +72,15 @@ void main() {
return mc!.status!;
}

Future<void> _verifyCaseExistence(String caseId, bool exists) async {
final mc = await adminBackend.lookupModerationCase(caseId);
if (exists) {
expect(mc, isNotNull);
} else {
expect(mc, isNull);
}
}

testWithProfile('notification: no action', fn: () async {
final mc = await _prepare(apply: null);
expect(await _close(mc.caseId), 'no-action');
Expand All @@ -82,13 +92,25 @@ void main() {
'SHOUT Deleting object from public bucket: "packages/oxygen-2.0.0-dev.tar.gz".',
], fn: () async {
final mc = await _prepare(apply: true);

// cleanup doesn't remove case prematurely
await _verifyCaseExistence(mc.caseId, true);
await adminBackend.deleteModerationCases(before: clock.now().toUtc());
await _verifyCaseExistence(mc.caseId, true);

// close case
expect(
await _close(
mc.caseId,
reason: 'The package violated our policy.',
),
'moderation-applied',
);

// cleanup does remove case after the threshold is reached
await _verifyCaseExistence(mc.caseId, true);
await adminBackend.deleteModerationCases(before: clock.now().toUtc());
await _verifyCaseExistence(mc.caseId, false);
});

testWithProfile('appeal no action: revert', expectedLogMessages: [
Expand Down