Skip to content

Commit

Permalink
Admin action + some tests for PackageVersion.isModerated. (#7560)
Browse files Browse the repository at this point in the history
  • Loading branch information
isoos committed Mar 21, 2024
1 parent 399e533 commit 61d518f
Show file tree
Hide file tree
Showing 6 changed files with 368 additions and 1 deletion.
3 changes: 2 additions & 1 deletion app/lib/admin/actions/actions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
// BSD-style license that can be found in the LICENSE file.

import '../../shared/exceptions.dart';

import 'create_publisher.dart';
import 'delete_publisher.dart';
import 'merge_moderated_package_into_existing.dart';
import 'moderate_package.dart';
import 'moderate_package_versions.dart';
import 'moderate_user.dart';
import 'publisher_block.dart';
import 'publisher_members_list.dart';
Expand Down Expand Up @@ -73,6 +73,7 @@ final class AdminAction {
deletePublisher,
mergeModeratedPackageIntoExisting,
moderatePackage,
moderatePackageVersion,
moderateUser,
publisherBlock,
publisherMembersList,
Expand Down
113 changes: 113 additions & 0 deletions app/lib/admin/actions/moderate_package_versions.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'package:_pub_shared/utils/dart_sdk_version.dart';
import 'package:clock/clock.dart';

import '../../package/backend.dart';
import '../../package/models.dart';
import '../../shared/datastore.dart';
import '../../shared/versions.dart';
import '../../task/backend.dart';
import '../../tool/maintenance/update_public_bucket.dart';

import 'actions.dart';

final moderatePackageVersion = AdminAction(
name: 'moderate-package-version',
summary:
'Set the moderated flag on a package version (making it not visible).',
description: '''
Set the moderated flag on a package version (updating the flag and the timestamp).
''',
options: {
'package': 'The package name to be moderated',
'version': 'The version to be moderated',
'state':
'Set moderated state true / false. Returns current state if omitted.',
},
invoke: (options) async {
final package = options['package'];
InvalidInputException.check(
package != null && package.isNotEmpty,
'package must be given',
);
final version = options['version'];
InvalidInputException.check(
version != null && version.isNotEmpty,
'version must be given',
);

final state = options['state'];
bool? valueToSet;
switch (state) {
case 'true':
valueToSet = true;
break;
case 'false':
valueToSet = false;
break;
}

final p = await packageBackend.lookupPackage(package!);
if (p == null) {
throw NotFoundException.resource(package);
}
final pv = await packageBackend.lookupPackageVersion(package, version!);
if (pv == null) {
throw NotFoundException.resource('$package $version');
}

PackageVersion? pv2;
if (valueToSet != null) {
final currentDartSdk =
await getDartSdkVersion(lastKnownStable: toolStableDartSdkVersion);
pv2 = await withRetryTransaction(dbService, (tx) async {
final v = await tx.lookupValue<PackageVersion>(pv.key);
v.updateIsModerated(isModerated: valueToSet!);
tx.insert(v);

// Update references to latest versions.
final pkg = await tx.lookupValue<Package>(p.key);
if (pkg.mayAffectLatestVersions(v.semanticVersion)) {
final versions =
await tx.query<PackageVersion>(pkg.key).run().toList();
pkg.updateLatestVersionReferences(
versions,
dartSdkVersion: currentDartSdk.semanticVersion,
replaced: v,
);
}
pkg.updated = clock.now().toUtc();
tx.insert(pkg);

return v;
});

// retract or re-populate public archive files
await updatePublicArchiveBucket(
package: package,
ageCheckThreshold: Duration.zero,
deleteIfOlder: Duration.zero,
);

await taskBackend.trackPackage(package);
await purgePackageCache(package);
}

return {
'package': p.name,
'version': pv.version,
'before': {
'isModerated': pv.isModerated,
'moderatedAt': pv.moderatedAt?.toIso8601String(),
},
if (pv2 != null)
'after': {
'isModerated': pv2.isModerated,
'moderatedAt': pv2.moderatedAt?.toIso8601String(),
},
};
},
);
3 changes: 3 additions & 0 deletions app/lib/package/backend.dart
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,9 @@ class PackageBackend {
if (pv == null) {
throw NotFoundException.resource(version);
}
if (pv.isModerated) {
throw ModeratedException.packageVersion(package, version);
}

if (options.isRetracted != null &&
options.isRetracted != pv.isRetracted) {
Expand Down
12 changes: 12 additions & 0 deletions app/lib/package/models.dart
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,12 @@ class Package extends db.ExpandoModel<String> {
}) {
final versions = allVersions
.map((v) => v.version == replaced?.version ? replaced! : v)
.where((v) => !v.isModerated)
.toList();
if (versions.isEmpty) {
throw NotAcceptableException('No visible versions left.');
}

final oldStableVersion = latestSemanticVersion;
final oldPrereleaseVersion = latestPrereleaseSemanticVersion;
final oldPreviewVersion = latestPreviewSemanticVersion;
Expand Down Expand Up @@ -666,6 +671,13 @@ class PackageVersion extends db.ExpandoModel<String> {
bool get canUndoRetracted =>
isRetracted &&
retracted!.isAfter(clock.now().toUtc().subtract(const Duration(days: 7)));

void updateIsModerated({
required bool isModerated,
}) {
this.isModerated = isModerated;
moderatedAt = isModerated ? clock.now().toUtc() : null;
}
}

/// A derived entity that holds derived/cleaned content of [PackageVersion].
Expand Down
3 changes: 3 additions & 0 deletions app/lib/shared/exceptions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -577,6 +577,9 @@ class RemovedPackageException extends NotFoundException {
class ModeratedException extends NotFoundException {
ModeratedException.package(String package)
: super('Package "$package" has been moderated.');

ModeratedException.packageVersion(String package, String version)
: super('PackageVersion "$package" "$version" has been moderated.');
}

/// Thrown when API endpoint is not implemented.
Expand Down
Loading

0 comments on commit 61d518f

Please sign in to comment.