diff --git a/packages/dart/example/main.dart b/packages/dart/example/main.dart index ce96602d..b4e9fa6d 100644 --- a/packages/dart/example/main.dart +++ b/packages/dart/example/main.dart @@ -14,10 +14,23 @@ 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').execute({ + r'$match': {'Name': 'Ketogenic'}, + r'$group': { + '_id': r'isHungry', + 'count': {r'$sum': 1} + }, + }); + + print('${res.statusCode}, ${res.results}'); + } diff --git a/packages/dart/lib/parse_server_sdk.dart b/packages/dart/lib/parse_server_sdk.dart index 3b27e056..9716c748 100644 --- a/packages/dart/lib/parse_server_sdk.dart +++ b/packages/dart/lib/parse_server_sdk.dart @@ -29,111 +29,61 @@ 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_aggregate.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_jobs.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/base/parse_constants.dart b/packages/dart/lib/src/base/parse_constants.dart index 5136d4c5..20344b1d 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_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/objects/parse_aggregate.dart b/packages/dart/lib/src/objects/parse_aggregate.dart new file mode 100644 index 00000000..7f87f575 --- /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 5e307303..69f49cf3 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 00000000..e7ada64c --- /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 ad1c21bd..c337ec6b 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(); + } } 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