Skip to content

Commit e5889f7

Browse files
authored
Migrate isBlocked flags to isModerated. (#8356)
1 parent e0c395d commit e5889f7

File tree

5 files changed

+140
-2
lines changed

5 files changed

+140
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ AppEngine version, listed here to ease deployment and troubleshooting.
88
* Upgraded dartdoc to `8.3.0`.
99
* Upgraded pana to `0.22.16`.
1010
* Upgraded dependencies.
11+
* Note: started migrating `isBlocked` flags to `isModerated`.
1112

1213
## `20241121t150900-all`
1314
* Bump runtimeVersion to `2024.11.21`.

app/lib/tool/backfill/backfill_new_fields.dart

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,14 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5+
import 'package:clock/clock.dart';
56
import 'package:logging/logging.dart';
7+
import 'package:pub_dev/account/models.dart';
8+
import 'package:pub_dev/package/api_export/api_exporter.dart';
9+
import 'package:pub_dev/package/backend.dart';
10+
import 'package:pub_dev/package/models.dart';
11+
import 'package:pub_dev/publisher/models.dart';
12+
import 'package:pub_dev/shared/datastore.dart';
613

714
final _logger = Logger('backfill_new_fields');
815

@@ -12,5 +19,72 @@ final _logger = Logger('backfill_new_fields');
1219
/// CHANGELOG.md must be updated with the new fields, and the next
1320
/// release could remove the backfill from here.
1421
Future<void> backfillNewFields() async {
15-
_logger.info('Nothing to backfill');
22+
await migrateIsBlocked();
23+
}
24+
25+
/// Migrates entities from the `isBlocked` fields to the new `isModerated` instead.
26+
Future<void> migrateIsBlocked() async {
27+
_logger.info('Migrating isBlocked...');
28+
final pkgQuery = dbService.query<Package>()..filter('isBlocked =', true);
29+
await for (final entity in pkgQuery.run()) {
30+
await withRetryTransaction(dbService, (tx) async {
31+
final pkg = await tx.lookupValue<Package>(entity.key);
32+
// sanity check
33+
if (!pkg.isBlocked) {
34+
return;
35+
}
36+
pkg
37+
..isModerated = true
38+
..moderatedAt = pkg.moderatedAt ?? pkg.blocked ?? clock.now()
39+
..isBlocked = false
40+
..blocked = null
41+
..blockedReason = null;
42+
tx.insert(pkg);
43+
});
44+
45+
// sync exported API(s)
46+
await apiExporter?.synchronizePackage(entity.name!, forceDelete: true);
47+
48+
// retract or re-populate public archive files
49+
await packageBackend.tarballStorage.updatePublicArchiveBucket(
50+
package: entity.name!,
51+
ageCheckThreshold: Duration.zero,
52+
deleteIfOlder: Duration.zero,
53+
);
54+
}
55+
56+
final publisherQuery = dbService.query<Publisher>()
57+
..filter('isBlocked =', true);
58+
await for (final entity in publisherQuery.run()) {
59+
await withRetryTransaction(dbService, (tx) async {
60+
final publisher = await tx.lookupValue<Publisher>(entity.key);
61+
// sanity check
62+
if (!publisher.isBlocked) {
63+
return;
64+
}
65+
publisher
66+
..isModerated = true
67+
..moderatedAt = publisher.moderatedAt ?? clock.now()
68+
..isBlocked = false;
69+
tx.insert(publisher);
70+
});
71+
}
72+
73+
final userQuery = dbService.query<User>()..filter('isBlocked =', true);
74+
await for (final entity in userQuery.run()) {
75+
await withRetryTransaction(dbService, (tx) async {
76+
final user = await tx.lookupValue<User>(entity.key);
77+
// sanity check
78+
if (!user.isBlocked) {
79+
return;
80+
}
81+
user
82+
..isModerated = true
83+
..moderatedAt = user.moderatedAt ?? clock.now()
84+
..isBlocked = false;
85+
tx.insert(user);
86+
});
87+
}
88+
89+
_logger.info('isBlocked migration completed.');
1690
}

app/test/shared/test_services.dart

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import 'package:pub_dev/shared/redis_cache.dart';
3131
import 'package:pub_dev/shared/versions.dart';
3232
import 'package:pub_dev/task/cloudcompute/fakecloudcompute.dart';
3333
import 'package:pub_dev/task/global_lock.dart';
34+
import 'package:pub_dev/tool/backfill/backfill_new_fields.dart';
3435
import 'package:pub_dev/tool/test_profile/import_source.dart';
3536
import 'package:pub_dev/tool/test_profile/importer.dart';
3637
import 'package:pub_dev/tool/test_profile/models.dart';
@@ -120,7 +121,6 @@ class FakeAppengineEnv {
120121
await fork(() async {
121122
await fn();
122123
});
123-
// post-test integrity check
124124
final problems =
125125
await IntegrityChecker(dbService).findProblems().toList();
126126
if (problems.isNotEmpty &&
@@ -131,6 +131,14 @@ class FakeAppengineEnv {
131131
} else if (problems.isEmpty && integrityProblem != null) {
132132
throw Exception('Integrity problem expected but not present.');
133133
}
134+
135+
// TODO: run all background tasks here
136+
await backfillNewFields();
137+
138+
// re-run integrity checks on the updated state
139+
final laterProblems =
140+
await IntegrityChecker(dbService).findProblems().toList();
141+
expect(laterProblems, problems);
134142
},
135143
);
136144
}) as R;
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'package:pub_dev/account/backend.dart';
6+
import 'package:pub_dev/package/backend.dart';
7+
import 'package:pub_dev/publisher/backend.dart';
8+
import 'package:pub_dev/shared/datastore.dart';
9+
import 'package:pub_dev/tool/backfill/backfill_new_fields.dart';
10+
import 'package:test/test.dart';
11+
12+
import '../../shared/test_services.dart';
13+
14+
void main() {
15+
group('Migrate isBlocked', () {
16+
testWithProfile('package', fn: () async {
17+
final p1 = await packageBackend.lookupPackage('oxygen');
18+
await dbService.commit(
19+
inserts: [p1!..updateIsBlocked(isBlocked: true, reason: 'abc')]);
20+
await migrateIsBlocked();
21+
22+
final p2 = await packageBackend.lookupPackage('oxygen');
23+
expect(p2!.isModerated, true);
24+
});
25+
26+
testWithProfile('publisher', fn: () async {
27+
final p1 = await publisherBackend.getPublisher('example.com');
28+
await dbService.commit(inserts: [p1!..markForBlocked()]);
29+
final members =
30+
await publisherBackend.listPublisherMembers('example.com');
31+
for (final m in members) {
32+
await accountBackend.updateBlockedFlag(m.userId, true);
33+
}
34+
final neon = await packageBackend.lookupPackage('neon');
35+
await dbService.commit(inserts: [neon!..isDiscontinued = true]);
36+
37+
await migrateIsBlocked();
38+
39+
final p2 = await publisherBackend.getPublisher('example.com');
40+
expect(p2!.isModerated, true);
41+
});
42+
43+
testWithProfile('user', fn: () async {
44+
final u1 = await accountBackend.lookupUserByEmail('[email protected]');
45+
await dbService.commit(inserts: [u1..isBlocked = true]);
46+
await migrateIsBlocked();
47+
48+
final u2 = await accountBackend.lookupUserByEmail('[email protected]');
49+
expect(u2.isModerated, true);
50+
});
51+
});
52+
}

pkg/fake_gcloud/lib/mem_datastore.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,9 @@ class MemDatastore implements Datastore {
172172
return -1;
173173
}
174174
}
175+
if (a is bool && b is bool) {
176+
return a == b ? 0 : (a ? 1 : -1);
177+
}
175178
if (a is Key && b is Key) {
176179
if (a.elements.length != 1) {
177180
throw UnimplementedError();

0 commit comments

Comments
 (0)