From 8b404aeeccba72b51a3c0b98cb5dcc2ba72bf5e1 Mon Sep 17 00:00:00 2001 From: rodrigosmarques Date: Sun, 14 Apr 2019 13:42:24 -0300 Subject: [PATCH 1/9] Bugfix save and create object / LiveQuery Bugfix save and create object / LiveQuery --- lib/src/network/parse_live_query.dart | 2 +- lib/src/objects/parse_object.dart | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/src/network/parse_live_query.dart b/lib/src/network/parse_live_query.dart index 066920f8b..71a5d7cdc 100644 --- a/lib/src/network/parse_live_query.dart +++ b/lib/src/network/parse_live_query.dart @@ -98,7 +98,7 @@ class LiveQuery { if (_debug) { print('$_printConstLiveQuery: Done'); } - }, onError: (Error error) { + }, onError: (error) { if (_debug) { print( '$_printConstLiveQuery: Error: ${error.runtimeType.toString()}'); diff --git a/lib/src/objects/parse_object.dart b/lib/src/objects/parse_object.dart index 113b19c82..73fb5e871 100644 --- a/lib/src/objects/parse_object.dart +++ b/lib/src/objects/parse_object.dart @@ -65,7 +65,7 @@ class ParseObject extends ParseBase implements ParseCloneable { Future create() async { try { final Uri url = getSanitisedUri(_client, '$_path'); - final String body = json.encode(toJson()); + final String body = json.encode(toJson(forApiRQ: true); final Response result = await _client.post(url, body: body); //Set the objectId on the object after it is created. @@ -89,7 +89,7 @@ class ParseObject extends ParseBase implements ParseCloneable { } else { try { final Uri url = getSanitisedUri(_client, '$_path/$objectId'); - final String body = json.encode(toJson()); + final String body = json.encode(toJson(forApiRQ: true)); final Response result = await _client.put(url, body: body); return handleResponse( this, result, ParseApiRQ.save, _debug, className); From 6d863fdbe573fd4e9dd644819eea71d2ce743589 Mon Sep 17 00:00:00 2001 From: rodrigosmarques Date: Sun, 14 Apr 2019 17:03:43 -0300 Subject: [PATCH 2/9] Bugfix create object Bugfix create object --- lib/src/objects/parse_object.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/objects/parse_object.dart b/lib/src/objects/parse_object.dart index 73fb5e871..80f3ac00f 100644 --- a/lib/src/objects/parse_object.dart +++ b/lib/src/objects/parse_object.dart @@ -65,7 +65,7 @@ class ParseObject extends ParseBase implements ParseCloneable { Future create() async { try { final Uri url = getSanitisedUri(_client, '$_path'); - final String body = json.encode(toJson(forApiRQ: true); + final String body = json.encode(toJson(forApiRQ: true)); final Response result = await _client.post(url, body: body); //Set the objectId on the object after it is created. From 7cd3bf9e6be2e013a5aebb2983936500e09652fb Mon Sep 17 00:00:00 2001 From: rodrigosmarques Date: Fri, 19 Apr 2019 00:14:44 -0300 Subject: [PATCH 3/9] ParseACL implementation ParseACL implementation --- lib/parse_server_sdk.dart | 2 + lib/src/objects/parse_acl.dart | 150 ++++++++++++++++++++++++++++++++ lib/src/objects/parse_base.dart | 18 +++- 3 files changed, 169 insertions(+), 1 deletion(-) create mode 100644 lib/src/objects/parse_acl.dart diff --git a/lib/parse_server_sdk.dart b/lib/parse_server_sdk.dart index 0ec5bcf6e..5541d9a94 100644 --- a/lib/parse_server_sdk.dart +++ b/lib/parse_server_sdk.dart @@ -62,6 +62,8 @@ part 'src/objects/parse_session.dart'; part 'src/objects/parse_user.dart'; +part 'src/objects/parse_acl.dart'; + part 'src/utils/parse_decoder.dart'; part 'src/utils/parse_encoder.dart'; diff --git a/lib/src/objects/parse_acl.dart b/lib/src/objects/parse_acl.dart new file mode 100644 index 000000000..f58dcb209 --- /dev/null +++ b/lib/src/objects/parse_acl.dart @@ -0,0 +1,150 @@ +part of flutter_parse_sdk; + +/// [ParseACL] is used to control which users can access or modify a particular object +/// [ParseObject] can have its own [ParceACL] +/// You can grant read and write permissions separately to specific users +/// or you can grant permissions to "the public" so that, for example, any user could read a particular object but +/// only a particular set of users could write to that object +class ParseACL { + ///Creates an ACL where only the provided user has access. + ///[owner] The only user that can read or write objects governed by this ACL. + ParseACL({ParseUser owner}) { + if (owner != null) { + setReadAccess(userId: owner.objectId, allowed: true); + setWriteAccess(userId: owner.objectId, allowed: true); + } + } + + final String _publicKEY = '*'; + final Map _permissionsById = {}; + + /// Helper for setting stuff + void _setPermissionsIfNonEmpty( + {@required String userId, bool readPermission, bool writePermission}) { + if (!(readPermission || writePermission)) { + _permissionsById.remove(userId); + } else { + _permissionsById[userId] = + _ACLPermissions(readPermission, writePermission); + } + } + + ///Get whether the public is allowed to read this object. + bool getPublicReadAccess() { + return getReadAccess(userId: _publicKEY); + } + + ///Set whether the public is allowed to read this object. + void setPublicReadAccess({@required bool allowed}) { + setReadAccess(userId: _publicKEY, allowed: allowed); + } + + /// Set whether the public is allowed to write this object. + bool getPublicWriteAccess() { + return getWriteAccess(userId: _publicKEY); + } + + ///Set whether the public is allowed to write this object. + void setPublicWriteAccess({@required bool allowed}) { + setWriteAccess(userId: _publicKEY, allowed: allowed); + } + + ///Set whether the given user id is allowed to read this object. + void setReadAccess({@required String userId, bool allowed}) { + if (userId == null) { + throw 'cannot setReadAccess for null userId'; + } + final bool writePermission = getWriteAccess(userId: userId); + _setPermissionsIfNonEmpty( + userId: userId, + readPermission: allowed, + writePermission: writePermission); + } + + /// Get whether the given user id is *explicitly* allowed to read this object. Even if this returns + /// [false], the user may still be able to access it if getPublicReadAccess returns + /// [true] or a role that the user belongs to has read access. + bool getReadAccess({@required String userId}) { + if (userId == null) { + throw 'cannot getReadAccess for null userId'; + } + final _ACLPermissions _permissions = _permissionsById[userId]; + return _permissions != null && _permissions.getReadPermission(); + } + + ///Set whether the given user id is allowed to write this object. + void setWriteAccess({@required String userId, bool allowed}) { + if (userId == null) { + throw 'cannot setWriteAccess for null userId'; + } + final bool readPermission = getReadAccess(userId: userId); + _setPermissionsIfNonEmpty( + userId: userId, + readPermission: readPermission, + writePermission: allowed); + } + + ///Get whether the given user id is *explicitly* allowed to write this object. Even if this + ///returns [false], the user may still be able to write it if getPublicWriteAccess returns + ///[true] or a role that the user belongs to has write access. + bool getWriteAccess({@required String userId}) { + if (userId == null) { + throw 'cannot getWriteAccess for null userId'; + } + final _ACLPermissions _permissions = _permissionsById[userId]; + return _permissions != null && _permissions.getReadPermission(); + } + + Map toJson() { + final Map map = {}; + _permissionsById.forEach((String user, _ACLPermissions permission) { + map[user] = permission.toJson(); + }); + print(map); + return map; + } + + @override + String toString() => json.encode(toJson()); + + ParseACL fromJson(Map map) { + final ParseACL parseACL = ParseACL(); + + map.forEach((String userId, dynamic permission) { + if (permission['read'] != null) { + parseACL.setReadAccess(userId: userId, allowed: permission['read']); + } + if (permission['write'] != null) { + parseACL.setWriteAccess(userId: userId, allowed: permission['write']); + } + }); + return parseACL; + } +} + +class _ACLPermissions { + _ACLPermissions(this._readPermission, this._writePermission); + final String _keyReadPermission = 'read'; + final String _keyWritePermission = 'write'; + bool _readPermission = false; + bool _writePermission = false; + + bool getReadPermission() { + return _readPermission; + } + + bool getWritePermission() { + return _writePermission; + } + + Map toJson() { + final Map map = {}; + if (_readPermission) { + map[_keyReadPermission] = true; + } + if (_writePermission) { + map[_keyWritePermission] = true; + } + return map; + } +} diff --git a/lib/src/objects/parse_base.dart b/lib/src/objects/parse_base.dart index 2cdd579db..906f40ee2 100644 --- a/lib/src/objects/parse_base.dart +++ b/lib/src/objects/parse_base.dart @@ -65,7 +65,7 @@ abstract class ParseBase { map.remove(keyVarCreatedAt); map.remove(keyVarUpdatedAt); map.remove(keyVarClassName); - map.remove(keyVarAcl); + //map.remove(keyVarAcl); map.remove(keyParamSessionToken); } @@ -97,6 +97,8 @@ abstract class ParseBase { } else { set(keyVarUpdatedAt, value); } + } else if (key == keyVarAcl) { + getObjectData()[keyVarAcl] = ParseACL().fromJson(value); } else { getObjectData()[key] = parseDecode(value); } @@ -142,6 +144,20 @@ abstract class ParseBase { } } + ///Set the [ParseACL] governing this object. + void setACL(ParseACL acl) { + getObjectData()[keyVarAcl] = acl; + } + + ///Access the [ParseACL] governing this object. + ParseACL getACL() { + if (getObjectData().containsKey(keyVarAcl)) { + return getObjectData()[keyVarAcl]; + } else { + return ParseACL(); + } + } + /// Gets type [T] from objectData /// /// Returns null or [defaultValue] if provided. To get an int, call From b657650bc86e5d3f3358fa2dbdefaba088425074 Mon Sep 17 00:00:00 2001 From: Rodrigo de Souza Marques Date: Mon, 22 Apr 2019 02:21:29 -0300 Subject: [PATCH 4/9] Update README.MD Update README.MD with ParseACL and ParseCloudFunction --- README.md | 300 +++++++++++++++++++++++++++++++++++------------------- 1 file changed, 194 insertions(+), 106 deletions(-) diff --git a/README.md b/README.md index 7c0f66bb3..768c269d1 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,16 @@ You can create custom objects by calling: var dietPlan = ParseObject('DietPlan') ..set('Name', 'Ketogenic') ..set('Fat', 65); +await dietPlan.save() ``` +Verify that the object has been successfully saved using +```dart +response = await dietPlan.save(); +if (response.success) { + dietPlan = response.result; +} +``` + You then have the ability to do the following with that object: The features available are:- * Get @@ -129,41 +138,41 @@ Once you have setup the project and initialised the instance, you can then retre ```dart var apiResponse = await ParseObject('ParseTableName').getAll(); - if (apiResponse.success){ - for (var testObject in apiResponse.result) { - print(ApplicationConstants.APP_NAME + ": " + testObject.toString()); - } - } +if (apiResponse.success){ + for (var testObject in apiResponse.result) { + print(ApplicationConstants.APP_NAME + ": " + testObject.toString()); + } +} ``` Or you can get an object by its objectId: ```dart var dietPlan = await DietPlan().getObject('R5EonpUDWy'); - if (dietPlan.success) { - print(ApplicationConstants.keyAppName + ": " + (dietPlan.result as DietPlan).toString()); - } else { - print(ApplicationConstants.keyAppName + ": " + dietPlan.exception.message); - } +if (dietPlan.success) { + print(ApplicationConstants.keyAppName + ": " + (dietPlan.result as DietPlan).toString()); +} else { + print(ApplicationConstants.keyAppName + ": " + dietPlan.exception.message); +} ``` ## Complex queries You can create complex queries to really put your database to the test: ```dart - var queryBuilder = QueryBuilder(DietPlan()) - ..startsWith(DietPlan.keyName, "Keto") - ..greaterThan(DietPlan.keyFat, 64) - ..lessThan(DietPlan.keyFat, 66) - ..equals(DietPlan.keyCarbs, 5); - - var response = await queryBuilder.query(); - - if (response.success) { - print(ApplicationConstants.keyAppName + ": " + ((response.result as List).first as DietPlan).toString()); - } else { - print(ApplicationConstants.keyAppName + ": " + response.exception.message); - } +var queryBuilder = QueryBuilder(DietPlan()) + ..startsWith(DietPlan.keyName, "Keto") + ..greaterThan(DietPlan.keyFat, 64) + ..lessThan(DietPlan.keyFat, 66) + ..equals(DietPlan.keyCarbs, 5); + +var response = await queryBuilder.query(); + +if (response.success) { + print(ApplicationConstants.keyAppName + ": " + ((response.result as List).first as DietPlan).toString()); +} else { + print(ApplicationConstants.keyAppName + ": " + response.exception.message); +} ``` The features available are:- @@ -197,15 +206,15 @@ For example, imagine you have Post class and a Comment class, where each Comment You can find comments on posts with images by doing: ```dart - QueryBuilder queryPost = - QueryBuilder(ParseObject('Post')) - ..whereValueExists('image', true); +QueryBuilder queryPost = + QueryBuilder(ParseObject('Post')) + ..whereValueExists('image', true); - QueryBuilder queryComment = - QueryBuilder(ParseObject('Comment')) - ..whereMatchesQuery('post', queryPost); +QueryBuilder queryComment = + QueryBuilder(ParseObject('Comment')) + ..whereMatchesQuery('post', queryPost); - var apiResponse = await queryComment.query(); +var apiResponse = await queryComment.query(); ``` If you want to retrieve objects where a field contains an object that does not match another query, you can use the @@ -214,28 +223,28 @@ Imagine you have Post class and a Comment class, where each Comment has a pointe You can find comments on posts without images by doing: ```dart - QueryBuilder queryPost = - QueryBuilder(ParseObject('Post')) - ..whereValueExists('image', true); +QueryBuilder queryPost = + QueryBuilder(ParseObject('Post')) + ..whereValueExists('image', true); - QueryBuilder queryComment = - QueryBuilder(ParseObject('Comment')) - ..whereDoesNotMatchQuery('post', queryPost); +QueryBuilder queryComment = + QueryBuilder(ParseObject('Comment')) + ..whereDoesNotMatchQuery('post', queryPost); - var apiResponse = await queryComment.query(); +var apiResponse = await queryComment.query(); ``` ## Counting Objects If you only care about the number of games played by a particular player: ```dart - QueryBuilder queryPlayers = - QueryBuilder(ParseObject('GameScore')) - ..whereEqualTo('playerName', 'Jonathan Walsh'); - var apiResponse = await queryPlayers.count(); - if (apiResponse.success && apiResponse.result != null) { - int countGames = apiResponse.count; - } +QueryBuilder queryPlayers = + QueryBuilder(ParseObject('GameScore')) + ..whereEqualTo('playerName', 'Jonathan Walsh'); +var apiResponse = await queryPlayers.count(); +if (apiResponse.success && apiResponse.result != null) { + int countGames = apiResponse.count; +} ``` ## Live Queries @@ -249,32 +258,32 @@ The Parse Server configuration guide on the server is found here https://docs.pa Initialize the Parse Live Query by entering the parameter liveQueryUrl in Parse().initialize: ```dart - Parse().initialize( - ApplicationConstants.keyApplicationId, - ApplicationConstants.keyParseServerUrl, - clientKey: ApplicationConstants.keyParseClientKey, - debug: true, - liveQueryUrl: ApplicationConstants.keyLiveQueryUrl, - autoSendSessionId: true); +Parse().initialize( + ApplicationConstants.keyApplicationId, + ApplicationConstants.keyParseServerUrl, + clientKey: ApplicationConstants.keyParseClientKey, + debug: true, + liveQueryUrl: ApplicationConstants.keyLiveQueryUrl, + autoSendSessionId: true); ``` Declare LiveQuery: ```dart - final LiveQuery liveQuery = LiveQuery(); +final LiveQuery liveQuery = LiveQuery(); ``` Set the QueryBuilder that will be monitored by LiveQuery: ```dart - QueryBuilder query = - QueryBuilder(ParseObject('TestAPI')) - ..whereEqualTo('intNumber', 1); +QueryBuilder query = + QueryBuilder(ParseObject('TestAPI')) + ..whereEqualTo('intNumber', 1); ``` __Create a subscription__ You’ll get the LiveQuery events through this subscription. The first time you call subscribe, we’ll try to open the WebSocket connection to the LiveQuery server for you. ```dart - await liveQuery.subscribe(query); +await liveQuery.subscribe(query); ``` __Event Handling__ @@ -284,15 +293,15 @@ __Create event__ When a new ParseObject is created and it fulfills the QueryBuilder you subscribe, you’ll get this event. The object is the ParseObject which was created. ```dart - liveQuery.on(LiveQueryEvent.create, (value) { - print('*** CREATE ***: ${DateTime.now().toString()}\n $value '); - print((value as ParseObject).objectId); - print((value as ParseObject).updatedAt); - print((value as ParseObject).createdAt); - print((value as ParseObject).get('objectId')); - print((value as ParseObject).get('updatedAt')); - print((value as ParseObject).get('createdAt')); - }); +liveQuery.on(LiveQueryEvent.create, (value) { + print('*** CREATE ***: ${DateTime.now().toString()}\n $value '); + print((value as ParseObject).objectId); + print((value as ParseObject).updatedAt); + print((value as ParseObject).createdAt); + print((value as ParseObject).get('objectId')); + print((value as ParseObject).get('updatedAt')); + print((value as ParseObject).get('createdAt')); +}); ``` __Update event__ @@ -300,15 +309,15 @@ When an existing ParseObject which fulfills the QueryBuilder you subscribe is up QueryBuilder before and after changes), you’ll get this event. The object is the ParseObject which was updated. Its content is the latest value of the ParseObject. ```dart - liveQuery.on(LiveQueryEvent.update, (value) { - print('*** UPDATE ***: ${DateTime.now().toString()}\n $value '); - print((value as ParseObject).objectId); - print((value as ParseObject).updatedAt); - print((value as ParseObject).createdAt); - print((value as ParseObject).get('objectId')); - print((value as ParseObject).get('updatedAt')); - print((value as ParseObject).get('createdAt')); - }); +liveQuery.on(LiveQueryEvent.update, (value) { + print('*** UPDATE ***: ${DateTime.now().toString()}\n $value '); + print((value as ParseObject).objectId); + print((value as ParseObject).updatedAt); + print((value as ParseObject).createdAt); + print((value as ParseObject).get('objectId')); + print((value as ParseObject).get('updatedAt')); + print((value as ParseObject).get('createdAt')); +}); ``` __Enter event__ @@ -316,15 +325,15 @@ When an existing ParseObject’s old value does not fulfill the QueryBuilder but you’ll get this event. The object is the ParseObject which enters the QueryBuilder. Its content is the latest value of the ParseObject. ```dart - liveQuery.on(LiveQueryEvent.enter, (value) { - print('*** ENTER ***: ${DateTime.now().toString()}\n $value '); - print((value as ParseObject).objectId); - print((value as ParseObject).updatedAt); - print((value as ParseObject).createdAt); - print((value as ParseObject).get('objectId')); - print((value as ParseObject).get('updatedAt')); - print((value as ParseObject).get('createdAt')); - }); +liveQuery.on(LiveQueryEvent.enter, (value) { + print('*** ENTER ***: ${DateTime.now().toString()}\n $value '); + print((value as ParseObject).objectId); + print((value as ParseObject).updatedAt); + print((value as ParseObject).createdAt); + print((value as ParseObject).get('objectId')); + print((value as ParseObject).get('updatedAt')); + print((value as ParseObject).get('createdAt')); +}); ``` __Leave event__ @@ -332,30 +341,30 @@ When an existing ParseObject’s old value fulfills the QueryBuilder but its new you’ll get this event. The object is the ParseObject which leaves the QueryBuilder. Its content is the latest value of the ParseObject. ```dart - liveQuery.on(LiveQueryEvent.leave, (value) { - print('*** LEAVE ***: ${DateTime.now().toString()}\n $value '); - print((value as ParseObject).objectId); - print((value as ParseObject).updatedAt); - print((value as ParseObject).createdAt); - print((value as ParseObject).get('objectId')); - print((value as ParseObject).get('updatedAt')); - print((value as ParseObject).get('createdAt')); - }); +liveQuery.on(LiveQueryEvent.leave, (value) { + print('*** LEAVE ***: ${DateTime.now().toString()}\n $value '); + print((value as ParseObject).objectId); + print((value as ParseObject).updatedAt); + print((value as ParseObject).createdAt); + print((value as ParseObject).get('objectId')); + print((value as ParseObject).get('updatedAt')); + print((value as ParseObject).get('createdAt')); +}); ``` __Delete event__ When an existing ParseObject which fulfills the QueryBuilder is deleted, you’ll get this event. The object is the ParseObject which is deleted ```dart - liveQuery.on(LiveQueryEvent.delete, (value) { - print('*** DELETE ***: ${DateTime.now().toString()}\n $value '); - print((value as ParseObject).objectId); - print((value as ParseObject).updatedAt); - print((value as ParseObject).createdAt); - print((value as ParseObject).get('objectId')); - print((value as ParseObject).get('updatedAt')); - print((value as ParseObject).get('createdAt')); - }); +liveQuery.on(LiveQueryEvent.delete, (value) { + print('*** DELETE ***: ${DateTime.now().toString()}\n $value '); + print((value as ParseObject).objectId); + print((value as ParseObject).updatedAt); + print((value as ParseObject).createdAt); + print((value as ParseObject).get('objectId')); + print((value as ParseObject).get('updatedAt')); + print((value as ParseObject).get('createdAt')); +}); ``` __Unsubscribe__ @@ -364,7 +373,7 @@ After that, you won’t get any events from the subscription object and will clo LiveQuery server. ```dart - await liveQuery.unSubscribe(); +await liveQuery.unSubscribe(); ``` ## Users @@ -397,8 +406,64 @@ Other user features are:- * Destroy user * Queries +## Security for Objects - ParseACL +For any object, you can specify which users are allowed to read the object, and which users are allowed to modify an object. +To support this type of security, each object has an access control list, implemented by the __ParseACL__ class. + +If ParseACL is not specified (with the exception of the ParseUser class) all objects are set to Public for read and write. +The simplest way to use a ParseACL is to specify that an object may only be read or written by a single user. +To create such an object, there must first be a logged in ParseUser. Then, new ParseACL(user) generates a ParseACL that +limits access to that user. An object’s ACL is updated when the object is saved, like any other property. + +```dart +ParseUser user = await ParseUser.currentUser() as ParseUser; +ParseACL parseACL = ParseACL(owner: user); + +ParseObject parseObject = ParseObject("TestAPI"); +... +parseObject.setACL(parseACL); +var apiResponse = await parseObject.save(); +``` +Permissions can also be granted on a per-user basis. You can add permissions individually to a ParseACL using +__setReadAccess__ and __setWriteAccess__ +```dart +ParseUser user = await ParseUser.currentUser() as ParseUser; +ParseACL parseACL = ParseACL(); +//grant total access to current user +parseACL.setReadAccess(userId: user.objectId, allowed: true); +parseACL.setWriteAccess(userId: user.objectId, allowed: true); +//grant read access to userId: 'TjRuDjuSAO' +parseACL.setReadAccess(userId: 'TjRuDjuSAO', allowed: true); +parseACL.setWriteAccess(userId: 'TjRuDjuSAO', allowed: false); + +ParseObject parseObject = ParseObject("TestAPI"); +... +parseObject.setACL(parseACL); +var apiResponse = await parseObject.save(); +``` +You can also grant permissions to all users at once using setPublicReadAccess and setPublicWriteAccess. +```dart +ParseACL parseACL = ParseACL(); +parseACL.setPublicReadAccess(allowed: true); +parseACL.setPublicWriteAccess(allowed: true); + +ParseObject parseObject = ParseObject("TestAPI"); +... +parseObject.setACL(parseACL); +var apiResponse = await parseObject.save(); +``` +Operations that are forbidden, such as deleting an object that you do not have write access to, result in a +ParseError with code 101: 'ObjectNotFound'. +For security purposes, this prevents clients from distinguishing which object ids exist but are secured, versus which +object ids do not exist at all. + +You can retrieve the ACL list of an object using: +```dart +ParseACL parseACL = parseObject.getACL(); +``` + ## Config -The SDK now supports Parse Config. A map of all configs can be grabbed from the server by calling : +The SDK supports Parse Config. A map of all configs can be grabbed from the server by calling : ```dart var response = await ParseConfig().getConfigs(); ``` @@ -408,11 +473,34 @@ and to add a config: ParseConfig().addConfig('TestConfig', 'testing'); ``` +## Cloud Functions +The SDK supports call Cloud Functions. + +Executes a cloud function that returns a ParseObject type +```dart +final ParseCloudFunction function = ParseCloudFunction('hello'); +final ParseResponse result = + await function.executeObjectFunction(); +if (result.success) { + if (result.result is ParseObject) { + final ParseObject parseObject = result.result; + print(parseObject.className); + } +} +``` + +Executes a cloud function with parameters +```dart +final ParseCloudFunction function = ParseCloudFunction('hello'); +final Map params = {'plan': 'paid'}; +function.execute(parameters: params); +``` + ## Other Features of this library Main: -* Installation -* GeoPoints -* Files +* Installation (View the example application) +* GeoPoints (View the example application) +* Files (View the example application) * Persistent storage * Debug Mode - Logging API calls * Manage Session ID's tokens From 09e9444a2ad218b7dc4ca54c0e5380fc8423a55d Mon Sep 17 00:00:00 2001 From: Rodrigo de Souza Marques Date: Mon, 22 Apr 2019 02:26:32 -0300 Subject: [PATCH 5/9] Update README.md --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index 768c269d1..d64825aa3 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,17 @@ if (response.success) { dietPlan = response.result; } ``` +Types supported: + * String + * Double + * Int + * Boolean + * DateTime + * File + * Geopoint + * ParseObject/ParseUser (Pointer) + * Map + * List (all types supported) You then have the ability to do the following with that object: The features available are:- From 1fc8b4bfd2c4a31cd5dfeb8f55cdeaec2a65b18c Mon Sep 17 00:00:00 2001 From: Rodrigo de Souza Marques Date: Mon, 22 Apr 2019 02:27:59 -0300 Subject: [PATCH 6/9] Update README.md --- README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/README.md b/README.md index d64825aa3..5a93efecc 100644 --- a/README.md +++ b/README.md @@ -130,6 +130,14 @@ Retrieve it, call ```dart var response = await dietPlan.increment("count", 1); +``` +or using with save function + +```dart +dietPlan.setIncrement('count', 1); +dietPlan.setDecrement('count', 1); +var response = dietPlan.save() + ``` ## Array Operator in objects @@ -142,6 +150,14 @@ var response = await dietPlan.addUnique("listKeywords", ["a", "a","d"]); var response = await dietPlan.remove("listKeywords", ["a"]); +``` +or using with save function + +```dart +dietPlan.setAdd('listKeywords', ['a','a','d']); +dietPlan.setAddUnique('listKeywords', ['a','a','d']); +dietPlan.setRemove('listKeywords', ['a']); +var response = dietPlan.save() ``` ## Queries From 4fc4063603fceff88637c6956766ef8b59ddfad9 Mon Sep 17 00:00:00 2001 From: Rodrigo de Souza Marques Date: Mon, 22 Apr 2019 02:29:40 -0300 Subject: [PATCH 7/9] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5a93efecc..b1ffcee34 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ await dietPlan.save() ``` Verify that the object has been successfully saved using ```dart -response = await dietPlan.save(); +var response = await dietPlan.save(); if (response.success) { dietPlan = response.result; } From 61e833d4261ed577928277c29a618d091787eb95 Mon Sep 17 00:00:00 2001 From: Rodrigo de Souza Marques Date: Mon, 22 Apr 2019 02:36:32 -0300 Subject: [PATCH 8/9] Update README.md --- README.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/README.md b/README.md index b1ffcee34..63fad1ae1 100644 --- a/README.md +++ b/README.md @@ -533,15 +533,7 @@ Main: * Manage Session ID's tokens User: -* Create -* Login * Logout -* CurrentUser -* RequestPasswordReset -* VerificationEmailRequest -* AllUsers -* Save -* Destroy * Queries * Anonymous * 3rd Party Authentication From d1cc5758d09d922b945c348ee9524a7183a601a2 Mon Sep 17 00:00:00 2001 From: Rodrigo de Souza Marques Date: Mon, 22 Apr 2019 02:41:46 -0300 Subject: [PATCH 9/9] Update README.md --- README.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 63fad1ae1..668770a47 100644 --- a/README.md +++ b/README.md @@ -416,11 +416,18 @@ Then have the user sign up: var response = await user.signUp(); if (response.success) user = response.result; ``` -You can also logout and login with the user: +You can also login with the user: ```dart var response = await user.login(); if (response.success) user = response.result; ``` +You can also logout with the user: +```dart +var response = await user.logout(); +if (response.success) { + print('User logout'); +} +``` Also, once logged in you can manage sessions tokens. This feature can be called after Parse().init() on startup to check for a logged in user. ```dart user = ParseUser.currentUser(); @@ -533,9 +540,8 @@ Main: * Manage Session ID's tokens User: -* Logout * Queries -* Anonymous +* Anonymous (View the example application) * 3rd Party Authentication Objects: