diff --git a/packages/dart/example/main.dart b/packages/dart/example/main.dart index ce96602d..6a4260be 100644 --- a/packages/dart/example/main.dart +++ b/packages/dart/example/main.dart @@ -14,10 +14,22 @@ Future main() async { ..set('Name', 'Ketogenic') ..set('Fat', 65); + + var response = await dietPlan.save(); if (response.success) { dietPlan = response.results?.first; print("Response received successfully"); } + + final res = await ParseAggregate('DietPlan', pipeline: { + r'$match': {'Name': 'Ketogenic'}, + r'$group': { + '_id': r'isHungry', + 'count': {r'$sum': 1} + }, + }).execute(); + + print(res.result); } diff --git a/packages/dart/lib/parse_server_sdk.dart b/packages/dart/lib/parse_server_sdk.dart index 3b27e056..dbc09ef7 100644 --- a/packages/dart/lib/parse_server_sdk.dart +++ b/packages/dart/lib/parse_server_sdk.dart @@ -29,111 +29,59 @@ export 'src/network/parse_dio_client.dart'; export 'src/network/parse_http_client.dart'; part 'src/base/parse_constants.dart'; - part 'src/data/parse_core_data.dart'; - part 'src/data/parse_subclass_handler.dart'; - part 'src/enums/parse_enum_api_rq.dart'; - part 'src/network/options.dart'; - +part 'src/network/parse_aggregate.dart'; part 'src/network/parse_client.dart'; - part 'src/network/parse_connectivity.dart'; - part 'src/network/parse_live_query.dart'; - part 'src/network/parse_query.dart'; - part 'src/objects/parse_acl.dart'; - part 'src/objects/parse_array.dart'; - part 'src/objects/parse_base.dart'; - part 'src/objects/parse_cloneable.dart'; - part 'src/objects/parse_config.dart'; - part 'src/objects/parse_error.dart'; - part 'src/objects/parse_exception.dart'; - part 'src/objects/parse_file.dart'; - part 'src/objects/parse_file_base.dart'; - part 'src/objects/parse_file_web.dart'; - part 'src/objects/parse_function.dart'; - part 'src/objects/parse_geo_point.dart'; - part 'src/objects/parse_installation.dart'; - part 'src/objects/parse_number.dart'; - part 'src/objects/parse_object.dart'; - part 'src/objects/parse_operation/parse_add_operation.dart'; - part 'src/objects/parse_operation/parse_add_relation_operation.dart'; - part 'src/objects/parse_operation/parse_add_unique_operation.dart'; - part 'src/objects/parse_operation/parse_increment_operation.dart'; - part 'src/objects/parse_operation/parse_operation.dart'; - part 'src/objects/parse_operation/parse_remove_operation.dart'; - part 'src/objects/parse_operation/parse_remove_relation_operation.dart'; - part 'src/objects/parse_relation.dart'; - part 'src/objects/parse_response.dart'; - part 'src/objects/parse_save_state_aware_child.dart'; - part 'src/objects/parse_session.dart'; - part 'src/objects/parse_user.dart'; - part 'src/objects/parse_x_file.dart'; - part 'src/objects/response/parse_error_response.dart'; - part 'src/objects/response/parse_exception_response.dart'; - part 'src/objects/response/parse_response_builder.dart'; - part 'src/objects/response/parse_response_utils.dart'; - part 'src/objects/response/parse_success_no_results.dart'; - part 'src/storage/core_store.dart'; - part 'src/storage/core_store_memory.dart'; - part 'src/storage/core_store_sem_impl.dart'; - part 'src/storage/xxtea_codec.dart'; - part 'src/utils/parse_date_format.dart'; - part 'src/utils/parse_decoder.dart'; - part 'src/utils/parse_encoder.dart'; - part 'src/utils/parse_live_list.dart'; - part 'src/utils/parse_logger.dart'; - part 'src/utils/parse_login_helpers.dart'; - part 'src/utils/parse_utils.dart'; - part 'src/utils/valuable.dart'; class Parse { diff --git a/packages/dart/lib/src/network/parse_aggregate.dart b/packages/dart/lib/src/network/parse_aggregate.dart new file mode 100644 index 00000000..d9532c2c --- /dev/null +++ b/packages/dart/lib/src/network/parse_aggregate.dart @@ -0,0 +1,92 @@ +part of '../../parse_server_sdk.dart'; + +/// A class that allows aggregation queries on a Parse Server class using a pipeline. +/// +/// Example usage: +/// ```dart +/// final aggregate = ParseAggregate('GameScore', pipeline: { +/// '\$group': { +/// '_id': '\$userId', +/// 'totalScore': {'\$sum': '\$score'} +/// } +/// }); +/// final response = await aggregate.execute(); +/// ``` +class ParseAggregate { + /// The name of the Parse class to perform the aggregation on. + final String className; + + /// The aggregation pipeline operations. + /// + /// Each operation should follow MongoDB-like syntax. + /// Example: + /// ```dart + /// { + /// '\$group': { + /// '_id': '\$userId', + /// 'totalScore': {'\$sum': '\$score'} + /// } + /// } + /// ``` + Map pipeline; + + /// Whether to enable debug mode for this request. + final bool? debug; + + /// The custom ParseClient to use for the request (optional). + final ParseClient? client; + + /// If true, includes the session ID automatically in the request (optional). + final bool? autoSendSessionId; + + /// Optional override for the Parse class name used in response handling. + final String? parseClassName; + + /// Creates a new [ParseAggregate] instance to perform aggregation queries. + /// + /// [className] is required and specifies the target Parse class. + /// [pipeline] must contain at least one aggregation operation. + ParseAggregate( + this.className, { + required this.pipeline, + this.debug, + this.client, + this.autoSendSessionId, + this.parseClassName, + }); + + /// Executes the aggregation query using the configured pipeline. + /// + /// Returns a [ParseResponse] containing the results of the aggregation. + /// Throws [ArgumentError] if the pipeline is empty. + Future execute() async { + Map _pipeline = {}; + + if (pipeline.isEmpty) { + throw ArgumentError( + 'pipeline must not be empty. Please add pipeline operations to aggregate data. ' + 'Example: {"\$group": {"_id": "\$userId", "totalScore": {"\$sum": "\$score"}}}', + ); + } else { + _pipeline.addAll({ + 'pipeline': jsonEncode(pipeline.entries.map((e) => {e.key: e.value}).toList()) + }); + } + + final debugBool = isDebugEnabled(objectLevelDebug: debug); + final result = await ParseObject(className)._client.get( + Uri.parse('${ParseCoreData().serverUrl}$keyEndPointAggregate$className').replace( + queryParameters: {'pipeline': jsonEncode(pipeline.entries.map((e) => {e.key: e.value}).toList())} + ).toString(), + ); + + + return handleResponse( + ParseObject(className), + result, + ParseApiRQ.get, + debugBool, + parseClassName ?? 'ParseBase', + ); + } +} diff --git a/packages/dart/lib/src/network/parse_http_client.dart b/packages/dart/lib/src/network/parse_http_client.dart index 5b1b9795..1732ec68 100644 --- a/packages/dart/lib/src/network/parse_http_client.dart +++ b/packages/dart/lib/src/network/parse_http_client.dart @@ -1,9 +1,8 @@ import 'dart:convert'; -import 'package:universal_io/io.dart'; import 'package:http/http.dart' as http; - import 'package:parse_server_sdk/parse_server_sdk.dart'; +import 'package:universal_io/io.dart'; import 'http_client_io.dart' if (dart.library.js) 'http_client_js.dart'; @@ -31,6 +30,7 @@ class ParseHTTPClient extends ParseClient { ParseNetworkOptions? options, ProgressCallback? onReceiveProgress, }) async { + final http.Response response = await _client.get( Uri.parse(path), headers: options?.headers, diff --git a/packages/dart/lib/src/utils/url_replace.dart b/packages/dart/lib/src/utils/url_replace.dart new file mode 100644 index 00000000..9a0a1e0e --- /dev/null +++ b/packages/dart/lib/src/utils/url_replace.dart @@ -0,0 +1,24 @@ +class UrlReplace{ + String? scheme; + String? userInfo; + String? host; + int? port; + String? path; + Iterable? pathSegments; + String? query; + Map? queryParameters; + String? fragment; + + UrlReplace({ + this.scheme, + this.userInfo, + this.host, + this.port, + this.path, + this.pathSegments, + this.query, + this.queryParameters, + this.fragment, + }); + +} \ No newline at end of file diff --git a/packages/dart/pubspec.yaml b/packages/dart/pubspec.yaml index ae668f69..82424aa4 100644 --- a/packages/dart/pubspec.yaml +++ b/packages/dart/pubspec.yaml @@ -24,7 +24,7 @@ dependencies: # Networking dio: ^5.7.0 http: ^1.2.0 - web_socket_channel: ^2.4.3 + web_socket_channel: ^3.0.3 #Database sembast: ^3.6.0 @@ -34,15 +34,15 @@ dependencies: uuid: ^4.5.1 meta: ^1.16.0 path: ^1.9.0 - mime: ^1.0.0 - timezone: ^0.9.4 + mime: ^2.0.0 + timezone: ^0.10.1 universal_io: ^2.2.2 xxtea: ^2.1.0 collection: ^1.18.0 cross_file: ^0.3.3+8 dev_dependencies: - lints: ^4.0.0 + lints: ^5.1.1 # Testing build_runner: ^2.4.9 diff --git a/packages/flutter/example/.gitignore b/packages/flutter/example/.gitignore index 24476c5d..6c319542 100644 --- a/packages/flutter/example/.gitignore +++ b/packages/flutter/example/.gitignore @@ -5,9 +5,11 @@ *.swp .DS_Store .atom/ +.build/ .buildlog/ .history .svn/ +.swiftpm/ migrate_working_dir/ # IntelliJ related diff --git a/packages/flutter/pubspec.yaml b/packages/flutter/pubspec.yaml index 21f3d0a4..059c2298 100644 --- a/packages/flutter/pubspec.yaml +++ b/packages/flutter/pubspec.yaml @@ -25,7 +25,7 @@ dependencies: flutter: sdk: flutter - parse_server_sdk: ^6.4.0 + parse_server_sdk: ^8.0.0 # Uncomment for local testing #parse_server_sdk: # path: ../dart @@ -40,14 +40,14 @@ dependencies: # Utils path_provider: ^2.1.4 - package_info_plus: ^5.0.1 + package_info_plus: ^8.3.0 path: ^1.8.3 dev_dependencies: flutter_test: sdk: flutter - flutter_lints: ^4.0.0 + flutter_lints: ^5.0.0 path_provider_platform_interface: ^2.1.2 plugin_platform_interface: ^2.1.8