Skip to content

Commit

Permalink
Discontinue packages if the moderated user is single owner. (dart-lan…
Browse files Browse the repository at this point in the history
  • Loading branch information
isoos authored Apr 17, 2024
1 parent 46d1ebd commit a7390b3
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 6 deletions.
66 changes: 63 additions & 3 deletions app/lib/admin/actions/moderate_user.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,15 @@
// 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/account/agent.dart';
import 'package:pub_dev/account/backend.dart';
import 'package:pub_dev/account/models.dart';
import 'package:clock/clock.dart';

import '../../account/agent.dart';
import '../../account/backend.dart';
import '../../account/models.dart';
import '../../package/backend.dart';
import '../../package/models.dart';
import '../../publisher/backend.dart';
import '../../shared/datastore.dart';

import 'actions.dart';

Expand Down Expand Up @@ -56,6 +62,60 @@ The active web sessions of the user will be expired.
if (valueToSet != null) {
await accountBackend.updateModeratedFlag(user!.userId, valueToSet);
user2 = await accountBackend.lookupUserById(user.userId);

if (valueToSet) {
await for (final p
in packageBackend.streamPackagesWhereUserIsUploader(user.userId)) {
await withRetryTransaction(dbService, (tx) async {
final key = dbService.emptyKey.append(Package, id: p);
final pkg = await tx.lookupOrNull<Package>(key);
if (pkg == null || pkg.isDiscontinued || pkg.uploaderCount != 1) {
return;
}
pkg.isDiscontinued = true;
pkg.updated = clock.now().toUtc();
tx.insert(pkg);
});
}

final publishers =
await publisherBackend.listPublishersForUser(user.userId);
for (final e in publishers.publishers!) {
final p = await publisherBackend.getPublisher(e.publisherId);
if (p == null) {
continue;
}
// Only restrict publishers where the user was a single active admin.
// Note: at this point the User.isModerated flag is already set.
final members =
await publisherBackend.listPublisherMembers(e.publisherId);
var nonBlockedCount = 0;
for (final member in members) {
final mu = await accountBackend.lookupUserById(member.userId);
if (mu?.isVisible ?? false) {
nonBlockedCount++;
}
}
if (nonBlockedCount > 0) {
continue;
}

final query = dbService.query<Package>()
..filter('publisherId =', e.publisherId);
await for (final p in query.run()) {
if (p.isDiscontinued) continue;
await withRetryTransaction(dbService, (tx) async {
final pkg = await tx.lookupOrNull<Package>(p.key);
if (pkg == null || pkg.isDiscontinued) {
return;
}
pkg.isDiscontinued = true;
pkg.updated = clock.now().toUtc();
tx.insert(pkg);
});
}
}
}
}

return {
Expand Down
13 changes: 13 additions & 0 deletions app/lib/package/backend.dart
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,19 @@ class PackageBackend {
);
}

/// Streams package names where the [userId] is an uploader.
Stream<String> streamPackagesWhereUserIsUploader(String userId) async* {
var page = await listPackagesForUser(userId);
while (page.packages.isNotEmpty) {
yield* Stream.fromIterable(page.packages);
if (page.nextPackage == null) {
break;
} else {
page = await listPackagesForUser(userId, next: page.nextPackage);
}
}
}

/// Returns the latest releases info of a package.
Future<LatestReleases> latestReleases(Package package) async {
// TODO: implement runtimeVersion-specific release calculation
Expand Down
42 changes: 39 additions & 3 deletions app/test/account/moderate_user_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import 'package:pub_dev/account/auth_provider.dart';
import 'package:pub_dev/account/backend.dart';
import 'package:pub_dev/account/models.dart';
import 'package:pub_dev/fake/backend/fake_auth_provider.dart';
import 'package:pub_dev/package/backend.dart';
import 'package:pub_dev/shared/configuration.dart';
import 'package:pub_dev/shared/datastore.dart';
import 'package:test/test.dart';
Expand Down Expand Up @@ -163,8 +164,43 @@ void main() {
expect(rs.websiteUrl, 'https://other.com/');
});

// TODO(https://github.com/dart-lang/pub-dev/issues/7535):
// - single packages owned exclusively by the user are marked discontinued
// - publisher packages owned exclusively by the user are marked discontinued
testWithProfile('single packages marked discontinued', fn: () async {
final pubspecContent = generatePubspecYaml('foo', '1.0.0');
final bytes = await packageArchiveBytes(pubspecContent: pubspecContent);
await createPubApiClient(authToken: userClientToken)
.uploadPackageBytes(bytes);

await _moderate('user@pub.dev', state: true);
final p1 = await packageBackend.lookupPackage('foo');
expect(p1!.isDiscontinued, true);

await _moderate('user@pub.dev', state: false);
final p2 = await packageBackend.lookupPackage('foo');
expect(p2!.isDiscontinued, true);
});

testWithProfile('publisher packages marked discontinued', fn: () async {
final client = await createFakeAuthPubApiClient(
email: 'user@pub.dev', scopes: [webmasterScope]);
await client.createPublisher('verified.com');

final pubspecContent = generatePubspecYaml('foo', '1.0.0');
final bytes = await packageArchiveBytes(pubspecContent: pubspecContent);
await createPubApiClient(authToken: userClientToken)
.uploadPackageBytes(bytes);
await (await createFakeAuthPubApiClient(email: 'user@pub.dev'))
.setPackagePublisher(
'foo', PackagePublisherInfo(publisherId: 'verified.com'));

await _moderate('user@pub.dev', state: true);
final p1 = await packageBackend.lookupPackage('foo');
expect(p1!.publisherId, isNotEmpty);
expect(p1.isDiscontinued, true);

await _moderate('user@pub.dev', state: false);
final p2 = await packageBackend.lookupPackage('foo');
expect(p2!.publisherId, isNotEmpty);
expect(p2.isDiscontinued, true);
});
});
}

0 comments on commit a7390b3

Please sign in to comment.