Files
zapstore/lib/utils/app_query.dart
T
2026-04-04 23:57:22 -03:00

115 lines
3.1 KiB
Dart

import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:models/models.dart';
/// Reactive query provider: fetches SoftwareAsset (3063) events and resolves
/// their parent App (32267) via the direct relationship.
///
/// This is the primary query path for app listings. Each SoftwareAsset carries
/// version, versionCode, hash, urls, and platform — everything needed for
/// app cards and update detection — and links directly to its App.
AutoDisposeStateNotifierProvider<
RequestNotifier<SoftwareAsset>,
StorageState<SoftwareAsset>
>
appAssetsQuery({
Set<String>? authors,
Map<String, Set<String>>? tags,
String? search,
DateTime? since,
DateTime? until,
int? limit,
Source? source,
required String subscriptionPrefix,
}) {
return query<SoftwareAsset>(
authors: authors,
tags: tags,
search: search,
since: since,
until: until,
limit: limit,
and: (asset) => {
asset.app.query(source: const LocalAndRemoteSource(relays: 'AppCatalog', stream: false)),
asset.author.query(
source: const LocalAndRemoteSource(
relays: {'vertex', 'social'},
cachedFor: Duration(hours: 2),
stream: false,
),
),
},
source: source,
subscriptionPrefix: subscriptionPrefix,
);
}
/// Result of a paginated asset-first fetch.
class AssetFetchResult {
final List<App> apps;
final int assetCount;
const AssetFetchResult(this.apps, this.assetCount);
}
/// Imperative one-shot: fetches a page of SoftwareAsset (3063) events,
/// resolves their parent Apps, and pre-loads author profiles.
/// Returns the resolved Apps (deduplicated) and the raw asset count
/// (for accurate pagination — multiple assets may map to one app).
Future<AssetFetchResult> fetchAppsByAsset(
StorageNotifier storage, {
Map<String, Set<String>>? tags,
DateTime? until,
int? limit,
Source? source,
required String subscriptionPrefix,
}) async {
final assets = await storage.query(
RequestFilter<SoftwareAsset>(
tags: tags,
until: until,
limit: limit,
).toRequest(),
source: source,
subscriptionPrefix: subscriptionPrefix,
);
if (assets.isEmpty) return const AssetFetchResult([], 0);
final appFilters = assets
.map((a) => a.app.req?.filters.firstOrNull)
.nonNulls
.toList();
if (appFilters.isEmpty) return AssetFetchResult(const [], assets.length);
final apps = await storage.query(
Request<App>(appFilters),
source: source,
subscriptionPrefix: '$subscriptionPrefix-apps',
);
await loadAuthors(storage, apps, '$subscriptionPrefix-authors');
return AssetFetchResult(apps, assets.length);
}
Future<void> loadAuthors(
StorageNotifier storage,
List<App> apps,
String subscriptionPrefix,
) async {
if (apps.isEmpty) return;
final authorFilters = apps
.map((a) => a.author.req?.filters.firstOrNull)
.nonNulls
.toList();
if (authorFilters.isEmpty) return;
await storage.query(
Request<Profile>(authorFilters),
source: const LocalAndRemoteSource(
relays: {'vertex', 'social'},
cachedFor: Duration(hours: 2),
),
subscriptionPrefix: subscriptionPrefix,
);
}