Skip to content

Commit 974228d

Browse files
authored
Export more package API endpoints. (#8758)
1 parent 25c2a5a commit 974228d

File tree

8 files changed

+116
-10
lines changed

8 files changed

+116
-10
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ AppEngine version, listed here to ease deployment and troubleshooting.
44
## Next Release (replace with git tag when deployed)
55
* Bump runtimeVersion to `2025.06.20`.
66
* Upgraded stable Flutter analysis SDK to `3.32.4`.
7+
* Note: started to export `/api/packages/<package>/[likes|options|publisher|score]` endpoints.
78

89
## `20250619t085100-all`
910
* Bump runtimeVersion to `2025.06.03`.

app/lib/package/api_export/api_exporter.dart

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import 'package:gcloud/service_scope.dart' as ss;
1010
import 'package:gcloud/storage.dart';
1111
import 'package:logging/logging.dart';
1212
import 'package:pub_dev/frontend/handlers/atom_feed.dart';
13+
import 'package:pub_dev/scorecard/backend.dart';
1314
import 'package:pub_dev/service/security_advisories/backend.dart';
1415
import 'package:pub_dev/shared/exceptions.dart';
1516
import 'package:pub_dev/shared/parallel_foreach.dart';
@@ -245,6 +246,22 @@ final class ApiExporter {
245246
versionListing,
246247
forceWrite: forceWrite,
247248
);
249+
await _api.package(package).likes.write(
250+
await packageBackend.getPackageLikesCount(package),
251+
forceWrite: forceWrite,
252+
);
253+
await _api.package(package).options.write(
254+
await packageBackend.getPackageOptions(package),
255+
forceWrite: forceWrite,
256+
);
257+
await _api.package(package).publisher.write(
258+
await packageBackend.getPublisherInfo(package),
259+
forceWrite: forceWrite,
260+
);
261+
await _api.package(package).score.write(
262+
await scoreCardBackend.getVersionScore(package),
263+
forceWrite: forceWrite,
264+
);
248265
await _api.package(package).feedAtomFile.write(
249266
await buildPackageAtomFeedContent(package),
250267
forceWrite: forceWrite,

app/lib/package/api_export/exported_api.dart

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'dart:async';
66
import 'dart:convert';
77
import 'dart:io';
88

9+
import 'package:_pub_shared/data/account_api.dart';
910
import 'package:_pub_shared/data/advisories_api.dart';
1011
import 'package:_pub_shared/data/package_api.dart';
1112
import 'package:clock/clock.dart';
@@ -273,6 +274,20 @@ final class ExportedPackage {
273274
ExportedJsonFile<ListAdvisoriesResponse> get advisories =>
274275
_suffix<ListAdvisoriesResponse>('/advisories');
275276

277+
/// Interface for writing `/api/packages/<package>/likes`.
278+
ExportedJsonFile<PackageLikesCount> get likes =>
279+
_suffix<PackageLikesCount>('/likes');
280+
281+
/// Interface for writing `/api/packages/<package>/options`.
282+
ExportedJsonFile<PkgOptions> get options => _suffix<PkgOptions>('/options');
283+
284+
/// Interface for writing `/api/packages/<package>/publisher`.
285+
ExportedJsonFile<PackagePublisherInfo> get publisher =>
286+
_suffix<PackagePublisherInfo>('/publisher');
287+
288+
/// Interface for writing `/api/packages/<package>/score`.
289+
ExportedJsonFile<VersionScore> get score => _suffix<VersionScore>('/score');
290+
276291
/// Interface for writing `/api/packages/<package>/feed.atom`
277292
ExportedAtomFeedFile get feedAtomFile => ExportedAtomFeedFile._(
278293
_owner,
@@ -406,6 +421,10 @@ final class ExportedPackage {
406421
await Future.wait([
407422
_owner._pool.withResource(() async => await versions.delete()),
408423
_owner._pool.withResource(() async => await advisories.delete()),
424+
_owner._pool.withResource(() async => await likes.delete()),
425+
_owner._pool.withResource(() async => await options.delete()),
426+
_owner._pool.withResource(() async => await publisher.delete()),
427+
_owner._pool.withResource(() async => await score.delete()),
409428
_owner._pool.withResource(() async => await feedAtomFile.delete()),
410429
..._owner._prefixes.map((prefix) async {
411430
await _owner._listBucket(

app/lib/package/backend.dart

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import 'package:meta/meta.dart';
1818
import 'package:pool/pool.dart';
1919
import 'package:pub_dev/package/api_export/api_exporter.dart';
2020
import 'package:pub_dev/package/tarball_storage.dart';
21+
import 'package:pub_dev/scorecard/backend.dart';
2122
import 'package:pub_dev/service/async_queue/async_queue.dart';
2223
import 'package:pub_dev/service/rate_limit/rate_limit.dart';
2324
import 'package:pub_dev/shared/versions.dart';
@@ -481,6 +482,7 @@ class PackageBackend {
481482
});
482483
await purgePackageCache(package);
483484
await taskBackend.trackPackage(package);
485+
await apiExporter.synchronizePackage(package);
484486
}
485487

486488
/// Updates [options] on [package]/[version], assuming the current user
@@ -519,6 +521,9 @@ class PackageBackend {
519521
}
520522
});
521523
await purgePackageCache(package);
524+
await purgeScorecardData(package, version,
525+
isLatest: pkg.latestVersion == version);
526+
await apiExporter.synchronizePackage(package);
522527
}
523528

524529
/// Verifies an update to the credential-less publishing settings and
@@ -783,7 +788,7 @@ class PackageBackend {
783788
if (currentPublisherId != null) {
784789
await purgePublisherCache(publisherId: currentPublisherId);
785790
}
786-
791+
await apiExporter.synchronizePackage(packageName);
787792
return rs;
788793
}
789794

app/lib/task/backend.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import 'package:indexed_blob/indexed_blob.dart' show BlobIndex, FileRange;
1818
import 'package:logging/logging.dart' show Logger;
1919
import 'package:pana/models.dart' show Summary;
2020
import 'package:pool/pool.dart' show Pool;
21+
import 'package:pub_dev/package/api_export/api_exporter.dart';
2122
import 'package:pub_dev/package/backend.dart';
2223
import 'package:pub_dev/package/models.dart';
2324
import 'package:pub_dev/package/upload_signer_service.dart';
@@ -723,6 +724,7 @@ class TaskBackend {
723724

724725
// Clearing the state cache after the update.
725726
await _purgeCache(package, version);
727+
await apiExporter.synchronizePackage(package);
726728

727729
// If nothing else is running on the instance, delete it!
728730
// We do this in a microtask after returning, so that it doesn't slow down

app/test/admin/exported_api_sync_test.dart

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,8 @@ void main() {
7171
testWithProfile('baseline checks', fn: () async {
7272
await syncExportedApi();
7373
final data = await listExportedApi();
74-
expect(data.keys, hasLength(greaterThan(20)));
75-
expect(data.keys, hasLength(lessThan(40)));
74+
expect(data.keys, hasLength(greaterThan(50)));
75+
expect(data.keys, hasLength(lessThan(70)));
7676

7777
final oxygenFiles = data.keys.where((k) => k.contains('oxygen')).toSet();
7878
expect(oxygenFiles, hasLength(greaterThan(5)));
@@ -82,12 +82,20 @@ void main() {
8282
'$runtimeVersion/api/archives/oxygen-2.0.0-dev.tar.gz',
8383
'$runtimeVersion/api/packages/oxygen',
8484
'$runtimeVersion/api/packages/oxygen/advisories',
85+
'$runtimeVersion/api/packages/oxygen/likes',
86+
'$runtimeVersion/api/packages/oxygen/options',
87+
'$runtimeVersion/api/packages/oxygen/publisher',
88+
'$runtimeVersion/api/packages/oxygen/score',
8589
'$runtimeVersion/api/packages/oxygen/feed.atom',
8690
'latest/api/archives/oxygen-1.0.0.tar.gz',
8791
'latest/api/archives/oxygen-1.2.0.tar.gz',
8892
'latest/api/archives/oxygen-2.0.0-dev.tar.gz',
8993
'latest/api/packages/oxygen',
9094
'latest/api/packages/oxygen/advisories',
95+
'latest/api/packages/oxygen/likes',
96+
'latest/api/packages/oxygen/options',
97+
'latest/api/packages/oxygen/publisher',
98+
'latest/api/packages/oxygen/score',
9199
'latest/api/packages/oxygen/feed.atom',
92100
});
93101

app/test/package/api_export/api_exporter_test.dart

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -128,8 +128,36 @@ Future<void> _testExportedApiSynchronization(
128128
isNotNull,
129129
);
130130
expect(
131-
await bucket.readBytes('$runtimeVersion/api/packages/foo/feed.atom'),
132-
isNotNull,
131+
await bucket.readGzippedJson('$runtimeVersion/api/packages/foo/likes'),
132+
{
133+
'package': 'foo',
134+
'likes': 0,
135+
},
136+
);
137+
expect(
138+
await bucket.readGzippedJson('$runtimeVersion/api/packages/foo/options'),
139+
{
140+
'isDiscontinued': false,
141+
'replacedBy': null,
142+
'isUnlisted': false,
143+
},
144+
);
145+
expect(
146+
await bucket
147+
.readGzippedJson('$runtimeVersion/api/packages/foo/publisher'),
148+
{
149+
'publisherId': null,
150+
},
151+
);
152+
expect(
153+
await bucket.readGzippedJson('$runtimeVersion/api/packages/foo/score'),
154+
{
155+
'grantedPoints': isNotNull,
156+
'maxPoints': isNotNull,
157+
'likeCount': isNotNull,
158+
'tags': isNotEmpty,
159+
'lastUpdated': isNotNull,
160+
},
133161
);
134162
expect(
135163
await bucket.readGzippedJson('$runtimeVersion/api/packages/foo'),
@@ -139,6 +167,10 @@ Future<void> _testExportedApiSynchronization(
139167
'versions': hasLength(1),
140168
},
141169
);
170+
expect(
171+
await bucket.readString('$runtimeVersion/api/packages/foo/feed.atom'),
172+
contains('v1.0.0 of foo'),
173+
);
142174
expect(
143175
await bucket
144176
.readGzippedJson('$runtimeVersion/api/package-name-completion-data'),
@@ -152,10 +184,6 @@ Future<void> _testExportedApiSynchronization(
152184
await bucket.readString('$runtimeVersion/feed.atom'),
153185
contains('v1.0.0 of foo'),
154186
);
155-
expect(
156-
await bucket.readString('$runtimeVersion/api/packages/foo/feed.atom'),
157-
contains('v1.0.0 of foo'),
158-
);
159187
}
160188

161189
_log.info('## New package');
@@ -193,6 +221,22 @@ Future<void> _testExportedApiSynchronization(
193221
await bucket.readString('latest/api/packages/foo/feed.atom'),
194222
contains('v1.0.0 of foo'),
195223
);
224+
expect(
225+
await bucket.readGzippedJson('latest/api/packages/foo/likes'),
226+
isNotNull,
227+
);
228+
expect(
229+
await bucket.readGzippedJson('latest/api/packages/foo/options'),
230+
isNotNull,
231+
);
232+
expect(
233+
await bucket.readGzippedJson('latest/api/packages/foo/publisher'),
234+
isNotNull,
235+
);
236+
expect(
237+
await bucket.readGzippedJson('latest/api/packages/foo/score'),
238+
isNotNull,
239+
);
196240
// Note. that name completion data won't be updated until search caches
197241
// are purged, so we won't test that it is updated.
198242

@@ -440,6 +484,10 @@ Future<void> _testExportedApiSynchronization(
440484
await bucket.readGzippedJson('latest/api/packages/bar'),
441485
isNull,
442486
);
487+
expect(
488+
await bucket.readGzippedJson('latest/api/packages/bar/options'),
489+
isNull,
490+
);
443491
expect(
444492
await bucket.readGzippedJson('latest/api/packages/feed.atom'),
445493
isNull,
@@ -480,6 +528,10 @@ Future<void> _testExportedApiSynchronization(
480528
'versions': hasLength(2),
481529
},
482530
);
531+
expect(
532+
await bucket.readGzippedJson('latest/api/packages/bar/options'),
533+
isNotNull,
534+
);
483535
expect(
484536
await bucket.readBytes('latest/api/archives/bar-2.0.0.tar.gz'),
485537
isNotNull,

app/test/task/task_test.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,9 @@ void main() {
176176
await clockControl.elapse(minutes: 10);
177177
});
178178

179-
testWithProfile('failing instances will be retried', fn: () async {
179+
testWithProfile('failing instances will be retried', expectedLogMessages: [
180+
'SHOUT [pub-notice:cached_value] Updating cached `thirtyDaysTotalDownloadCounts` value failed.',
181+
], fn: () async {
180182
await taskBackend.backfillTrackingState();
181183
await clockControl.elapse(minutes: 1);
182184

0 commit comments

Comments
 (0)