Skip to content

Commit f0376d7

Browse files
authored
Turn remove package and remove package-version into admin actions (#8614)
1 parent 97fc0c7 commit f0376d7

File tree

9 files changed

+207
-140
lines changed

9 files changed

+207
-140
lines changed

app/lib/admin/actions/actions.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,14 @@ import 'moderation_case_list.dart';
1919
import 'moderation_case_resolve.dart';
2020
import 'moderation_case_update.dart';
2121
import 'moderation_transparency_metrics.dart';
22+
import 'package_delete.dart';
2223
import 'package_discontinue.dart';
2324
import 'package_info.dart';
2425
import 'package_latest_update.dart';
2526
import 'package_reservation_create.dart';
2627
import 'package_reservation_delete.dart';
2728
import 'package_reservation_list.dart';
29+
import 'package_version_delete.dart';
2830
import 'package_version_info.dart';
2931
import 'package_version_retraction.dart';
3032
import 'publisher_create.dart';
@@ -105,12 +107,14 @@ final class AdminAction {
105107
moderationCaseResolve,
106108
moderationCaseUpdate,
107109
moderationTransparencyMetrics,
110+
packageDelete,
108111
packageDiscontinue,
109112
packageInfo,
110113
packageLatestUpdate,
111114
packageReservationCreate,
112115
packageReservationDelete,
113116
packageReservationList,
117+
packageVersionDelete,
114118
packageVersionInfo,
115119
packageVersionRetraction,
116120
publisherCreate,
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
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 '../../account/backend.dart';
6+
import '../../shared/configuration.dart';
7+
import '../backend.dart';
8+
import 'actions.dart';
9+
10+
final packageDelete = AdminAction(
11+
name: 'package-delete',
12+
options: {
13+
'package': 'name of package to delete',
14+
},
15+
summary: 'Deletes package <package>.',
16+
description: '''
17+
Deletes package <package>.
18+
19+
Deletes all associated resources:
20+
21+
* PackageVersions
22+
* Likes
23+
* AuditLogRecords
24+
* PackageVersionAsset
25+
* replacedBy references
26+
* archives (might be retrievable from backup)
27+
28+
The package will be "tombstoned" and no package with the same name can be
29+
published later.
30+
''',
31+
invoke: (args) async {
32+
final packageName = args['package'];
33+
if (packageName == null) {
34+
throw InvalidInputException('Missing `package` argument');
35+
}
36+
37+
await requireAuthenticatedAdmin(AdminPermission.removePackage);
38+
final result = await adminBackend.removePackage(packageName);
39+
40+
return {
41+
'message': '''
42+
Package and all associated resources deleted.
43+
44+
A tombstone has been created
45+
46+
'NOTICE: Redis caches referencing the package will expire given time.'
47+
''',
48+
'package': packageName,
49+
'deletedPackages': result.deletedPackages,
50+
'deletedPackageVersions': result.deletedPackageVersions,
51+
'deletedPackageVersionInfos': result.deletedPackageVersionInfos,
52+
'deletedPackageVersionAssets': result.deletedPackageVersionAssets,
53+
'deletedLikes': result.deletedLikes,
54+
'deletedAuditLogs': result.deletedAuditLogs,
55+
'replacedByFixes': result.replacedByFixes,
56+
};
57+
});
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
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 '../../account/backend.dart';
6+
import '../../shared/configuration.dart';
7+
import '../backend.dart';
8+
import 'actions.dart';
9+
10+
final packageVersionDelete = AdminAction(
11+
name: 'package-version-delete',
12+
options: {
13+
'package': 'name of package to delete',
14+
'version': 'version of package',
15+
},
16+
summary: 'Deletes package <package> version <version>.',
17+
description: '''
18+
Deletes package <package> version <version>.
19+
20+
Deletes all associated resources:
21+
22+
* PackageVersions
23+
* PackageVersionAsset
24+
* archives (might be retrievable from backup)
25+
26+
The package version will be "tombstoned" and same version cannot be published
27+
later.
28+
''',
29+
invoke: (args) async {
30+
await requireAuthenticatedAdmin(AdminPermission.removePackage);
31+
final packageName = args['package'];
32+
if (packageName == null) {
33+
throw InvalidInputException('Missing `package` argument');
34+
}
35+
final version = args['version'];
36+
if (version == null) {
37+
throw InvalidInputException('Missing `version` argument');
38+
}
39+
final result =
40+
await adminBackend.removePackageVersion(packageName, version);
41+
42+
return {
43+
'message': 'Package version and all associated resources deleted.',
44+
'package': packageName,
45+
'version': version,
46+
'deletedPackageVersions': result.deletedPackageVersions,
47+
'deletedPackageVersionInfos': result.deletedPackageVersionInfos,
48+
'deletedPackageVersionAssets': result.deletedPackageVersionAssets,
49+
};
50+
});

app/lib/admin/backend.dart

Lines changed: 46 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -294,24 +294,16 @@ class AdminBackend {
294294
/// Creates a [ModeratedPackage] instance (if not already present) in
295295
/// Datastore representing the removed package. No new package with the same
296296
/// name can be published.
297-
///
298-
/// Verifies the current authenticated user for admin permissions.
299-
Future<void> removePackage(String packageName) async {
300-
final caller =
301-
await requireAuthenticatedAdmin(AdminPermission.removePackage);
302-
_logger.info('${caller.displayId}) initiated the delete '
303-
'of package $packageName');
304-
await _removePackage(packageName);
305-
}
306-
307-
/// Removes the package from the Datastore and updates other related
308-
/// entities. It is safe to call [removePackage] on an already removed
309-
/// package, as the call is idempotent.
310-
///
311-
/// Creates a [ModeratedPackage] instance (if not already present) in
312-
/// Datastore representing the removed package. No new package with the same
313-
/// name can be published.
314-
Future<void> _removePackage(
297+
Future<
298+
({
299+
int deletedPackages,
300+
int deletedPackageVersions,
301+
int deletedPackageVersionInfos,
302+
int deletedPackageVersionAssets,
303+
int deletedLikes,
304+
int deletedAuditLogs,
305+
int replacedByFixes,
306+
})> removePackage(
315307
String packageName, {
316308
DateTime? moderated,
317309
}) async {
@@ -336,6 +328,7 @@ class AdminBackend {
336328
_logger.info('Removing package from Package.replacedBy...');
337329
final replacedByQuery = _db.query<Package>()
338330
..filter('replacedBy =', packageName);
331+
var replacedByFixes = 0;
339332
await for (final pkg in replacedByQuery.run()) {
340333
await withRetryTransaction(_db, (tx) async {
341334
final p = await tx.lookupOrNull<Package>(pkg.key);
@@ -346,26 +339,28 @@ class AdminBackend {
346339
p.replacedBy = null;
347340
tx.insert(p);
348341
}
342+
replacedByFixes++;
349343
});
350344
}
351345

352346
_logger.info('Removing package from PackageVersionInfo ...');
353-
await _db.deleteWithQuery(
347+
final deletedPackageVersionInfos = await _db.deleteWithQuery(
354348
_db.query<PackageVersionInfo>()..filter('package =', packageName));
355349

356350
_logger.info('Removing package from PackageVersionAsset ...');
357-
await _db.deleteWithQuery(
351+
final deletedPackageVersionAssets = await _db.deleteWithQuery(
358352
_db.query<PackageVersionAsset>()..filter('package =', packageName));
359353

360354
_logger.info('Removing package from Like ...');
361-
await _db.deleteWithQuery(
355+
final deletedLikes = await _db.deleteWithQuery(
362356
_db.query<Like>()..filter('packageName =', packageName));
363357

364358
_logger.info('Removing package from AuditLogRecord...');
365-
await _db.deleteWithQuery(
359+
final deletedAuditLogRecords = await _db.deleteWithQuery(
366360
_db.query<AuditLogRecord>()..filter('packages =', packageName));
367361

368362
_logger.info('Removing Package from Datastore...');
363+
var deletedPackages = 0;
369364
await withRetryTransaction(_db, (tx) async {
370365
final package = await tx.lookupOrNull<Package>(packageKey);
371366
if (package == null) {
@@ -376,7 +371,7 @@ class AdminBackend {
376371
return;
377372
}
378373
tx.delete(packageKey);
379-
374+
deletedPackages = 1;
380375
final moderatedPkgKey =
381376
_db.emptyKey.append(ModeratedPackage, id: packageName);
382377
final moderatedPkg =
@@ -409,8 +404,15 @@ class AdminBackend {
409404
.deleteWithQuery(_db.query<PackageVersion>(ancestorKey: packageKey));
410405

411406
_logger.info('Package "$packageName" got successfully removed.');
412-
_logger.info(
413-
'NOTICE: Redis caches referencing the package will expire given time.');
407+
return (
408+
deletedPackages: deletedPackages,
409+
deletedPackageVersions: versions.length,
410+
deletedPackageVersionInfos: deletedPackageVersionInfos.deleted,
411+
deletedPackageVersionAssets: deletedPackageVersionAssets.deleted,
412+
deletedLikes: deletedLikes.deleted,
413+
deletedAuditLogs: deletedAuditLogRecords.deleted,
414+
replacedByFixes: replacedByFixes
415+
);
414416
}
415417

416418
/// Updates the options (e.g. retraction) of the specific package version and
@@ -454,13 +456,13 @@ class AdminBackend {
454456
/// Removes the specific package version from the Datastore and updates other
455457
/// related entities. It is safe to call [removePackageVersion] on an already
456458
/// removed version, as the call is idempotent.
457-
Future<void> removePackageVersion(String packageName, String version) async {
458-
final caller =
459-
await requireAuthenticatedAdmin(AdminPermission.removePackage);
460-
461-
_logger.info('${caller.displayId}) initiated the delete '
462-
'of package $packageName $version');
463-
459+
Future<
460+
({
461+
int deletedPackageVersions,
462+
int deletedPackageVersionInfos,
463+
int deletedPackageVersionAssets,
464+
})> removePackageVersion(String packageName, String version) async {
465+
var deletedPackageVersions = 0;
464466
final currentDartSdk = await getCachedDartSdkVersion(
465467
lastKnownStable: toolStableDartSdkVersion);
466468
final currentFlutterSdk = await getCachedFlutterSdkVersion(
@@ -469,8 +471,7 @@ class AdminBackend {
469471
final packageKey = _db.emptyKey.append(Package, id: packageName);
470472
final package = await tx.lookupOrNull<Package>(packageKey);
471473
if (package == null) {
472-
throw Exception(
473-
'Package "$packageName" does not exists. Use full package removal without the version qualifier.');
474+
throw Exception('Package "$packageName" does not exists.');
474475
}
475476

476477
final versionsQuery = tx.query<PackageVersion>(packageKey);
@@ -479,8 +480,9 @@ class AdminBackend {
479480
if (versionNames.contains(version)) {
480481
tx.delete(packageKey.append(PackageVersion, id: version));
481482
package.updated = clock.now().toUtc();
483+
deletedPackageVersions = 1;
482484
} else {
483-
print('Package $packageName does not have a version $version.');
485+
_logger.info('Package $packageName does not have a version $version.');
484486
}
485487

486488
if (versionNames.length == 1 && versionNames.single == version) {
@@ -501,15 +503,15 @@ class AdminBackend {
501503
tx.insert(package);
502504
});
503505

504-
print('Removing GCS objects ...');
506+
_logger.info('Removing GCS objects ...');
505507
await packageBackend.removePackageTarball(packageName, version);
506508

507-
await _db.deleteWithQuery(
509+
final deletedPackageVersionInfos = await _db.deleteWithQuery(
508510
_db.query<PackageVersionInfo>()..filter('package =', packageName),
509511
where: (PackageVersionInfo info) => info.version == version,
510512
);
511513

512-
await _db.deleteWithQuery(
514+
final deletedPackageVersionAssets = await _db.deleteWithQuery(
513515
_db.query<PackageVersionAsset>()..filter('package =', packageName),
514516
where: (PackageVersionAsset asset) => asset.version == version,
515517
);
@@ -518,6 +520,11 @@ class AdminBackend {
518520
await purgeScorecardData(packageName, version, isLatest: true);
519521
// trigger (eventual) re-analysis
520522
await taskBackend.trackPackage(packageName);
523+
return (
524+
deletedPackageVersions: deletedPackageVersions,
525+
deletedPackageVersionInfos: deletedPackageVersionInfos.deleted,
526+
deletedPackageVersionAssets: deletedPackageVersionAssets.deleted,
527+
);
521528
}
522529

523530
/// Handles `GET '/api/admin/packages/<package>/assigned-tags'`.
@@ -793,7 +800,7 @@ class AdminBackend {
793800
}
794801

795802
_logger.info('Deleting moderated package: ${package.name}');
796-
await _removePackage(
803+
await removePackage(
797804
package.name!,
798805
moderated: package.moderatedAt,
799806
);

app/lib/frontend/handlers/pubapi.client.dart

Lines changed: 0 additions & 17 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/lib/frontend/handlers/pubapi.dart

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -524,19 +524,6 @@ class PubApi {
524524
return jsonResponse({'status': 'OK'});
525525
}
526526

527-
@EndPoint.delete('/api/admin/packages/<package>')
528-
Future<Response> adminRemovePackage(Request request, String package) async {
529-
await adminBackend.removePackage(package);
530-
return jsonResponse({'status': 'OK'});
531-
}
532-
533-
@EndPoint.delete('/api/admin/packages/<package>/versions/<version>')
534-
Future<Response> adminRemovePackageVersion(
535-
Request request, String package, String version) async {
536-
await adminBackend.removePackageVersion(package, version);
537-
return jsonResponse({'status': 'OK'});
538-
}
539-
540527
@EndPoint.put('/api/admin/packages/<package>/versions/<version>/options')
541528
Future<VersionOptions> adminUpdateVersionOptions(Request request,
542529
String package, String version, VersionOptions options) async {

0 commit comments

Comments
 (0)