From 8ad7e19ef4f13cc47c78041f02eddd57400b2b0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mehmet=20S=CC=A7eref=20Kayal=C4=B1?= Date: Wed, 30 Apr 2025 17:49:46 +0300 Subject: [PATCH 1/9] no message --- packages/dart/example/main.dart | 2 + packages/dart/lib/parse_server_sdk.dart | 55 +------------------ .../dart/lib/src/network/parse_aggregate.dart | 30 ++++++++++ .../lib/src/network/parse_http_client.dart | 17 +++++- packages/dart/lib/src/utils/url_replace.dart | 24 ++++++++ 5 files changed, 72 insertions(+), 56 deletions(-) create mode 100644 packages/dart/lib/src/network/parse_aggregate.dart create mode 100644 packages/dart/lib/src/utils/url_replace.dart diff --git a/packages/dart/example/main.dart b/packages/dart/example/main.dart index ce96602d3..3ef6f8254 100644 --- a/packages/dart/example/main.dart +++ b/packages/dart/example/main.dart @@ -14,6 +14,8 @@ Future main() async { ..set('Name', 'Ketogenic') ..set('Fat', 65); + // ParseAggregate('className', pipeline: {}).execute(); + var response = await dietPlan.save(); if (response.success) { diff --git a/packages/dart/lib/parse_server_sdk.dart b/packages/dart/lib/parse_server_sdk.dart index 3b27e0560..4f4e57141 100644 --- a/packages/dart/lib/parse_server_sdk.dart +++ b/packages/dart/lib/parse_server_sdk.dart @@ -11,6 +11,7 @@ import 'package:cross_file/cross_file.dart'; import 'package:dio/dio.dart'; import 'package:meta/meta.dart'; import 'package:mime/mime.dart'; +import 'package:parse_server_sdk/src/utils/url_replace.dart'; import 'package:path/path.dart' as path; import 'package:sembast/sembast.dart'; import 'package:sembast/sembast_io.dart'; @@ -29,111 +30,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 000000000..5f4ac370d --- /dev/null +++ b/packages/dart/lib/src/network/parse_aggregate.dart @@ -0,0 +1,30 @@ +part of '../../parse_server_sdk.dart'; + +class ParseAggregate { + final String className; + final Map pipeline; + final bool? debug; + final ParseClient? client; + final bool? autoSendSessionId; + final String? parseClassName; + + ParseAggregate(this.className,{required this.pipeline,this.debug, this.client, this.autoSendSessionId, this.parseClassName}); + + Future execute() async { + if(pipeline.isEmpty){ + throw ArgumentError('pipeline must not be empty. Please add pipeline operations to aggregate data. Example: {"\$group": {"_id": "\$userId", "totalScore": {"\$sum": "\$score"}}} '); + } + final debugBool = isDebugEnabled(objectLevelDebug: debug); + final result = await ParseHTTPClient().get( + '${ParseCoreData().serverUrl}$keyEndPointAggregate/$className', + replace: UrlReplace(queryParameters: pipeline) + ); + return handleResponse( + this, + 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 5b1b9795d..7ea1a27d7 100644 --- a/packages/dart/lib/src/network/parse_http_client.dart +++ b/packages/dart/lib/src/network/parse_http_client.dart @@ -1,9 +1,9 @@ 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:parse_server_sdk/src/utils/url_replace.dart'; +import 'package:universal_io/io.dart'; import 'http_client_io.dart' if (dart.library.js) 'http_client_js.dart'; @@ -30,9 +30,20 @@ class ParseHTTPClient extends ParseClient { String path, { ParseNetworkOptions? options, ProgressCallback? onReceiveProgress, + UrlReplace? replace }) async { final http.Response response = await _client.get( - Uri.parse(path), + replace != null ? Uri.parse(path).replace( + scheme:replace.scheme, + userInfo:replace.userInfo, + host:replace.host, + port:replace.port, + path:replace.path, + pathSegments:replace.pathSegments, + query:replace.query, + queryParameters:replace.queryParameters, + fragment:replace.fragment, + ): Uri.parse(path), headers: options?.headers, ); return ParseNetworkResponse( 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 000000000..9a0a1e0e1 --- /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 From e65a3f560918085c2c28fbc986b44337e2ca0612 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mehmet=20S=CC=A7eref=20Kayal=C4=B1?= Date: Wed, 30 Apr 2025 18:25:07 +0300 Subject: [PATCH 2/9] packages updated --- packages/dart/pubspec.yaml | 8 ++++---- packages/flutter/pubspec.yaml | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/dart/pubspec.yaml b/packages/dart/pubspec.yaml index ae668f690..82424aa4c 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/pubspec.yaml b/packages/flutter/pubspec.yaml index 21f3d0a46..059c22983 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 From c093ff1387942452505ddd72aabc227ec2344c72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mehmet=20S=CC=A7eref=20Kayal=C4=B1?= Date: Wed, 30 Apr 2025 21:53:34 +0300 Subject: [PATCH 3/9] Aggregate done! --- packages/dart/example/main.dart | 8 +++++++- .../dart/lib/src/network/parse_aggregate.dart | 14 +++++++++----- .../dart/lib/src/network/parse_http_client.dart | 15 ++------------- packages/flutter/example/.gitignore | 2 ++ 4 files changed, 20 insertions(+), 19 deletions(-) diff --git a/packages/dart/example/main.dart b/packages/dart/example/main.dart index 3ef6f8254..8f0b03a35 100644 --- a/packages/dart/example/main.dart +++ b/packages/dart/example/main.dart @@ -14,7 +14,7 @@ Future main() async { ..set('Name', 'Ketogenic') ..set('Fat', 65); - // ParseAggregate('className', pipeline: {}).execute(); + var response = await dietPlan.save(); @@ -22,4 +22,10 @@ Future main() async { dietPlan = response.results?.first; print("Response received successfully"); } + + final res = await ParseAggregate('DietPlan', pipeline: { + r'$match': {'Name': 'Ketogenic'} + }).execute(); + + print(res); } diff --git a/packages/dart/lib/src/network/parse_aggregate.dart b/packages/dart/lib/src/network/parse_aggregate.dart index 5f4ac370d..f30b7ba32 100644 --- a/packages/dart/lib/src/network/parse_aggregate.dart +++ b/packages/dart/lib/src/network/parse_aggregate.dart @@ -2,7 +2,7 @@ part of '../../parse_server_sdk.dart'; class ParseAggregate { final String className; - final Map pipeline; + Map pipeline; final bool? debug; final ParseClient? client; final bool? autoSendSessionId; @@ -11,16 +11,20 @@ class ParseAggregate { ParseAggregate(this.className,{required this.pipeline,this.debug, this.client, this.autoSendSessionId, this.parseClassName}); 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])}); + } final debugBool = isDebugEnabled(objectLevelDebug: debug); - final result = await ParseHTTPClient().get( - '${ParseCoreData().serverUrl}$keyEndPointAggregate/$className', - replace: UrlReplace(queryParameters: pipeline) + final result = await ParseObject(className)._client.get( + Uri.parse('${ParseCoreData().serverUrl}$keyEndPointAggregate$className').replace(queryParameters: _pipeline).toString(), ); + print('result >>> ${result.data}'); return handleResponse( - this, + ParseObject(className), result, ParseApiRQ.get, debugBool, diff --git a/packages/dart/lib/src/network/parse_http_client.dart b/packages/dart/lib/src/network/parse_http_client.dart index 7ea1a27d7..1732ec687 100644 --- a/packages/dart/lib/src/network/parse_http_client.dart +++ b/packages/dart/lib/src/network/parse_http_client.dart @@ -2,7 +2,6 @@ import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:parse_server_sdk/parse_server_sdk.dart'; -import 'package:parse_server_sdk/src/utils/url_replace.dart'; import 'package:universal_io/io.dart'; import 'http_client_io.dart' if (dart.library.js) 'http_client_js.dart'; @@ -30,20 +29,10 @@ class ParseHTTPClient extends ParseClient { String path, { ParseNetworkOptions? options, ProgressCallback? onReceiveProgress, - UrlReplace? replace }) async { + final http.Response response = await _client.get( - replace != null ? Uri.parse(path).replace( - scheme:replace.scheme, - userInfo:replace.userInfo, - host:replace.host, - port:replace.port, - path:replace.path, - pathSegments:replace.pathSegments, - query:replace.query, - queryParameters:replace.queryParameters, - fragment:replace.fragment, - ): Uri.parse(path), + Uri.parse(path), headers: options?.headers, ); return ParseNetworkResponse( diff --git a/packages/flutter/example/.gitignore b/packages/flutter/example/.gitignore index 24476c5d1..6c319542b 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 From 29d83f5f70e179175b5bf7da0e51828bb4a72583 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mehmet=20S=CC=A7eref=20Kayal=C4=B1?= Date: Fri, 30 May 2025 18:47:14 +0300 Subject: [PATCH 4/9] no message --- packages/dart/example/main.dart | 2 +- packages/dart/lib/src/network/parse_aggregate.dart | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/dart/example/main.dart b/packages/dart/example/main.dart index 8f0b03a35..59e9f3589 100644 --- a/packages/dart/example/main.dart +++ b/packages/dart/example/main.dart @@ -27,5 +27,5 @@ Future main() async { r'$match': {'Name': 'Ketogenic'} }).execute(); - print(res); + print(res.result); } diff --git a/packages/dart/lib/src/network/parse_aggregate.dart b/packages/dart/lib/src/network/parse_aggregate.dart index f30b7ba32..bf4a60670 100644 --- a/packages/dart/lib/src/network/parse_aggregate.dart +++ b/packages/dart/lib/src/network/parse_aggregate.dart @@ -2,7 +2,7 @@ part of '../../parse_server_sdk.dart'; class ParseAggregate { final String className; - Map pipeline; + dynamic pipeline; final bool? debug; final ParseClient? client; final bool? autoSendSessionId; @@ -12,11 +12,14 @@ class ParseAggregate { Future execute() async { Map _pipeline={}; + if(!((pipeline is Map) || (pipeline is List))){ + throw ArgumentError('pipeline Object should be a Map or a List of Maps. Example1: {"\$group": {"_id": "\$userId", "totalScore": {"\$sum": "\$score"}}} '); + } 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])}); + _pipeline.addAll({'pipeline':jsonEncode(pipeline is List? pipeline : [pipeline])}); } final debugBool = isDebugEnabled(objectLevelDebug: debug); final result = await ParseObject(className)._client.get( From 3a4feb6b1ec264ccd6d0048414109e50d2aaadbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mehmet=20S=CC=A7eref=20Kayal=C4=B1?= Date: Sat, 31 May 2025 15:21:20 +0300 Subject: [PATCH 5/9] Pipeline bug fixed (complex aggregates supported) --- packages/dart/lib/src/network/parse_aggregate.dart | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/dart/lib/src/network/parse_aggregate.dart b/packages/dart/lib/src/network/parse_aggregate.dart index bf4a60670..d2fb1f5e6 100644 --- a/packages/dart/lib/src/network/parse_aggregate.dart +++ b/packages/dart/lib/src/network/parse_aggregate.dart @@ -2,7 +2,7 @@ part of '../../parse_server_sdk.dart'; class ParseAggregate { final String className; - dynamic pipeline; + Map pipeline; final bool? debug; final ParseClient? client; final bool? autoSendSessionId; @@ -12,14 +12,11 @@ class ParseAggregate { Future execute() async { Map _pipeline={}; - if(!((pipeline is Map) || (pipeline is List))){ - throw ArgumentError('pipeline Object should be a Map or a List of Maps. Example1: {"\$group": {"_id": "\$userId", "totalScore": {"\$sum": "\$score"}}} '); - } 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 is List? pipeline : [pipeline])}); + _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( From bd2b0571d4faf6b712ead6f86ede46297390f3d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mehmet=20S=CC=A7eref=20Kayal=C4=B1?= Date: Sat, 31 May 2025 15:21:38 +0300 Subject: [PATCH 6/9] no message --- packages/dart/example/main.dart | 6 +++++- packages/dart/lib/parse_server_sdk.dart | 1 - 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/dart/example/main.dart b/packages/dart/example/main.dart index 59e9f3589..6a4260bea 100644 --- a/packages/dart/example/main.dart +++ b/packages/dart/example/main.dart @@ -24,7 +24,11 @@ Future main() async { } final res = await ParseAggregate('DietPlan', pipeline: { - r'$match': {'Name': 'Ketogenic'} + 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 4f4e57141..dbc09ef77 100644 --- a/packages/dart/lib/parse_server_sdk.dart +++ b/packages/dart/lib/parse_server_sdk.dart @@ -11,7 +11,6 @@ import 'package:cross_file/cross_file.dart'; import 'package:dio/dio.dart'; import 'package:meta/meta.dart'; import 'package:mime/mime.dart'; -import 'package:parse_server_sdk/src/utils/url_replace.dart'; import 'package:path/path.dart' as path; import 'package:sembast/sembast.dart'; import 'package:sembast/sembast_io.dart'; From 7dabdcba232de08a03891cb62f68815edddee8d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mehmet=20S=CC=A7eref=20Kayal=C4=B1?= Date: Sat, 31 May 2025 15:33:36 +0300 Subject: [PATCH 7/9] code documentation compleated --- .../dart/lib/src/network/parse_aggregate.dart | 78 ++++++++++++++++--- 1 file changed, 68 insertions(+), 10 deletions(-) diff --git a/packages/dart/lib/src/network/parse_aggregate.dart b/packages/dart/lib/src/network/parse_aggregate.dart index d2fb1f5e6..d9532c2c1 100644 --- a/packages/dart/lib/src/network/parse_aggregate.dart +++ b/packages/dart/lib/src/network/parse_aggregate.dart @@ -1,28 +1,86 @@ 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; - ParseAggregate(this.className,{required this.pipeline,this.debug, this.client, this.autoSendSessionId, this.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())}); + 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).toString(), - ); - print('result >>> ${result.data}'); + 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, From 953b54aea928513b45599b98dba209672e086e8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mehmet=20S=CC=A7eref=20Kayal=C4=B1?= Date: Tue, 17 Jun 2025 15:41:41 +0300 Subject: [PATCH 8/9] Aggregate result type changed to ParseNetworkResponse to handle custom objects. --- packages/dart/example/main.dart | 2 +- packages/dart/lib/src/network/parse_aggregate.dart | 12 +++--------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/packages/dart/example/main.dart b/packages/dart/example/main.dart index 6a4260bea..5a8fcb9ad 100644 --- a/packages/dart/example/main.dart +++ b/packages/dart/example/main.dart @@ -31,5 +31,5 @@ Future main() async { }, }).execute(); - print(res.result); + print('${res.statusCode}, ${res.data}'); } diff --git a/packages/dart/lib/src/network/parse_aggregate.dart b/packages/dart/lib/src/network/parse_aggregate.dart index d9532c2c1..66f07671f 100644 --- a/packages/dart/lib/src/network/parse_aggregate.dart +++ b/packages/dart/lib/src/network/parse_aggregate.dart @@ -59,7 +59,7 @@ class ParseAggregate { /// /// Returns a [ParseResponse] containing the results of the aggregation. /// Throws [ArgumentError] if the pipeline is empty. - Future execute() async { + Future execute() async { Map _pipeline = {}; if (pipeline.isEmpty) { @@ -73,7 +73,7 @@ class ParseAggregate { }); } - final debugBool = isDebugEnabled(objectLevelDebug: debug); + // 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())} @@ -81,12 +81,6 @@ class ParseAggregate { ); - return handleResponse( - ParseObject(className), - result, - ParseApiRQ.get, - debugBool, - parseClassName ?? 'ParseBase', - ); + return result; } } From a255b0a72ddf45de8d52da143a589ca7ecc9fbf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mehmet=20S=CC=A7eref=20Kayal=C4=B1?= Date: Tue, 1 Jul 2025 15:06:38 +0300 Subject: [PATCH 9/9] ParseAggregate method updated, small bugs fixed --- packages/dart/example/main.dart | 7 +- packages/dart/lib/parse_server_sdk.dart | 4 +- .../dart/lib/src/base/parse_constants.dart | 2 + .../dart/lib/src/network/parse_aggregate.dart | 86 -------------- .../dart/lib/src/objects/parse_aggregate.dart | 53 +++++++++ .../dart/lib/src/objects/parse_function.dart | 2 +- packages/dart/lib/src/objects/parse_jobs.dart | 58 ++++++++++ .../dart/lib/src/objects/parse_response.dart | 105 +++++++++++++++++- 8 files changed, 221 insertions(+), 96 deletions(-) delete mode 100644 packages/dart/lib/src/network/parse_aggregate.dart create mode 100644 packages/dart/lib/src/objects/parse_aggregate.dart create mode 100644 packages/dart/lib/src/objects/parse_jobs.dart diff --git a/packages/dart/example/main.dart b/packages/dart/example/main.dart index 5a8fcb9ad..b4e9fa6da 100644 --- a/packages/dart/example/main.dart +++ b/packages/dart/example/main.dart @@ -23,13 +23,14 @@ Future main() async { print("Response received successfully"); } - final res = await ParseAggregate('DietPlan', pipeline: { + final res = await ParseAggregate('DietPlan').execute({ r'$match': {'Name': 'Ketogenic'}, r'$group': { '_id': r'isHungry', 'count': {r'$sum': 1} }, - }).execute(); + }); + + print('${res.statusCode}, ${res.results}'); - print('${res.statusCode}, ${res.data}'); } diff --git a/packages/dart/lib/parse_server_sdk.dart b/packages/dart/lib/parse_server_sdk.dart index dbc09ef77..9716c7484 100644 --- a/packages/dart/lib/parse_server_sdk.dart +++ b/packages/dart/lib/parse_server_sdk.dart @@ -33,12 +33,13 @@ 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_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_aggregate.dart'; part 'src/objects/parse_array.dart'; part 'src/objects/parse_base.dart'; part 'src/objects/parse_cloneable.dart'; @@ -51,6 +52,7 @@ 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_jobs.dart'; part 'src/objects/parse_number.dart'; part 'src/objects/parse_object.dart'; part 'src/objects/parse_operation/parse_add_operation.dart'; diff --git a/packages/dart/lib/src/base/parse_constants.dart b/packages/dart/lib/src/base/parse_constants.dart index 5136d4c55..20344b1da 100644 --- a/packages/dart/lib/src/base/parse_constants.dart +++ b/packages/dart/lib/src/base/parse_constants.dart @@ -16,6 +16,8 @@ const String keyEndPointRequestPasswordReset = '/requestPasswordReset'; const String keyEndPointClasses = '/classes/'; const String keyEndPointHealth = '/health'; const String keyEndPointAggregate = '/aggregate/'; +const String keyEndPointFunctions = '/functions/'; +const String keyEndPointJobs = '/jobs/'; // ParseObject variables const String keyVarClassName = 'className'; diff --git a/packages/dart/lib/src/network/parse_aggregate.dart b/packages/dart/lib/src/network/parse_aggregate.dart deleted file mode 100644 index 66f07671f..000000000 --- a/packages/dart/lib/src/network/parse_aggregate.dart +++ /dev/null @@ -1,86 +0,0 @@ -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 result; - } -} diff --git a/packages/dart/lib/src/objects/parse_aggregate.dart b/packages/dart/lib/src/objects/parse_aggregate.dart new file mode 100644 index 000000000..7f87f5757 --- /dev/null +++ b/packages/dart/lib/src/objects/parse_aggregate.dart @@ -0,0 +1,53 @@ + +part of '../../parse_server_sdk.dart'; + +class ParseAggregate extends ParseObject { + + ParseAggregate( + this.functionName, { + bool? debug, + ParseClient? client, + bool? autoSendSessionId, + }) : super( + functionName, + client: client, + autoSendSessionId: autoSendSessionId, + debug: debug, + ) { + _path = '$keyEndPointAggregate$functionName'; + } + + final String functionName; + + @override + late String _path; + + Future execute(Map pipeline, {Map? headers}) async { + final String uri = '${ParseCoreData().serverUrl}$_path'; + + Map parameters = {}; + + 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 { + parameters.addAll({ + 'pipeline': jsonEncode(pipeline.entries.map((e) => {e.key: e.value}).toList()) + }); + _setObjectData(pipeline); + } + + try { + print(Uri.parse(uri).replace(queryParameters: parameters).toString()); + final ParseNetworkResponse result = await _client.get( + Uri.parse(uri).replace(queryParameters: parameters).toString(), + options: ParseNetworkOptions(headers: headers) + ); + return ParseResponse.fromParseNetworkResponse(result); + } on Exception catch (e) { + return handleException(e, ParseApiRQ.execute, _debug, parseClassName); + } + } +} diff --git a/packages/dart/lib/src/objects/parse_function.dart b/packages/dart/lib/src/objects/parse_function.dart index 5e307303e..69f49cf37 100644 --- a/packages/dart/lib/src/objects/parse_function.dart +++ b/packages/dart/lib/src/objects/parse_function.dart @@ -15,7 +15,7 @@ class ParseCloudFunction extends ParseObject { autoSendSessionId: autoSendSessionId, debug: debug, ) { - _path = '/functions/$functionName'; + _path = '$keyEndPointFunctions$functionName'; } final String functionName; diff --git a/packages/dart/lib/src/objects/parse_jobs.dart b/packages/dart/lib/src/objects/parse_jobs.dart new file mode 100644 index 000000000..e7ada64c8 --- /dev/null +++ b/packages/dart/lib/src/objects/parse_jobs.dart @@ -0,0 +1,58 @@ +part of '../../parse_server_sdk.dart'; + +class ParseJobs extends ParseObject { + /// Creates a new cloud function object + /// + /// {https://docs.parseplatform.org/cloudcode/guide/} + ParseJobs( + this.functionName, { + bool? debug, + ParseClient? client, + bool? autoSendSessionId, + }) : super( + functionName, + client: client, + autoSendSessionId: autoSendSessionId, + debug: debug, + ) { + _path = '$keyEndPointJobs$functionName'; + } + + final String functionName; + + @override + // ignore: overridden_fields + late String _path; + + /// Executes a cloud function + /// + /// To add the parameters, create an object and call [set](value to set) + Future execute({Map? parameters, Map? headers}) async { + final String uri = '${ParseCoreData().serverUrl}$_path'; + if (parameters != null) { + _setObjectData(parameters); + } + try { + final ParseNetworkResponse result = await _client.post(uri, options: ParseNetworkOptions(headers: headers), data: json.encode(_getObjectData())); + return handleResponse(this, result, ParseApiRQ.execute, _debug, parseClassName); + } on Exception catch (e) { + return handleException(e, ParseApiRQ.execute, _debug, parseClassName); + } + } + + /// Executes a cloud function that returns a ParseObject type + /// + /// To add the parameters, create an object and call [set](value to set) + Future executeObjectFunction({Map? parameters, Map? headers}) async { + final String uri = '${ParseCoreData().serverUrl}$_path'; + if (parameters != null) { + _setObjectData(parameters); + } + try { + final ParseNetworkResponse result = await _client.post(uri, options: ParseNetworkOptions(headers: headers), data: json.encode(_getObjectData())); + return handleResponse(this, result, ParseApiRQ.executeObjectionFunction, _debug, parseClassName); + } on Exception catch (e) { + return handleException(e, ParseApiRQ.executeObjectionFunction, _debug, parseClassName); + } + } +} diff --git a/packages/dart/lib/src/objects/parse_response.dart b/packages/dart/lib/src/objects/parse_response.dart index ad1c21bd0..c337ec6b8 100644 --- a/packages/dart/lib/src/objects/parse_response.dart +++ b/packages/dart/lib/src/objects/parse_response.dart @@ -1,22 +1,117 @@ part of '../../parse_server_sdk.dart'; +/// A standardized response object returned by all Parse Server operations. +/// +/// This class wraps the response received from the Parse Server and normalizes +/// the structure so that your Dart application can easily determine: +/// - whether the request was successful +/// - the HTTP status code +/// - the result(s) returned by the server +/// - any error information +/// +/// +/// ## Example usage +/// ```dart +/// final response = await myParseQuery.find(); +/// if (response.success) { +/// print('Fetched ${response.count} objects.'); +/// for (final obj in response.results ?? []) { +/// print(obj); +/// } +/// } else { +/// print('Error: ${response.error?.message}'); +/// } +/// ``` class ParseResponse { + /// Creates a new [ParseResponse] instance. + /// + /// You typically don't instantiate this directly — it's created by + /// internal SDK logic, e.g. through [fromParseNetworkResponse]. ParseResponse({ this.error, }); + /// Whether the request was successful. + /// + /// This is `true` if the HTTP status code was 200 or 201. bool success = false; + + /// The HTTP status code returned by the Parse Server. + /// + /// Defaults to -1 if not yet populated. int statusCode = -1; - /// If result is a singular result, i.e. getByObjectID + /// The direct result from the Parse Server. /// - /// This is now deprecated - Please use results. This will contain a list of - /// results, no need to check if its a list or a list of elements anymore. + /// This might be a `Map`, a `List`, or any other decoded JSON. + /// + /// --- + /// ⚠️ **Deprecated:** + /// You should prefer using [results], which is guaranteed to be a list of results. dynamic result; - /// All results stored as a list - Even if only one response is returned - // ignore: always_specify_types + /// The list of results returned by the Parse Server. + /// + /// Even if only one object is returned, it will still be inside a list. + /// + /// This is the recommended way to access your fetched data. List? results; + + /// The number of objects returned (i.e. `results.length`). + /// + /// Will be 0 if there were no results. int count = 0; + + /// If the request failed, this contains error information. ParseError? error; + + /// Builds a [ParseResponse] from a [ParseNetworkResponse], + /// typically after making an HTTP request. + /// + /// - Decodes JSON data + /// - Determines `success` based on HTTP status codes + /// - Populates [results] and [count] if the response contains a list + factory ParseResponse.fromParseNetworkResponse(ParseNetworkResponse response) { + final ParseResponse result = ParseResponse(); + result.statusCode = response.statusCode; + result.success = response.statusCode >= 200 && response.statusCode < 300; + try { + // Attempt to decode JSON data + final data = jsonDecode(response.data); + // Fallback if `result` was not already populated + result.result ??= data; + // Handle typical Parse Server response structures + if (data is Map) { + if (data.containsKey('results')) { + final resultList = data['results']; + if (resultList is List) { + result.results = resultList; + result.count = resultList.length; + } + }else if(data.containsKey('error')){ + result.error=ParseError(code:response.statusCode, message:data['error'].toString() ); + } + } else if (data is List) { + result.results = data; + result.count = data.length; + } + result.results ??= [data]; + } catch (e,s) { + result.error = ParseError(message: e.toString(),exception: Exception(s)); + } + return result; + } + Map get toMap=>{ + 'success': success, + 'statusCode': statusCode, + // 'result': result, + 'results': results, + 'count': count, + 'error': error, + }; + + @override + String toString() { + return toMap.toString(); + } }