diff --git a/app/lib/admin/actions/actions.dart b/app/lib/admin/actions/actions.dart index d158d13ea..b46aadfbf 100644 --- a/app/lib/admin/actions/actions.dart +++ b/app/lib/admin/actions/actions.dart @@ -2,8 +2,6 @@ // 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_dev/admin/actions/package_invite_uploader.dart'; - import '../../shared/exceptions.dart'; import 'download_counts_backfill.dart'; import 'download_counts_delete.dart'; @@ -24,7 +22,9 @@ import 'moderation_transparency_metrics.dart'; import 'package_delete.dart'; import 'package_discontinue.dart'; import 'package_info.dart'; +import 'package_invite_uploader.dart'; import 'package_latest_update.dart'; +import 'package_publisher_set.dart'; import 'package_reservation_create.dart'; import 'package_reservation_delete.dart'; import 'package_reservation_list.dart'; @@ -115,6 +115,7 @@ final class AdminAction { packageInfo, packageInviteUploader, packageLatestUpdate, + packagePublisherSet, packageReservationCreate, packageReservationDelete, packageReservationList, diff --git a/app/lib/admin/actions/package_publisher_set.dart b/app/lib/admin/actions/package_publisher_set.dart new file mode 100644 index 000000000..dea56ccd5 --- /dev/null +++ b/app/lib/admin/actions/package_publisher_set.dart @@ -0,0 +1,64 @@ +// 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:clock/clock.dart'; +import 'package:pub_dev/package/backend.dart'; +import 'package:pub_dev/package/models.dart'; +import 'package:pub_dev/publisher/backend.dart'; +import 'package:pub_dev/shared/datastore.dart'; + +import 'actions.dart'; + +final packagePublisherSet = AdminAction( + name: 'package-publisher-set', + summary: 'Sets the publisher for a package.', + description: 'Sets a new `publisherId` for a `package`.', + options: { + 'package': 'The package to be updated.', + 'publisher': 'The `publisherId` to set.', + }, + invoke: (options) async { + final packageName = options['package']; + final publisherId = options['publisher']; + + if (packageName == null) { + throw InvalidInputException('Missing --package argument.'); + } + if (publisherId == null) { + throw InvalidInputException('Missing --publisher argument.'); + } + + final package = (await packageBackend.lookupPackage(packageName))!; + final publisher = await publisherBackend.lookupPublisher(publisherId); + if (publisher == null || !publisher.isVisible) { + InvalidInputException( + 'Publisher `$publisherId` does not exists or is not visible.', + ); + } + final currentPublisherId = package.publisherId; + if (currentPublisherId != publisherId) { + await withRetryTransaction(dbService, (tx) async { + final pkg = await tx.lookupValue(package.key); + pkg.publisherId = publisherId; + pkg.updated = clock.now().toUtc(); + tx.insert(pkg); + }); + await purgePublisherCache(publisherId: publisherId); + triggerPackagePostUpdates( + packageName, + skipReanalysis: true, + skipVersionsExport: true, + ); + if (currentPublisherId != null) { + await purgePublisherCache(publisherId: currentPublisherId); + } + } + + final pkg = await packageBackend.lookupPackage(packageName); + return { + 'before': {'publisherId': currentPublisherId}, + 'after': {'publisherId': pkg!.publisherId}, + }; + }, +); diff --git a/app/lib/admin/backend.dart b/app/lib/admin/backend.dart index 6f60d653f..3a423ff1c 100644 --- a/app/lib/admin/backend.dart +++ b/app/lib/admin/backend.dart @@ -36,7 +36,6 @@ import 'actions/actions.dart' show AdminAction; import 'tools/delete_all_staging.dart'; import 'tools/list_tools.dart'; import 'tools/notify_service.dart'; -import 'tools/package_publisher.dart'; import 'tools/recent_uploaders.dart'; import 'tools/user_merger.dart'; @@ -55,7 +54,6 @@ typedef Tool = Future Function(List args); final Map availableTools = { 'delete-all-staging': executeDeleteAllStaging, 'notify-service': executeNotifyService, - 'package-publisher': executeSetPackagePublisher, 'recent-uploaders': executeRecentUploaders, 'user-merger': executeUserMergerTool, 'list-tools': executeListTools, diff --git a/app/lib/admin/tools/package_publisher.dart b/app/lib/admin/tools/package_publisher.dart deleted file mode 100644 index 292aa9cfc..000000000 --- a/app/lib/admin/tools/package_publisher.dart +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (c) 2020, 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:args/args.dart'; -import 'package:clock/clock.dart'; - -import 'package:pub_dev/package/backend.dart'; -import 'package:pub_dev/package/models.dart'; -import 'package:pub_dev/publisher/backend.dart'; -import 'package:pub_dev/shared/datastore.dart'; - -final _argParser = ArgParser() - ..addOption('package', help: 'The package to update.') - ..addOption('publisher-id', help: 'The `publisherId` to set.') - ..addFlag('help', abbr: 'h', defaultsTo: false, help: 'Show help.'); - -Future executeSetPackagePublisher(List args) async { - final argv = _argParser.parse(args); - final packageName = argv['package'] as String?; - final publisherId = argv['publisher-id'] as String?; - - if (argv['help'] as bool || packageName == null || publisherId == null) { - return 'Sets a new `publisherId` for a `package`.\n' - ' --package --publisher-id \n' - '${_argParser.usage}'; - } - - final package = (await packageBackend.lookupPackage(packageName))!; - final publisher = await publisherBackend.lookupPublisher(publisherId); - if (publisher == null) { - return 'No such publisher.'; - } - final currentPublisherId = package.publisherId; - if (currentPublisherId == publisherId) { - return 'No update needed.'; - } - await withRetryTransaction(dbService, (tx) async { - final pkg = await tx.lookupValue(package.key); - pkg.publisherId = publisherId; - pkg.updated = clock.now().toUtc(); - tx.insert(pkg); - }); - await purgePublisherCache(publisherId: publisherId); - triggerPackagePostUpdates( - packageName, - skipReanalysis: true, - skipVersionsExport: true, - ); - if (currentPublisherId != null) { - await purgePublisherCache(publisherId: currentPublisherId); - } - return 'Done.'; -} diff --git a/app/test/admin/package_actions_test.dart b/app/test/admin/package_actions_test.dart index 86f88ab79..14c020862 100644 --- a/app/test/admin/package_actions_test.dart +++ b/app/test/admin/package_actions_test.dart @@ -75,5 +75,22 @@ void main() { expect(rs.output, {'updatedCount': 0}); }, ); + + testWithProfile( + 'set publisher on a package', + fn: () async { + final client = createPubApiClient(authToken: siteAdminToken); + final rs = await client.adminInvokeAction( + 'package-publisher-set', + AdminInvokeActionArguments( + arguments: {'package': 'oxygen', 'publisher': 'example.com'}, + ), + ); + expect(rs.output, { + 'before': {'publisherId': null}, + 'after': {'publisherId': 'example.com'}, + }); + }, + ); }); }