@_nazmiforreal/flutter-ota 0.1.11 → 0.1.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/bin/flutter-ota-linux-x64 +0 -0
- package/dart-src/packages/cli-tools/.dart_tool/package_config.json +46 -76
- package/dart-src/packages/cli-tools/.dart_tool/package_graph.json +140 -166
- package/dart-src/packages/cli-tools/README.md +1 -1
- package/dart-src/packages/cli-tools/lib/flutter_ota_kit_cli.dart +30 -17
- package/dart-src/packages/cli-tools/lib/src/backend.dart +30 -31
- package/dart-src/packages/cli-tools/lib/src/cli_base.dart +3 -1
- package/dart-src/packages/cli-tools/lib/src/commands/build.dart +44 -43
- package/dart-src/packages/cli-tools/lib/src/commands/bundle.dart +265 -239
- package/dart-src/packages/cli-tools/lib/src/commands/channel.dart +75 -66
- package/dart-src/packages/cli-tools/lib/src/commands/config_command.dart +39 -35
- package/dart-src/packages/cli-tools/lib/src/commands/console.dart +30 -27
- package/dart-src/packages/cli-tools/lib/src/commands/deploy.dart +88 -72
- package/dart-src/packages/cli-tools/lib/src/commands/doctor.dart +66 -29
- package/dart-src/packages/cli-tools/lib/src/commands/fingerprint.dart +10 -6
- package/dart-src/packages/cli-tools/lib/src/commands/init.dart +299 -190
- package/dart-src/packages/cli-tools/lib/src/commands/keys.dart +24 -23
- package/dart-src/packages/cli-tools/lib/src/commands/migrate.dart +111 -97
- package/dart-src/packages/cli-tools/lib/src/commands/pocketbase.dart +237 -0
- package/dart-src/packages/cli-tools/lib/src/commands/rollback.dart +43 -34
- package/dart-src/packages/cli-tools/lib/src/commands/storage.dart +57 -53
- package/dart-src/packages/cli-tools/lib/src/config.dart +227 -93
- package/dart-src/packages/cli-tools/lib/src/operations.dart +9 -8
- package/dart-src/packages/cli-tools/lib/src/pack.dart +26 -25
- package/dart-src/packages/cli-tools/lib/src/pocketbase/data_bootstrap.dart +39 -0
- package/dart-src/packages/cli-tools/lib/src/pocketbase/hooks/bundles.pb.js +56 -0
- package/dart-src/packages/cli-tools/lib/src/pocketbase/installer.dart +189 -0
- package/dart-src/packages/cli-tools/lib/src/pocketbase/process_manager.dart +123 -0
- package/dart-src/packages/cli-tools/lib/src/pocketbase/schema_installer.dart +237 -0
- package/dart-src/packages/cli-tools/lib/src/runner.dart +36 -15
- package/dart-src/packages/cli-tools/lib/src/sign.dart +13 -5
- package/dart-src/packages/cli-tools/lib/src/ui/ui.dart +10 -13
- package/dart-src/packages/cli-tools/lib/src/util.dart +3 -1
- package/dart-src/packages/cli-tools/pubspec.lock +69 -116
- package/dart-src/packages/cli-tools/pubspec.yaml +25 -7
- package/dart-src/packages/cli-tools/test/backends_test.dart +62 -32
- package/dart-src/packages/cli-tools/test/build_pack_test.dart +4 -4
- package/dart-src/packages/cli-tools/test/cli_test.dart +89 -42
- package/dart-src/packages/cli-tools/test/mocks/mock_aws_s3_client.dart +8 -13
- package/dart-src/packages/cli-tools/test/mocks/mock_d1_client.dart +23 -18
- package/dart-src/packages/cli-tools/test/mocks/mock_pocketbase_client.dart +197 -0
- package/dart-src/packages/cli-tools/test/mocks/mock_postgres_client.dart +70 -46
- package/dart-src/packages/cli-tools/test/pocketbase_installer_test.dart +72 -0
- package/dart-src/packages/cli-tools/test/pocketbase_schema_test.dart +107 -0
- package/dart-src/packages/cli-tools/test/pocketbase_smoke_test.dart +59 -0
- package/dart-src/packages/core/README.md +2 -2
- package/dart-src/packages/core/example/lib/main.dart +69 -0
- package/dart-src/packages/core/lib/flutter_ota_kit_core.dart +1 -1
- package/dart-src/packages/core/lib/src/app_update_info.dart +17 -15
- package/dart-src/packages/core/lib/src/bundle.dart +102 -94
- package/dart-src/packages/core/lib/src/bundle_artifacts.dart +1 -2
- package/dart-src/packages/core/lib/src/bundle_patch_artifact.dart +15 -16
- package/dart-src/packages/core/lib/src/changed_asset.dart +24 -22
- package/dart-src/packages/core/lib/src/get_bundles_args.dart +13 -13
- package/dart-src/packages/core/lib/src/metadata.dart +9 -7
- package/dart-src/packages/core/lib/src/patch_info.dart +8 -7
- package/dart-src/packages/core/lib/src/platform.dart +4 -3
- package/dart-src/packages/core/lib/src/rollout.dart +14 -11
- package/dart-src/packages/core/lib/src/semver_range.dart +49 -39
- package/dart-src/packages/core/lib/src/semver_version.dart +8 -7
- package/dart-src/packages/core/lib/src/status.dart +4 -3
- package/dart-src/packages/core/lib/src/update_bundle_params.dart +7 -7
- package/dart-src/packages/core/lib/src/uuid.dart +1 -2
- package/dart-src/packages/core/pubspec.yaml +6 -7
- package/dart-src/packages/core/test/rollout_test.dart +15 -8
- package/dart-src/packages/core/test/semver_test.dart +5 -2
- package/dart-src/packages/core/test/types_test.dart +26 -24
- package/dart-src/packages/core/test/uuid_test.dart +3 -1
- package/dart-src/packages/core/tool/dbg.dart +1 -0
- package/dart-src/plugins/aws/CHANGELOG.md +6 -0
- package/dart-src/plugins/aws/example/lib/main.dart +60 -0
- package/dart-src/plugins/aws/lib/flutter_ota_kit_aws.dart +5 -1
- package/dart-src/plugins/aws/lib/src/aws_cloudfront_client.dart +11 -16
- package/dart-src/plugins/aws/lib/src/aws_cloudfront_signer.dart +7 -2
- package/dart-src/plugins/aws/lib/src/aws_database.dart +14 -18
- package/dart-src/plugins/aws/lib/src/aws_s3_client.dart +23 -18
- package/dart-src/plugins/aws/lib/src/aws_ssm_client.dart +5 -4
- package/dart-src/plugins/aws/lib/src/aws_storage.dart +2 -3
- package/dart-src/plugins/aws/lib/src/aws_storage_profile.dart +10 -16
- package/dart-src/plugins/aws/lib/src/with_cloudfront_signed_url.dart +6 -9
- package/dart-src/plugins/aws/pubspec.yaml +15 -10
- package/dart-src/plugins/aws/test/aws_database_test.dart +51 -57
- package/dart-src/plugins/aws/test/mock_aws_s3_client.dart +8 -13
- package/dart-src/plugins/cloudflare/CHANGELOG.md +7 -0
- package/dart-src/plugins/cloudflare/example/lib/main.dart +62 -0
- package/dart-src/plugins/cloudflare/lib/flutter_ota_kit_cloudflare.dart +4 -2
- package/dart-src/plugins/cloudflare/lib/src/cloudflare_worker_database.dart +3 -3
- package/dart-src/plugins/cloudflare/lib/src/d1_build_where.dart +3 -2
- package/dart-src/plugins/cloudflare/lib/src/d1_bundle_mapper.dart +24 -27
- package/dart-src/plugins/cloudflare/lib/src/d1_database_plugin.dart +22 -30
- package/dart-src/plugins/cloudflare/lib/src/r2_config.dart +1 -1
- package/dart-src/plugins/cloudflare/lib/src/r2_s3_client.dart +21 -29
- package/dart-src/plugins/cloudflare/lib/src/r2_storage.dart +7 -7
- package/dart-src/plugins/cloudflare/lib/src/r2_storage_profile.dart +7 -15
- package/dart-src/plugins/cloudflare/pubspec.yaml +14 -9
- package/dart-src/plugins/cloudflare/test/cloudflare_worker_database_test.dart +46 -42
- package/dart-src/plugins/cloudflare/test/d1_database_test.dart +100 -90
- package/dart-src/plugins/cloudflare/test/mock_d1_client.dart +23 -18
- package/dart-src/plugins/cloudflare/test/mock_r2_client.dart +8 -13
- package/dart-src/plugins/cloudflare/test/r2_storage_test.dart +1 -4
- package/dart-src/plugins/plugin-core/CHANGELOG.md +16 -0
- package/dart-src/plugins/plugin-core/example/lib/main.dart +61 -0
- package/dart-src/plugins/plugin-core/lib/flutter_ota_kit_plugin_core.dart +3 -7
- package/dart-src/plugins/plugin-core/lib/src/asset_storage_layout.dart +5 -5
- package/dart-src/plugins/plugin-core/lib/src/bundle_helpers.dart +23 -0
- package/dart-src/plugins/plugin-core/lib/src/bundle_storage_layout.dart +16 -10
- package/dart-src/plugins/plugin-core/lib/src/bundle_unit_of_work.dart +31 -26
- package/dart-src/plugins/plugin-core/lib/src/calculate_pagination.dart +5 -1
- package/dart-src/plugins/plugin-core/lib/src/content_addressed_assets.dart +1 -1
- package/dart-src/plugins/plugin-core/lib/src/create_blob_database_plugin.dart +78 -71
- package/dart-src/plugins/plugin-core/lib/src/create_database_plugin.dart +56 -35
- package/dart-src/plugins/plugin-core/lib/src/create_storage_plugin.dart +10 -13
- package/dart-src/plugins/plugin-core/lib/src/paginate_bundles.dart +30 -17
- package/dart-src/plugins/plugin-core/lib/src/parse_storage_uri.dart +3 -1
- package/dart-src/plugins/plugin-core/lib/src/query_bundles.dart +5 -3
- package/dart-src/plugins/plugin-core/lib/src/request_update_bundle_state.dart +2 -4
- package/dart-src/plugins/plugin-core/lib/src/storage_helpers.dart +18 -0
- package/dart-src/plugins/plugin-core/lib/src/types.dart +7 -6
- package/dart-src/plugins/plugin-core/pubspec.yaml +12 -9
- package/dart-src/plugins/plugin-core/test/asset_storage_layout_test.dart +13 -9
- package/dart-src/plugins/plugin-core/test/bundle_unit_of_work_test.dart +28 -15
- package/dart-src/plugins/plugin-core/test/create_blob_database_plugin_test.dart +254 -122
- package/dart-src/plugins/plugin-core/test/create_database_plugin_test.dart +36 -32
- package/dart-src/plugins/plugin-core/test/create_storage_plugin_test.dart +18 -15
- package/dart-src/plugins/plugin-core/test/generate_min_bundle_id_test.dart +8 -3
- package/dart-src/plugins/plugin-core/test/get_update_info_test.dart +682 -693
- package/dart-src/plugins/plugin-core/test/helpers_test.dart +45 -21
- package/dart-src/plugins/plugin-core/test/query_bundles_test.dart +76 -13
- package/dart-src/plugins/plugin-core/test/uuidv7_test.dart +20 -9
- package/dart-src/plugins/pocketbase/CHANGELOG.md +8 -0
- package/dart-src/plugins/pocketbase/LICENSE +21 -0
- package/dart-src/plugins/pocketbase/README.md +54 -0
- package/dart-src/plugins/pocketbase/example/lib/main.dart +62 -0
- package/dart-src/plugins/pocketbase/lib/flutter_ota_kit_pocketbase.dart +28 -0
- package/dart-src/plugins/pocketbase/lib/src/pocketbase_bundle_mapper.dart +121 -0
- package/dart-src/plugins/pocketbase/lib/src/pocketbase_client.dart +349 -0
- package/dart-src/plugins/pocketbase/lib/src/pocketbase_config.dart +61 -0
- package/dart-src/plugins/pocketbase/lib/src/pocketbase_database.dart +278 -0
- package/dart-src/plugins/pocketbase/lib/src/pocketbase_storage.dart +255 -0
- package/dart-src/plugins/pocketbase/pubspec.yaml +26 -0
- package/dart-src/plugins/pocketbase/test/pb_uri_test.dart +12 -0
- package/dart-src/plugins/postgres/CHANGELOG.md +6 -0
- package/dart-src/plugins/postgres/example/lib/main.dart +67 -0
- package/dart-src/plugins/postgres/lib/flutter_ota_kit_postgres.dart +3 -6
- package/dart-src/plugins/postgres/lib/src/postgres_bundle_mapper.dart +32 -52
- package/dart-src/plugins/postgres/lib/src/postgres_config.dart +3 -1
- package/dart-src/plugins/postgres/lib/src/postgres_database.dart +28 -38
- package/dart-src/plugins/postgres/lib/src/postgres_get_update_info.dart +16 -14
- package/dart-src/plugins/postgres/lib/src/postgres_storage.dart +26 -14
- package/dart-src/plugins/postgres/lib/src/postgres_types.dart +27 -28
- package/dart-src/plugins/postgres/pubspec.yaml +14 -9
- package/dart-src/plugins/postgres/test/mock_postgres_client.dart +62 -39
- package/dart-src/plugins/postgres/test/postgres_bundle_mapper_test.dart +3 -4
- package/dart-src/plugins/postgres/test/postgres_database_test.dart +102 -73
- package/dart-src/plugins/postgres/test/postgres_storage_runtime_test.dart +60 -0
- package/dart-src/plugins/supabase/CHANGELOG.md +17 -0
- package/dart-src/plugins/supabase/README.md +2 -2
- package/dart-src/plugins/supabase/example/lib/main.dart +63 -0
- package/dart-src/plugins/supabase/lib/flutter_ota_kit_supabase.dart +2 -0
- package/dart-src/plugins/supabase/lib/src/error_message.dart +11 -0
- package/dart-src/plugins/supabase/lib/src/supabase_bundle_mapper.dart +34 -58
- package/dart-src/plugins/supabase/lib/src/supabase_client_adapter.dart +3 -13
- package/dart-src/plugins/supabase/lib/src/supabase_client_http.dart +34 -52
- package/dart-src/plugins/supabase/lib/src/supabase_database.dart +38 -30
- package/dart-src/plugins/supabase/lib/src/supabase_edge_function_storage.dart +21 -29
- package/dart-src/plugins/supabase/lib/src/supabase_signed_url_batcher.dart +7 -19
- package/dart-src/plugins/supabase/lib/src/supabase_storage.dart +18 -29
- package/dart-src/plugins/supabase/lib/src/types.dart +26 -26
- package/dart-src/plugins/supabase/pubspec.yaml +14 -9
- package/dart-src/plugins/supabase/test/mock_supabase_client.dart +57 -57
- package/dart-src/plugins/supabase/test/supabase_bundle_mapper_test.dart +30 -27
- package/dart-src/plugins/supabase/test/supabase_database_test.dart +26 -35
- package/dart-src/plugins/supabase/test/supabase_storage_test.dart +27 -34
- package/package.json +2 -2
- package/scripts/vendor.sh +1 -1
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
/// PocketBase-backed [StoragePlugin] implementation.
|
|
2
|
+
///
|
|
3
|
+
/// Uses PocketBase's built-in file storage (per-collection buckets) for
|
|
4
|
+
/// bundle artifacts. Each bundle record carries a single `artifact` file
|
|
5
|
+
/// field that stores the patch zip; the storage URI is a `pb://` URL that
|
|
6
|
+
/// the runtime resolves to a signed download URL on the client.
|
|
7
|
+
library;
|
|
8
|
+
|
|
9
|
+
import 'dart:io';
|
|
10
|
+
|
|
11
|
+
import 'package:flutter_ota_kit_plugin_core/flutter_ota_kit_plugin_core.dart';
|
|
12
|
+
|
|
13
|
+
import 'pocketbase_client.dart';
|
|
14
|
+
|
|
15
|
+
/// Configuration for the PocketBase storage plugin.
|
|
16
|
+
class PocketBaseStorageConfig {
|
|
17
|
+
const PocketBaseStorageConfig({
|
|
18
|
+
required this.url,
|
|
19
|
+
required this.adminEmail,
|
|
20
|
+
required this.adminPassword,
|
|
21
|
+
required this.bundlesCollection,
|
|
22
|
+
this.bundlesBucket = 'bundles',
|
|
23
|
+
this.basePath,
|
|
24
|
+
this.clientFactory,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
final String url;
|
|
28
|
+
final String adminEmail;
|
|
29
|
+
final String adminPassword;
|
|
30
|
+
final String bundlesCollection;
|
|
31
|
+
final String bundlesBucket;
|
|
32
|
+
final String? basePath;
|
|
33
|
+
final PocketBaseClientFactory? clientFactory;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/// Parse a `pb://bundles/{recordId}/{filename}` URI back into its parts.
|
|
37
|
+
({String recordId, String filename, String? bucket})? parsePocketBaseStorageUri(
|
|
38
|
+
String storageUri,
|
|
39
|
+
) {
|
|
40
|
+
// Accepts both `pb://{bucket}/{id}/{file}` and `pb:///{id}/{file}` (no
|
|
41
|
+
// bucket). The leading double-slash with an optional bucket prefix is
|
|
42
|
+
// matched explicitly.
|
|
43
|
+
final match = RegExp(r'^pb://(?:([^/]*)/)?([^/]+)/(.+)$')
|
|
44
|
+
.firstMatch(storageUri);
|
|
45
|
+
if (match == null) return null;
|
|
46
|
+
final bucket = match.group(1);
|
|
47
|
+
return (
|
|
48
|
+
recordId: match.group(2)!,
|
|
49
|
+
filename: match.group(3)!,
|
|
50
|
+
bucket: (bucket == null || bucket.isEmpty) ? null : bucket,
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/// Stringify a PocketBase file storage URI.
|
|
55
|
+
String buildPocketBaseStorageUri(
|
|
56
|
+
String recordId,
|
|
57
|
+
String filename, [
|
|
58
|
+
String? bucket,
|
|
59
|
+
]) => 'pb://${bucket ?? ''}/${recordId}/$filename';
|
|
60
|
+
|
|
61
|
+
/// PocketBase-backed [StoragePlugin].
|
|
62
|
+
class _PocketBaseStorage implements StoragePlugin {
|
|
63
|
+
_PocketBaseStorage(this.config, this.client);
|
|
64
|
+
|
|
65
|
+
final PocketBaseStorageConfig config;
|
|
66
|
+
final PocketBaseClient client;
|
|
67
|
+
|
|
68
|
+
@override
|
|
69
|
+
String get name => 'pocketbaseStorage';
|
|
70
|
+
|
|
71
|
+
@override
|
|
72
|
+
String get supportedProtocol => 'pb:';
|
|
73
|
+
|
|
74
|
+
@override
|
|
75
|
+
StoragePluginProfiles get profiles => StoragePluginProfiles(
|
|
76
|
+
node: _PocketBaseNodeStorage(this),
|
|
77
|
+
runtime: _PocketBaseRuntimeStorage(this),
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/// Runtime profile: URL-oriented operations used by the device SDK
|
|
82
|
+
/// (resolve a signed download URL for a `pb://...` storage URI).
|
|
83
|
+
class _PocketBaseRuntimeStorage implements RuntimeStorageProfile {
|
|
84
|
+
_PocketBaseRuntimeStorage(this.parent);
|
|
85
|
+
|
|
86
|
+
final _PocketBaseStorage parent;
|
|
87
|
+
|
|
88
|
+
PocketBaseClient get _client => parent.client;
|
|
89
|
+
PocketBaseStorageConfig get _config => parent.config;
|
|
90
|
+
|
|
91
|
+
@override
|
|
92
|
+
Future<Map<String, String>> getDownloadUrl(String storageUri) async {
|
|
93
|
+
final parsed = parsePocketBaseStorageUri(storageUri);
|
|
94
|
+
if (parsed == null) {
|
|
95
|
+
throw FormatException('Not a pb:// URI: $storageUri');
|
|
96
|
+
}
|
|
97
|
+
// PocketBase file downloads require a short-lived token when the
|
|
98
|
+
// collection's file rules restrict public reads. The server can mint one
|
|
99
|
+
// via `POST /api/files/token` with admin auth.
|
|
100
|
+
final token = await _client.getFileToken(parsed.recordId, parsed.filename);
|
|
101
|
+
final url = _client.fileUrl(
|
|
102
|
+
_config.bundlesCollection,
|
|
103
|
+
parsed.recordId,
|
|
104
|
+
parsed.filename,
|
|
105
|
+
token.isEmpty ? null : token,
|
|
106
|
+
);
|
|
107
|
+
return {'fileUrl': url, 'storageUri': storageUri};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
@override
|
|
111
|
+
Future<String?> readText(String storageUri) async {
|
|
112
|
+
// PB serves binary files only; text reads aren't supported.
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/// Node profile: filesystem-aware operations.
|
|
118
|
+
class _PocketBaseNodeStorage implements NodeStorageProfile {
|
|
119
|
+
_PocketBaseNodeStorage(this.parent);
|
|
120
|
+
|
|
121
|
+
final _PocketBaseStorage parent;
|
|
122
|
+
|
|
123
|
+
PocketBaseClient get _client => parent.client;
|
|
124
|
+
PocketBaseStorageConfig get _config => parent.config;
|
|
125
|
+
|
|
126
|
+
String _keyToFilename(String key) =>
|
|
127
|
+
key.replaceAll('/', '_').replaceAll('\\', '_');
|
|
128
|
+
|
|
129
|
+
String _baseKey() {
|
|
130
|
+
final bp = _config.basePath;
|
|
131
|
+
return (bp == null || bp.isEmpty) ? '' : '$bp/';
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
String _fullKey(String key) => '${_baseKey()}$key';
|
|
135
|
+
|
|
136
|
+
@override
|
|
137
|
+
Future<Map<String, String>> upload(String key, String filePath) async {
|
|
138
|
+
final file = File(filePath);
|
|
139
|
+
if (!await file.exists()) {
|
|
140
|
+
throw FileSystemException('Artifact not found', filePath);
|
|
141
|
+
}
|
|
142
|
+
final bytes = await file.readAsBytes();
|
|
143
|
+
final filename = _keyToFilename(_fullKey(key));
|
|
144
|
+
// PocketBase file storage attaches files to records; use the storage key
|
|
145
|
+
// itself as the record id so callers can pass the bundle id (which the
|
|
146
|
+
// CLI already does) and find the file back via `exists()`.
|
|
147
|
+
final recordId = key;
|
|
148
|
+
final existing = await _client.recordExists(
|
|
149
|
+
_config.bundlesCollection,
|
|
150
|
+
recordId,
|
|
151
|
+
);
|
|
152
|
+
if (!existing) {
|
|
153
|
+
await _client.createRecord<dynamic>(_config.bundlesCollection, {
|
|
154
|
+
'id': recordId,
|
|
155
|
+
'channel': 'pending',
|
|
156
|
+
'platform': 'android',
|
|
157
|
+
'enabled': false,
|
|
158
|
+
'should_force_update': false,
|
|
159
|
+
'file_hash': '',
|
|
160
|
+
'storage_uri': '',
|
|
161
|
+
'rollout_cohort_count': 1000,
|
|
162
|
+
}, (j) => j);
|
|
163
|
+
}
|
|
164
|
+
await _client.uploadFile<dynamic>(
|
|
165
|
+
_config.bundlesCollection,
|
|
166
|
+
recordId,
|
|
167
|
+
'artifact',
|
|
168
|
+
filename,
|
|
169
|
+
bytes,
|
|
170
|
+
fromJson: (j) => j,
|
|
171
|
+
);
|
|
172
|
+
final uri = buildPocketBaseStorageUri(recordId, filename);
|
|
173
|
+
return {'key': _fullKey(key), 'storageUri': uri};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
@override
|
|
177
|
+
Future<bool> exists(String storageUri) async {
|
|
178
|
+
final parsed = parsePocketBaseStorageUri(storageUri);
|
|
179
|
+
if (parsed == null) return false;
|
|
180
|
+
return _client.recordExists(_config.bundlesCollection, parsed.recordId);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
@override
|
|
184
|
+
Future<void> delete(String storageUri) async {
|
|
185
|
+
final parsed = parsePocketBaseStorageUri(storageUri);
|
|
186
|
+
if (parsed == null) return;
|
|
187
|
+
await _client.deleteRecord(_config.bundlesCollection, parsed.recordId);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
@override
|
|
191
|
+
Future<void> downloadFile(String storageUri, String filePath) async {
|
|
192
|
+
final parsed = parsePocketBaseStorageUri(storageUri);
|
|
193
|
+
if (parsed == null) {
|
|
194
|
+
throw FormatException('Not a pb:// URI: $storageUri');
|
|
195
|
+
}
|
|
196
|
+
final token = await _client.getFileToken(parsed.recordId, parsed.filename);
|
|
197
|
+
final url = _client.fileUrl(
|
|
198
|
+
_config.bundlesCollection,
|
|
199
|
+
parsed.recordId,
|
|
200
|
+
parsed.filename,
|
|
201
|
+
token.isEmpty ? null : token,
|
|
202
|
+
);
|
|
203
|
+
final bytes = await _client.downloadFile(url);
|
|
204
|
+
final out = File(filePath);
|
|
205
|
+
await out.parent.create(recursive: true);
|
|
206
|
+
await out.writeAsBytes(bytes);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
@override
|
|
210
|
+
Future<List<StorageObject>> listObjects([String? prefix]) async {
|
|
211
|
+
final res = await _client.listRecords<dynamic>(
|
|
212
|
+
_config.bundlesCollection,
|
|
213
|
+
(j) => j,
|
|
214
|
+
perPage: 500,
|
|
215
|
+
);
|
|
216
|
+
final all = res.items.cast<Map>();
|
|
217
|
+
return all
|
|
218
|
+
.map((j) {
|
|
219
|
+
final id = j['id']?.toString() ?? '';
|
|
220
|
+
final filename = j['artifact']?.toString() ?? '';
|
|
221
|
+
if (filename.isEmpty) return null;
|
|
222
|
+
final key = filename.replaceFirst(_baseKey(), '');
|
|
223
|
+
if (prefix != null && prefix.isNotEmpty && !key.startsWith(prefix)) {
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
return StorageObject(
|
|
227
|
+
key: key,
|
|
228
|
+
storageUri: buildPocketBaseStorageUri(id, filename),
|
|
229
|
+
size: 0,
|
|
230
|
+
);
|
|
231
|
+
})
|
|
232
|
+
.whereType<StorageObject>()
|
|
233
|
+
.toList();
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
@override
|
|
237
|
+
Future<void> deleteObjects(List<String> keys) async {
|
|
238
|
+
for (final key in keys) {
|
|
239
|
+
await _client.deleteRecord(_config.bundlesCollection, key);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/// Build a `pocketbaseStorage` plugin.
|
|
245
|
+
StoragePlugin pocketbaseStorage(PocketBaseStorageConfig config) {
|
|
246
|
+
final client = config.clientFactory != null
|
|
247
|
+
? config.clientFactory!(
|
|
248
|
+
config.url,
|
|
249
|
+
config.adminEmail,
|
|
250
|
+
config.adminPassword,
|
|
251
|
+
)
|
|
252
|
+
: PocketBaseClient(config.url);
|
|
253
|
+
client.adminCredentials(config.adminEmail, config.adminPassword);
|
|
254
|
+
return _PocketBaseStorage(config, client);
|
|
255
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
name: flutter_ota_kit_pocketbase
|
|
2
|
+
description: PocketBase plugin for flutter_ota_kit — local-first self-hostable backend with admin UI, auth, file storage, and realtime.
|
|
3
|
+
version: 0.1.0
|
|
4
|
+
homepage: https://github.com/HYPER12755/flutter-ota-kit
|
|
5
|
+
repository: https://github.com/HYPER12755/flutter-ota-kit
|
|
6
|
+
issue_tracker: https://github.com/HYPER12755/flutter-ota-kit/issues
|
|
7
|
+
documentation: https://github.com/HYPER12755/flutter-ota-kit#readme
|
|
8
|
+
license: MIT
|
|
9
|
+
|
|
10
|
+
environment:
|
|
11
|
+
sdk: ">=3.13.2 <4.0.0"
|
|
12
|
+
|
|
13
|
+
dependencies:
|
|
14
|
+
flutter_ota_kit_core: ^0.1.1
|
|
15
|
+
flutter_ota_kit_plugin_core: ^0.0.3
|
|
16
|
+
http: ^1.2.0
|
|
17
|
+
|
|
18
|
+
dev_dependencies:
|
|
19
|
+
test: ^1.25.0
|
|
20
|
+
lints: ^4.0.0
|
|
21
|
+
|
|
22
|
+
dependency_overrides:
|
|
23
|
+
flutter_ota_kit_core:
|
|
24
|
+
path: ../../packages/core
|
|
25
|
+
flutter_ota_kit_plugin_core:
|
|
26
|
+
path: ../plugin-core
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import 'package:flutter_ota_kit_pocketbase/flutter_ota_kit_pocketbase.dart';
|
|
2
|
+
import 'package:test/test.dart';
|
|
3
|
+
|
|
4
|
+
void main() {
|
|
5
|
+
test('buildPocketBaseStorageUri with no bucket parses back', () {
|
|
6
|
+
final uri = buildPocketBaseStorageUri('abc', 'file.zip');
|
|
7
|
+
final parsed = parsePocketBaseStorageUri(uri);
|
|
8
|
+
expect(parsed, isNotNull);
|
|
9
|
+
expect(parsed!.recordId, 'abc');
|
|
10
|
+
expect(parsed.filename, 'file.zip');
|
|
11
|
+
});
|
|
12
|
+
}
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
## 0.1.1
|
|
2
|
+
|
|
3
|
+
- Add `PostgresBundleRow` + `mapRowToBundle` exports for writing custom
|
|
4
|
+
server tooling on top of `flutter_ota_kit`.
|
|
5
|
+
- Repository moved to `https://github.com/HYPER12755/flutter-ota-kit`.
|
|
6
|
+
|
|
1
7
|
## 0.1.0
|
|
2
8
|
|
|
3
9
|
- Initial release of `flutter_ota_kit_postgres` as part of the flutter_ota_kit OTA toolkit.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// flutter_ota_kit_postgres example
|
|
2
|
+
//
|
|
3
|
+
// `flutter_ota_kit_postgres` is a Postgres backend for `flutter_ota_kit`.
|
|
4
|
+
// It provides a `postgresDatabase` factory and a `postgresStorage`
|
|
5
|
+
// factory that talk to a vanilla Postgres database (no Supabase, no
|
|
6
|
+
// Cloudflare — just `pg`).
|
|
7
|
+
//
|
|
8
|
+
// Most apps don't import this package directly; they wire it through
|
|
9
|
+
// `flutter_ota_kit.configurePostgres(...)` in their app boot path. This
|
|
10
|
+
// file shows the low-level config types in case you're writing a custom
|
|
11
|
+
// server, a migration script, or a CLI extension.
|
|
12
|
+
//
|
|
13
|
+
// Run: `dart run example/main.dart`
|
|
14
|
+
|
|
15
|
+
import 'package:flutter_ota_kit_postgres/flutter_ota_kit_postgres.dart';
|
|
16
|
+
import 'package:flutter_ota_kit_core/flutter_ota_kit_core.dart';
|
|
17
|
+
|
|
18
|
+
void main() {
|
|
19
|
+
// ── 1. Database config (vanilla Postgres connection). ─────────────
|
|
20
|
+
// `PostgresConfig` is a thin wrapper around the `postgres` package
|
|
21
|
+
// connection parameters. The `host`/`port`/`database` triple is the
|
|
22
|
+
// canonical form; username/password are required for non-trust auth.
|
|
23
|
+
const dbConfig = PostgresConfig(
|
|
24
|
+
host: 'db.example.com',
|
|
25
|
+
port: 5432,
|
|
26
|
+
database: 'ota',
|
|
27
|
+
username: 'ota',
|
|
28
|
+
password: 'secret',
|
|
29
|
+
);
|
|
30
|
+
print('Postgres: ${dbConfig.host}:${dbConfig.port}/${dbConfig.database}');
|
|
31
|
+
|
|
32
|
+
// ── 2. Storage config (for storing patch blobs). ──────────────────
|
|
33
|
+
// The `servingBaseUrl` is the public-facing URL that the device SDK
|
|
34
|
+
// can use to download patches. Omit it only if you proxy the
|
|
35
|
+
// download through your own update-check endpoint.
|
|
36
|
+
const storageConfig = PostgresStorageConfig(
|
|
37
|
+
db: dbConfig,
|
|
38
|
+
servingBaseUrl: 'https://cdn.example.com/flutter-ota',
|
|
39
|
+
);
|
|
40
|
+
print('Serving base URL: ${storageConfig.servingBaseUrl}');
|
|
41
|
+
|
|
42
|
+
// ── 3. Row-to-Bundle mapping (server-side helper). ────────────────
|
|
43
|
+
// Use this when reading bundles from a custom query and passing them
|
|
44
|
+
// to the SDK's `PatchInfo.fromJson`. The mapper handles both
|
|
45
|
+
// camelCase and snake_case column names.
|
|
46
|
+
const row = PostgresBundleRow(
|
|
47
|
+
id: '01a059a6-7d3c-4f3e-8a2c-9c2e3a4b5d6e',
|
|
48
|
+
channel: 'production',
|
|
49
|
+
enabled: true,
|
|
50
|
+
shouldForceUpdate: false,
|
|
51
|
+
fileHash: '7c4a8d09ca3762af61e59520943dc26494f8941b',
|
|
52
|
+
platform: Platform.android,
|
|
53
|
+
storageUri:
|
|
54
|
+
'postgres-blob://flutter-ota-bundles/'
|
|
55
|
+
'01a059a6/patch.zip',
|
|
56
|
+
targetAppVersion: '1.0.0',
|
|
57
|
+
rolloutCohortCount: 100,
|
|
58
|
+
);
|
|
59
|
+
final bundle = mapRowToBundle(row);
|
|
60
|
+
print('Mapped bundle: ${bundle.id} (${bundle.storageUri})');
|
|
61
|
+
|
|
62
|
+
// ── 4. Bundle → SQL values (for inserts/updates). ────────────────
|
|
63
|
+
// Returns snake_case column→value maps ready to feed to a `pg` query.
|
|
64
|
+
final values = bundleToRowValues(bundle);
|
|
65
|
+
print('SQL column count: ${values.length}');
|
|
66
|
+
print(' file_hash = ${values['file_hash']}');
|
|
67
|
+
}
|
|
@@ -3,15 +3,12 @@
|
|
|
3
3
|
/// Faithful port of hot-updater `plugins/postgres`.
|
|
4
4
|
library;
|
|
5
5
|
|
|
6
|
-
export 'src/postgres_config.dart'
|
|
7
|
-
|
|
8
|
-
export 'src/postgres_client.dart'
|
|
9
|
-
show PostgresClientLike, PostgresClient;
|
|
6
|
+
export 'src/postgres_config.dart' show PostgresConfig, PostgresClientFactory;
|
|
7
|
+
export 'src/postgres_client.dart' show PostgresClientLike, PostgresClient;
|
|
10
8
|
export 'src/postgres_database.dart' show postgresDatabase;
|
|
11
9
|
export 'src/postgres_storage.dart'
|
|
12
10
|
show postgresStorage, PostgresStorageConfig, resolvePostgresStorageClient;
|
|
13
11
|
export 'src/postgres_get_update_info.dart' show getUpdateInfo;
|
|
14
|
-
export 'src/postgres_types.dart'
|
|
15
|
-
show PostgresBundleRow, PostgresBundlePatchRow;
|
|
12
|
+
export 'src/postgres_types.dart' show PostgresBundleRow, PostgresBundlePatchRow;
|
|
16
13
|
export 'src/postgres_bundle_mapper.dart'
|
|
17
14
|
show mapRowToBundle, bundleToRowValues, bundleToPatchRows;
|
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
/// (the `mapRowToBundle` / `bundleToRowValues` / `bundleToPatchRows` helpers).
|
|
3
3
|
library;
|
|
4
4
|
|
|
5
|
-
import 'dart:convert';
|
|
6
|
-
|
|
7
5
|
import 'package:flutter_ota_kit_core/flutter_ota_kit_core.dart'
|
|
8
6
|
show
|
|
9
7
|
Bundle,
|
|
@@ -16,36 +14,17 @@ import 'package:flutter_ota_kit_core/flutter_ota_kit_core.dart'
|
|
|
16
14
|
getManifestStorageUri,
|
|
17
15
|
stripBundleArtifactMetadata;
|
|
18
16
|
|
|
19
|
-
import '
|
|
20
|
-
|
|
21
|
-
/// Normalize metadata from various shapes to a plain map.
|
|
22
|
-
Map<String, Object?>? _normalizeMetadata(Object? value) {
|
|
23
|
-
if (value == null) return null;
|
|
24
|
-
|
|
25
|
-
if (value is String) {
|
|
26
|
-
try {
|
|
27
|
-
final parsed = jsonDecode(value);
|
|
28
|
-
return _normalizeMetadata(parsed);
|
|
29
|
-
} catch (_) {
|
|
30
|
-
return null;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
17
|
+
import 'package:flutter_ota_kit_plugin_core/flutter_ota_kit_plugin_core.dart'
|
|
18
|
+
show buildBundlePatchId, normalizeMetadata;
|
|
33
19
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
return null;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/// Build the compound primary key for a patch row.
|
|
40
|
-
String _buildBundlePatchId(String bundleId, String baseBundleId) =>
|
|
41
|
-
'$bundleId:$baseBundleId';
|
|
20
|
+
import 'postgres_types.dart';
|
|
42
21
|
|
|
43
22
|
/// Map a Postgres bundle row + optional patch rows to a [Bundle].
|
|
44
23
|
Bundle mapRowToBundle(
|
|
45
24
|
PostgresBundleRow data, [
|
|
46
25
|
List<PostgresBundlePatchRow> patchRows = const [],
|
|
47
26
|
]) {
|
|
48
|
-
final rawMetadata =
|
|
27
|
+
final rawMetadata = normalizeMetadata(data.metadata);
|
|
49
28
|
final patches = patchRows.toList()
|
|
50
29
|
..sort((a, b) {
|
|
51
30
|
final cmp = a.orderIndex.compareTo(b.orderIndex);
|
|
@@ -54,12 +33,14 @@ Bundle mapRowToBundle(
|
|
|
54
33
|
});
|
|
55
34
|
|
|
56
35
|
final patchArtifacts = patches
|
|
57
|
-
.map(
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
36
|
+
.map(
|
|
37
|
+
(p) => BundlePatchArtifact(
|
|
38
|
+
baseBundleId: p.baseBundleId,
|
|
39
|
+
baseFileHash: p.baseFileHash,
|
|
40
|
+
patchFileHash: p.patchFileHash,
|
|
41
|
+
patchStorageUri: p.patchStorageUri,
|
|
42
|
+
),
|
|
43
|
+
)
|
|
63
44
|
.toList();
|
|
64
45
|
|
|
65
46
|
final primaryPatch = patchArtifacts.isNotEmpty ? patchArtifacts.first : null;
|
|
@@ -76,8 +57,7 @@ Bundle mapRowToBundle(
|
|
|
76
57
|
targetAppVersion: data.targetAppVersion,
|
|
77
58
|
fingerprintHash: data.fingerprintHash,
|
|
78
59
|
storageUri: data.storageUri,
|
|
79
|
-
metadata:
|
|
80
|
-
rawMetadata != null ? BundleMetadata.fromJson(rawMetadata) : null,
|
|
60
|
+
metadata: rawMetadata != null ? BundleMetadata.fromJson(rawMetadata) : null,
|
|
81
61
|
manifestStorageUri: data.manifestStorageUri,
|
|
82
62
|
manifestFileHash: data.manifestFileHash,
|
|
83
63
|
assetBaseStorageUri: data.assetBaseStorageUri,
|
|
@@ -93,24 +73,24 @@ Bundle mapRowToBundle(
|
|
|
93
73
|
|
|
94
74
|
/// Map a [Bundle] to a `bundles` row for insert/update (snake_case columns).
|
|
95
75
|
Map<String, dynamic> bundleToRowValues(Bundle bundle) => {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
76
|
+
'id': bundle.id,
|
|
77
|
+
'enabled': bundle.enabled,
|
|
78
|
+
'should_force_update': bundle.shouldForceUpdate,
|
|
79
|
+
'file_hash': bundle.fileHash,
|
|
80
|
+
'git_commit_hash': bundle.gitCommitHash,
|
|
81
|
+
'message': bundle.message,
|
|
82
|
+
'platform': bundle.platform.value,
|
|
83
|
+
'target_app_version': bundle.targetAppVersion,
|
|
84
|
+
'channel': bundle.channel,
|
|
85
|
+
'storage_uri': bundle.storageUri,
|
|
86
|
+
'fingerprint_hash': bundle.fingerprintHash,
|
|
87
|
+
'metadata': stripBundleArtifactMetadata(bundle.metadata?.toJson()) ?? {},
|
|
88
|
+
'manifest_storage_uri': getManifestStorageUri(bundle),
|
|
89
|
+
'manifest_file_hash': getManifestFileHash(bundle),
|
|
90
|
+
'asset_base_storage_uri': getAssetBaseStorageUri(bundle),
|
|
91
|
+
'rollout_cohort_count': bundle.rolloutCohortCount,
|
|
92
|
+
'target_cohorts': bundle.targetCohorts,
|
|
93
|
+
};
|
|
114
94
|
|
|
115
95
|
/// Map a [Bundle] to its `bundle_patches` rows for insert.
|
|
116
96
|
List<Map<String, dynamic>> bundleToPatchRows(Bundle bundle) {
|
|
@@ -118,7 +98,7 @@ List<Map<String, dynamic>> bundleToPatchRows(Bundle bundle) {
|
|
|
118
98
|
return [
|
|
119
99
|
for (var i = 0; i < patchArtifacts.length; i++)
|
|
120
100
|
PostgresBundlePatchRow(
|
|
121
|
-
id:
|
|
101
|
+
id: buildBundlePatchId(bundle.id, patchArtifacts[i].baseBundleId),
|
|
122
102
|
bundleId: bundle.id,
|
|
123
103
|
baseBundleId: patchArtifacts[i].baseBundleId,
|
|
124
104
|
baseFileHash: patchArtifacts[i].baseFileHash,
|
|
@@ -32,4 +32,6 @@ class PostgresConfig {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
/// Builds a [PostgresClientLike] from a [PostgresConfig].
|
|
35
|
-
typedef PostgresClientFactory = PostgresClientLike Function(
|
|
35
|
+
typedef PostgresClientFactory = PostgresClientLike Function(
|
|
36
|
+
PostgresConfig config,
|
|
37
|
+
);
|
|
@@ -33,7 +33,8 @@ import 'postgres_types.dart';
|
|
|
33
33
|
DatabasePlugin Function() Function(
|
|
34
34
|
PostgresConfig config, [
|
|
35
35
|
DatabasePluginHooks? hooks,
|
|
36
|
-
])
|
|
36
|
+
])
|
|
37
|
+
postgresDatabase = createDatabasePlugin<PostgresConfig>(
|
|
37
38
|
name: 'postgresDatabase',
|
|
38
39
|
factory: (config) {
|
|
39
40
|
final client = config.clientFactory != null
|
|
@@ -95,18 +96,10 @@ DatabasePlugin Function() Function(
|
|
|
95
96
|
add('enabled = ?', where.enabled);
|
|
96
97
|
}
|
|
97
98
|
if (where.fingerprintHash != null) {
|
|
98
|
-
|
|
99
|
-
conditions.add('fingerprint_hash IS NULL');
|
|
100
|
-
} else {
|
|
101
|
-
add('fingerprint_hash = ?', where.fingerprintHash);
|
|
102
|
-
}
|
|
99
|
+
add('fingerprint_hash = ?', where.fingerprintHash);
|
|
103
100
|
}
|
|
104
101
|
if (where.targetAppVersion != null) {
|
|
105
|
-
|
|
106
|
-
conditions.add('target_app_version IS NULL');
|
|
107
|
-
} else {
|
|
108
|
-
add('target_app_version = ?', where.targetAppVersion);
|
|
109
|
-
}
|
|
102
|
+
add('target_app_version = ?', where.targetAppVersion);
|
|
110
103
|
}
|
|
111
104
|
if (where.targetAppVersionIn != null) {
|
|
112
105
|
add('target_app_version = ANY(?)', where.targetAppVersionIn);
|
|
@@ -134,12 +127,10 @@ class _PostgresDatabasePlugin implements AbstractDatabasePlugin {
|
|
|
134
127
|
final PostgresClientLike client;
|
|
135
128
|
final Future<Map<String, List<PostgresBundlePatchRow>>> Function(
|
|
136
129
|
List<String> bundleIds,
|
|
137
|
-
)
|
|
130
|
+
)
|
|
131
|
+
fetchPatchMap;
|
|
138
132
|
|
|
139
|
-
_PostgresDatabasePlugin({
|
|
140
|
-
required this.client,
|
|
141
|
-
required this.fetchPatchMap,
|
|
142
|
-
});
|
|
133
|
+
_PostgresDatabasePlugin({required this.client, required this.fetchPatchMap});
|
|
143
134
|
|
|
144
135
|
@override
|
|
145
136
|
bool get supportsCursorPagination => false;
|
|
@@ -151,17 +142,16 @@ class _PostgresDatabasePlugin implements AbstractDatabasePlugin {
|
|
|
151
142
|
@override
|
|
152
143
|
Future<Bundle?> getBundleById(String bundleId) async {
|
|
153
144
|
final results = await Future.wait([
|
|
154
|
-
client.execute(
|
|
155
|
-
'SELECT * FROM bundles WHERE id = @p0',
|
|
156
|
-
{'p0': bundleId},
|
|
157
|
-
),
|
|
145
|
+
client.execute('SELECT * FROM bundles WHERE id = @p0', {'p0': bundleId}),
|
|
158
146
|
fetchPatchMap([bundleId]),
|
|
159
147
|
]);
|
|
160
148
|
|
|
161
149
|
final data = results[0] as List<Map<String, dynamic>>;
|
|
162
150
|
if (data.isEmpty) return null;
|
|
163
151
|
final row = PostgresBundleRow.fromJson(data.first);
|
|
164
|
-
final patches =
|
|
152
|
+
final patches =
|
|
153
|
+
(results[1] as Map<String, List<PostgresBundlePatchRow>>)[bundleId] ??
|
|
154
|
+
[];
|
|
165
155
|
return mapRowToBundle(row, patches);
|
|
166
156
|
}
|
|
167
157
|
|
|
@@ -174,9 +164,11 @@ class _PostgresDatabasePlugin implements AbstractDatabasePlugin {
|
|
|
174
164
|
final orderBy =
|
|
175
165
|
options.orderBy ?? const DatabaseBundleQueryOrder(direction: 'desc');
|
|
176
166
|
final offset =
|
|
177
|
-
options.offset ??
|
|
167
|
+
options.offset ??
|
|
168
|
+
(options.page != null ? (options.page! - 1) * limit : 0);
|
|
178
169
|
|
|
179
|
-
if ((where?.targetAppVersionIn != null &&
|
|
170
|
+
if ((where?.targetAppVersionIn != null &&
|
|
171
|
+
where!.targetAppVersionIn!.isEmpty) ||
|
|
180
172
|
(where?.id?.ins != null && where!.id!.ins!.isEmpty)) {
|
|
181
173
|
return Paginated(
|
|
182
174
|
data: const [],
|
|
@@ -230,18 +222,15 @@ class _PostgresDatabasePlugin implements AbstractDatabasePlugin {
|
|
|
230
222
|
}
|
|
231
223
|
|
|
232
224
|
@override
|
|
233
|
-
Future<void> commitBundle({
|
|
234
|
-
required List<BundleChange> changedSets,
|
|
235
|
-
}) async {
|
|
225
|
+
Future<void> commitBundle({required List<BundleChange> changedSets}) async {
|
|
236
226
|
if (changedSets.isEmpty) return;
|
|
237
227
|
|
|
238
228
|
await client.runTx((tx) async {
|
|
239
229
|
for (final op in changedSets) {
|
|
240
230
|
if (op.operation == BundleChangeOperation.delete) {
|
|
241
|
-
await tx.execute(
|
|
242
|
-
'
|
|
243
|
-
|
|
244
|
-
);
|
|
231
|
+
await tx.execute('DELETE FROM bundle_patches WHERE bundle_id = @p0', {
|
|
232
|
+
'p0': op.data.id,
|
|
233
|
+
});
|
|
245
234
|
await tx.execute(
|
|
246
235
|
'DELETE FROM bundle_patches WHERE base_bundle_id = @p0',
|
|
247
236
|
{'p0': op.data.id},
|
|
@@ -258,8 +247,9 @@ class _PostgresDatabasePlugin implements AbstractDatabasePlugin {
|
|
|
258
247
|
final values = bundleToRowValues(bundle);
|
|
259
248
|
final columns = values.keys.toList();
|
|
260
249
|
final insertCols = columns.join(', ');
|
|
261
|
-
final placeholders =
|
|
262
|
-
|
|
250
|
+
final placeholders = columns
|
|
251
|
+
.map((c) => '@${c.replaceAll('.', '_')}')
|
|
252
|
+
.join(', ');
|
|
263
253
|
final updateSets = columns
|
|
264
254
|
.where((c) => c != 'id')
|
|
265
255
|
.map((c) => '$c = EXCLUDED.$c')
|
|
@@ -270,17 +260,17 @@ class _PostgresDatabasePlugin implements AbstractDatabasePlugin {
|
|
|
270
260
|
{for (final c in columns) c.replaceAll('.', '_'): values[c]},
|
|
271
261
|
);
|
|
272
262
|
|
|
273
|
-
await tx.execute(
|
|
274
|
-
'
|
|
275
|
-
|
|
276
|
-
);
|
|
263
|
+
await tx.execute('DELETE FROM bundle_patches WHERE bundle_id = @p0', {
|
|
264
|
+
'p0': bundle.id,
|
|
265
|
+
});
|
|
277
266
|
final patchRows = bundleToPatchRows(bundle);
|
|
278
267
|
if (patchRows.isNotEmpty) {
|
|
279
268
|
for (final pr in patchRows) {
|
|
280
269
|
final patchCols = pr.keys.toList();
|
|
281
270
|
final patchInsert = patchCols.join(', ');
|
|
282
|
-
final patchPlaceholders =
|
|
283
|
-
|
|
271
|
+
final patchPlaceholders = patchCols
|
|
272
|
+
.map((c) => '@${c.replaceAll('.', '_')}')
|
|
273
|
+
.join(', ');
|
|
284
274
|
await tx.execute(
|
|
285
275
|
'INSERT INTO bundle_patches ($patchInsert) '
|
|
286
276
|
'VALUES ($patchPlaceholders) '
|