├── README.MD ├── LICENSE └── mod_parse.lua /README.MD: -------------------------------------------------------------------------------- 1 | ### mod_parse has been replaced with the Parse plugin for Corona SDK. 2 | 3 | __You are free to continue using mod_parse, but it will no longer be updated.__ 4 | 5 | ## [Get the New Parse Plugin for Corona SDK](https://marketplace.coronalabs.com/plugin/parse) 6 | 7 | 8 | mod_parse 2.2.x 9 | 10 | A Parse.com module for Corona SDK. 11 | 12 | :page_facing_up: **Documentation and usage is [in the wiki](https://github.com/develephant/mod_parse/wiki).** 13 | 14 | ## Coronium Core 15 | 16 | _An all-in-one Lua application server for [Corona](https://coronalabs.com) developers_ 17 | 18 | __[Coronium Core](https://develephant.github.io/coronium-core-docs/)__ 19 | 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013-2015 C. Byerley - develephant.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /mod_parse.lua: -------------------------------------------------------------------------------- 1 | ---Parse module for Corona SDK 2 | -- @copyright develephant 2013-2015 3 | -- @author Chris Byerley 4 | -- @license MIT 5 | -- @version 2.2.4 6 | -- @see parse.com 7 | local json = require("json") 8 | local url = require("socket.url") 9 | 10 | ---Parse Class 11 | -- @type Parse 12 | local Parse = 13 | { 14 | --===========================================================================-- 15 | --== Options Start 16 | --===========================================================================-- 17 | 18 | --Shows a clean table/object output 19 | --in the main terminal. 20 | showStatus = false, --default: false 21 | 22 | --Show the http headers in the 23 | --Parse response status output. 24 | --showStatus must be 'true' as well. 25 | showStatusHeaders = false, --default: false 26 | 27 | --Output some basic information in 28 | --a pop-up alert. Best for phone. 29 | showAlert = false, --default: false 30 | 31 | --Output the Parse response as 32 | --JSON in the output console. 33 | showJSON = false, --default: false 34 | 35 | --Put 'results' outside of the 'response' key. 36 | --By default when you recieve multiple records, 37 | --the result set is put in a 'results' key on 38 | --the 'response' object. To instead place the 39 | --'results' key directly on the main object 40 | --set this to 'false'. You would then access 41 | --the results directly: `parse_response.results` 42 | --as opposed to: `parse_response.response.results` 43 | --Only works with multi-object response results. 44 | resultsInResponse = true, --default: true 45 | 46 | --===========================================================================-- 47 | --== Options Done. Nothing to see here... 48 | --===========================================================================-- 49 | 50 | --Various initialization 51 | endpoint = "https://api.parse.com/1/", 52 | sessionToken = nil, 53 | dispatcher = display.newGroup(), 54 | 55 | --Set up clean request queue 56 | requestQueue = {}, 57 | 58 | --Parse endpoints 59 | NIL = nil, 60 | ERROR = "ERROR", 61 | EXPIRED = 101, 62 | OBJECT = "classes", 63 | USER = "users", 64 | LOGIN = "login", 65 | ANALYTICS = "events", 66 | INSTALLATION = "installations", 67 | CLOUD = "functions", 68 | FILE = "files", 69 | ROLE = "roles", 70 | PUSH = "push", 71 | 72 | --class constants 73 | USER_CLASS = "_User", 74 | ROLE_CLASS = "_Role", 75 | 76 | --action constants 77 | POST = "POST", 78 | GET = "GET", 79 | PUT = "PUT", 80 | DELETE = "DELETE", 81 | 82 | --upload types 83 | TEXT = "text/plain", 84 | PNG = "image/png", 85 | JPG = "images/jpeg", 86 | MOV = "video/quicktime", 87 | M4V = "video/x-m4v", 88 | MP4 = "video/mp4", 89 | 90 | --set these with the init method 91 | --not directly in the file. 92 | appId = nil, 93 | apiKey = nil 94 | } 95 | 96 | ---Data Objects 97 | -- @section data-oblects 98 | 99 | ---Create a new data object. 100 | -- @string objClass The class object name. 101 | -- @tab objDataTable The data table to create the object with. 102 | -- @func[opt] _callback The callback function. 103 | -- @treturn int The network request ID. 104 | -- @usage 105 | -- local function onCreateObject( event ) 106 | -- if not event.error then 107 | -- print( event.response.createdAt ) 108 | -- end 109 | -- end 110 | -- local dataTable = { ["score"] = 1200, ["cheatMode"] = false } 111 | -- parse:createObject( "MyClass", dataTable, onCreateObject ) 112 | function Parse:createObject( objClass, objDataTable, _callback ) 113 | local uri = Parse:getEndpoint( Parse.OBJECT .. "/" .. objClass ) 114 | return self:sendRequest( uri, objDataTable, Parse.OBJECT, Parse.POST, _callback ) 115 | end 116 | 117 | ---Get a data object. 118 | -- @string objClass The object class name. 119 | -- @string objId The object ID. 120 | -- @func[opt] _callback The callback function. 121 | -- @treturn int The network request ID. 122 | -- @usage 123 | -- local function onGetObject( event ) 124 | -- if not event.error then 125 | -- print( event.response.objectId ) 126 | -- end 127 | -- end 128 | -- parse:getObject( "MyClass", "objectId", onGetObject ) 129 | function Parse:getObject( objClass, objId, _callback ) 130 | local uri = Parse:getEndpoint( Parse.OBJECT .. "/" .. objClass .. "/" .. objId ) 131 | return self:sendRequest( uri, {}, Parse.OBJECT, Parse.GET, _callback ) 132 | end 133 | 134 | ---Update a data object. 135 | -- @string objClass The object class name. 136 | -- @string objId The object ID. 137 | -- @tab objDataTable The data table to update the object with. 138 | -- @func[opt] _callback The callback function. 139 | -- @treturn int The network request ID. 140 | -- @usage 141 | -- local function onUpdateObject( event ) 142 | -- if not event.error then 143 | -- print( event.response.updatedAt ) 144 | -- end 145 | -- end 146 | -- local dataTable = { ["score"] = 5200, ["cheatMode"] = true } 147 | -- parse:updateObject( "MyClass", "objectId", dataTable, onUpdateObject ) 148 | function Parse:updateObject( objClass, objId, objDataTable, _callback ) 149 | local uri = Parse:getEndpoint( Parse.OBJECT .. "/" .. objClass .. "/" .. objId ) 150 | return self:sendRequest( uri, objDataTable, Parse.OBJECT, Parse.PUT, _callback ) 151 | end 152 | 153 | ---Delete a data object. 154 | -- @string objClass The object class name. 155 | -- @string objId The object ID. 156 | -- @func[opt] _callback The callback function. 157 | -- @treturn int The network request ID. 158 | -- @usage 159 | -- local function onDeleteObject( event ) 160 | -- if not event.error then 161 | -- print( event.response.value ) 162 | -- end 163 | -- end 164 | -- parse:deleteObject( "MyClass", "objectId", onDeleteObject ) 165 | function Parse:deleteObject( objClass, objId, _callback ) 166 | local uri = Parse:getEndpoint( Parse.OBJECT .. "/" .. objClass .. "/" .. objId ) 167 | return self:sendRequest( uri, {}, Parse.OBJECT, Parse.DELETE, _callback ) 168 | end 169 | 170 | ---Get objects. 171 | -- @string objClass The object class name. 172 | -- @tab queryTable A table based query. 173 | -- @func[opt] _callback The callback function. 174 | -- @treturn int The network request ID. 175 | -- @usage 176 | -- local function onGetObjects( event ) 177 | -- if not event.error then 178 | -- print( #event.results ) 179 | -- --OR 180 | -- print( #events.response.results ) 181 | -- end 182 | -- end 183 | -- local queryTable = { 184 | -- ["where"] = { ["score"] = { ["$lte"] = 2000 } } 185 | -- } 186 | -- parse:getObjects( "MyClass", queryTable, onGetObjects ) 187 | function Parse:getObjects( objClass, queryTable, _callback ) 188 | queryTable = queryTable or {} 189 | local uri = Parse:getEndpoint( Parse.OBJECT .. "/" .. objClass ) 190 | return self:sendQuery( uri, queryTable, Parse.OBJECT, _callback ) 191 | end 192 | 193 | ---Link a data object to another object. 194 | -- @string parseObjectType The Parse object type. 195 | -- @string parseObjectId The Parse object ID. 196 | -- @string linkField The name of the Parse `Pointer` field. 197 | -- @string objTypeToLink The type of object that is being linked. 198 | -- @string parseObjIdToLink The object id of the object being linked. 199 | -- @func[opt] _callback The callback function. 200 | -- @treturn[1] int The network request ID. 201 | -- @treturn[2] nil No link was performed. 202 | -- @usage 203 | -- local function onLinkObject( event ) 204 | -- if not event.error then 205 | -- print( event.response.updatedAt ) 206 | -- end 207 | -- end 208 | -- parse:linkObject( parse.USER, "user-object-id", "stats", "PlayerStat", "player-stat-object-id", onLinkFile ) 209 | function Parse:linkObject( parseObjectType, parseObjectId, linkField, objTypeToLink, parseObjIdToLink, _callback ) 210 | local linkField = linkField 211 | local fileDataTable = { [linkField] = { ["className"] = objTypeToLink, ["objectId"] = parseObjIdToLink, ["__type"] = "Pointer" } } 212 | if parseObjectType == Parse.USER then 213 | return self:updateUser( parseObjectId, fileDataTable, _callback ) 214 | else 215 | return self:updateObject( parseObjectType, parseObjectId, fileDataTable, _callback ) 216 | end 217 | 218 | return nil 219 | end 220 | 221 | ---Unlink a data object from another object. 222 | -- @string parseObjectType The Parse object type. 223 | -- @string parseObjectId The Parse object ID. 224 | -- @string linkField The name the field with the link. 225 | -- @func[opt] _callback The callback function. 226 | -- @treturn[1] int The network request ID. 227 | -- @treturn[2] nil No link was performed. 228 | -- @usage 229 | -- local function onUnlinkObject( event ) 230 | -- if not event.error then 231 | -- print( event.response.updatedAt ) 232 | -- end 233 | -- end 234 | -- parse:unlinkObject( "Contact", "contact-object-id", "photo", onUnlinkObject ) 235 | function Parse:unlinkObject( parseObjectType, parseObjectId, linkField, _callback ) 236 | local linkField = linkField 237 | local fileDataTable = { [linkField] = json.null } 238 | if parseObjectType == Parse.USER then 239 | return self:updateUser( parseObjectId, fileDataTable, _callback ) 240 | else 241 | return self:updateObject( parseObjectType, parseObjectId, fileDataTable, _callback ) 242 | end 243 | 244 | return nil 245 | end 246 | 247 | ---Relations 248 | -- @section relations 249 | 250 | ---Create a relationship. 251 | -- @string objClass The object class name. 252 | -- @string objId The object ID. 253 | -- @string objField The Parse `Relation` field. 254 | -- @tab objDataTable The data table to attach. 255 | -- @func[opt] _callback The callback function. 256 | -- @treturn int The network request ID. 257 | -- @usage 258 | -- local function onAddRelation( event ) 259 | -- print( event.response.updatedAt ) 260 | -- end 261 | -- local dataTbl = { { ["className"] = "Post", ["objectId"] = "postObjectId" } } 262 | -- parse:createRelation( parse.USER, "userObjectId", "posts", dataTbl, onAddRelation ) 263 | function Parse:createRelation( objClass, objId, objField, objDataTable, _callback ) 264 | 265 | local uri 266 | if objClass == Parse.USER then 267 | uri = Parse:getEndpoint( Parse.USER .. "/" .. objId ) 268 | else 269 | uri = Parse:getEndpoint( Parse.OBJECT .. "/" .. objClass .. "/" .. objId ) 270 | end 271 | 272 | local objects = {} 273 | for r=1, #objDataTable do 274 | table.insert( objects, 275 | { ["__type"] = "Pointer", ["className"] = objDataTable[r].className, ["objectId"] = objDataTable[r].objectId } 276 | ) 277 | end 278 | 279 | local objField = objField 280 | local relationDataTable = { 281 | [ objField ] = { ["__op"] = "AddRelation", ["objects"] = objects } 282 | } 283 | 284 | return self:sendRequest( uri, relationDataTable, Parse.OBJECT, Parse.PUT, _callback ) 285 | end 286 | 287 | ---Remove a relationship. 288 | -- @string objClass The object class name. 289 | -- @string objId The object ID. 290 | -- @string objField The object field. 291 | -- @tab objDataTable The data table to remove. 292 | -- @func[opt] _callback The callback function. 293 | -- @treturn int The network request ID. 294 | -- @usage 295 | -- local function onRemoveRelation( event ) 296 | -- print( event.response.updatedAt ) 297 | -- end 298 | -- local dataTbl = { { ["className"] = "Post", ["objectId"] = "postObjectId" } } 299 | -- parse:removeRelation( parse.USER, "userObjectId", "posts", dataTbl, onRemoveRelation ) 300 | function Parse:removeRelation( objClass, objId, objField, objDataTable, _callback ) 301 | 302 | local uri 303 | if objClass == Parse.USER then 304 | uri = Parse:getEndpoint( Parse.USER .. "/" .. objId ) 305 | else 306 | uri = Parse:getEndpoint( Parse.OBJECT .. "/" .. objClass .. "/" .. objId ) 307 | end 308 | 309 | local objects = {} 310 | for r=1, #objDataTable do 311 | table.insert( objects, 312 | { ["__type"] = "Pointer", ["className"] = objDataTable[r].className, ["objectId"] = objDataTable[r].objectId } 313 | ) 314 | end 315 | 316 | local objField = objField 317 | local relationDataTable = { 318 | [ objField ] = { ["__op"] = "RemoveRelation", ["objects"] = objects } 319 | } 320 | 321 | return self:sendRequest( uri, relationDataTable, Parse.OBJECT, Parse.PUT, _callback ) 322 | end 323 | 324 | ---File 325 | -- @section files 326 | 327 | ---Upload a file. 328 | -- Supports jpg, png, gif, mp4, mov, m4v 329 | -- @tab fileMetaTable The file meta data table. 330 | -- @string fileMetaTable.fileName The file name. 331 | -- @param[opt=system.TemporaryDirectory] fileMetaTable.directory The base directory. 332 | -- @func[opt] _callback The callback function. 333 | -- @treturn int The network request ID. 334 | -- @usage 335 | -- local function onUpload( event ) 336 | -- if event.name == "parseResponse" then --uploaded 337 | -- print( event.response.name, event.response.url ) 338 | -- elseif event.name == "parseProgress" then --uploading 339 | -- print( event.bytesTransferred ) 340 | -- end 341 | -- end 342 | -- parse:uploadFile( { ["filename"] = "photo.png", ["baseDir"] = system.DocumentsDirectory }, onUpload ) 343 | function Parse:uploadFile( fileMetaTable, _callback ) 344 | 345 | --filename, directory 346 | assert( fileMetaTable.filename, "A filename is required in the meta table") 347 | 348 | --V 1.64 fix by Alexander Sheety 349 | local fileName = fileMetaTable.filename:gsub("%w*/","") 350 | local directory = fileMetaTable.baseDir or system.TemporaryDirectory 351 | 352 | --determine mime 353 | local contentType = self:getMimeType( fileName ) 354 | 355 | local fileParams = self:newFileParams( contentType ) 356 | 357 | local q = { 358 | requestId = network.upload( 359 | self.endpoint .. self.FILE .. "/" .. fileName, 360 | self.POST, 361 | function(e) self:onResponse(e); end, 362 | fileParams, 363 | fileName, 364 | directory, 365 | contentType 366 | ), 367 | requestType = self.FILE, 368 | _callback = _callback, 369 | } 370 | table.insert( self.requestQueue, q ) 371 | 372 | return q.requestId 373 | 374 | end 375 | 376 | --V1.5 fix by https://bitbucket.org/neilhannah - Thanks! 377 | 378 | ---Link a file to another object. 379 | -- @string parseObjectType The Parse object type. 380 | -- @string parseObjectId The Parse object ID. 381 | -- @string linkField The name of the linking field. 382 | -- @string parseFileUriToLink The Parse supplied URI to the file. 383 | -- @string parseFileUriToLinkUrl The Url to the file. 384 | -- @func[opt] _callback The callback function. 385 | -- @treturn[1] int The network request ID. 386 | -- @treturn[2] nil No file was linked. 387 | -- @usage 388 | -- local function onLinkFile( event ) 389 | -- if not event.error then 390 | -- print( event.response.updatedAt ) 391 | -- end 392 | -- end 393 | -- parse:linkFile( parse.USER, "user-object-id", "avatar", "1234567890abcdef-photo.png", onLinkFile ) 394 | function Parse:linkFile( parseObjectType, parseObjectId, linkField, parseFileUriToLink, parseFileUriToLinkUrl, _callback ) 395 | local linkField = linkField 396 | local fileDataTable = { [linkField] = { ["name"] = parseFileUriToLink, ["url"] = parseFileUriToLinkUrl, ["__type"] = "File" } } 397 | if parseObjectType == Parse.USER then 398 | return self:updateUser( parseObjectId, fileDataTable, _callback ) 399 | else 400 | return self:updateObject( parseObjectType, parseObjectId, fileDataTable, _callback ) 401 | end 402 | 403 | return nil 404 | end 405 | 406 | ---Unlink a file from another object. 407 | -- 408 | -- NOTE: This does not delete the file from Parse.com, you must do that seperatly. 409 | -- @string parseObjectType The name of the class that you want to unlink the resource from. 410 | -- @string parseObjectId The objectId of the class object you want to unlink the resource from. 411 | -- @string linkField The property (col) in the objClass that holds the link. 412 | -- @func[opt] _callback The callback function. 413 | -- @treturn[1] int The network request ID. 414 | -- @treturn[2] nil No file was linked. 415 | -- @usage 416 | -- local function onUnlinkFile( event ) 417 | -- if not event.error then 418 | -- print( event.response.updatedAt ) 419 | -- end 420 | -- end 421 | -- parse:unlinkFile( "Contact", "contact-object-id", "photo", onUnlinkFile ) 422 | function Parse:unlinkFile( parseObjectType, parseObjectId, linkField, _callback ) 423 | local linkField = linkField 424 | local fileDataTable = { [linkField] = json.null } 425 | if parseObjectType == Parse.USER then 426 | return self:updateUser( parseObjectId, fileDataTable, _callback ) 427 | else 428 | return self:updateObject( parseObjectType, parseObjectId, fileDataTable, _callback ) 429 | end 430 | 431 | return nil 432 | end 433 | 434 | ---Delete a file. 435 | -- This method is depreciated. **Do Not Use.** 436 | -- You should not disclose your master key in an application. 437 | function Parse:deleteFile( parseFileName, parseMasterKey, _callback ) 438 | assert( parseMasterKey, "Parse Master Key is required to delete a file.") 439 | local uri = Parse.endpoint .. Parse.FILE .. "/" .. parseFileName 440 | return self:sendRequest( uri, {}, Parse.FILE, Parse.DELETE, _callback, parseMasterKey ) 441 | end 442 | 443 | ---Parse API BATCH 444 | -- @todo Add the batch processing 445 | 446 | ---User 447 | -- @section User 448 | 449 | ---Create a new User object. 450 | -- @tab objDataTable The user data. 451 | -- @func[opt] _callback The callback function. 452 | -- @treturn int Returns a network ID. 453 | -- @usage 454 | -- local function onCreateUser( event ) 455 | -- if not event.error then 456 | -- print( event.response.createdAt ) 457 | -- end 458 | -- end 459 | -- local userDataTable = { ["username"] = "Chris", ["password"] = "strongpw" } 460 | -- parse:createUser( userDataTable, onCreateUser ) 461 | function Parse:createUser( objDataTable, _callback ) 462 | local uri = Parse:getEndpoint( Parse.USER ) 463 | return self:sendRequest( uri, objDataTable, Parse.USER, Parse.POST, _callback ) 464 | end 465 | 466 | ---Gets a User object. 467 | -- @string objId The User object ID. 468 | -- @func[opt] _callback The callback function. 469 | -- @treturn int Returns a network ID. 470 | -- @usage 471 | -- local function onGetUser( event ) 472 | -- if not event.error then 473 | -- print( event.response.username ) 474 | -- end 475 | -- end 476 | -- parse:getUser( "objectId", onGetUser ) 477 | function Parse:getUser( objId, _callback ) 478 | local uri = Parse:getEndpoint( Parse.USER .. "/" .. objId ) 479 | return self:sendRequest( uri, {}, Parse.USER, Parse.GET, _callback ) 480 | end 481 | 482 | ---Get Users. 483 | -- @tab queryTable A query table. 484 | -- @func[opt] _callback The callback function. 485 | -- @treturn int The network request ID. 486 | -- @usage 487 | -- local function onGetUsers( event ) 488 | -- if not event.error then 489 | -- print( #event.results ) 490 | -- --OR 491 | -- print( #events.response.results ) 492 | -- end 493 | -- end 494 | -- local queryTable = { 495 | -- ["where"] = { ["username"] = "Chris" }, 496 | -- ["limit"] = 5 497 | -- } 498 | -- parse:getUsers( queryTable, onGetUsers ) 499 | function Parse:getUsers( queryTable, _callback ) 500 | queryTable = queryTable or {} 501 | local uri = Parse:getEndpoint( Parse.USER ) 502 | return self:sendQuery( uri, queryTable, Parse.OBJECT, _callback ) 503 | end 504 | 505 | ---Log in a User. 506 | -- @tab objDataTable The data table. 507 | -- @func[opt] _callback The callback function. 508 | -- @treturn[1] int The network request ID. 509 | -- @treturn[2] nil User was not logged in. 510 | -- @usage 511 | -- local function onLoginUser( event ) 512 | -- if not event.error then 513 | -- print( event.response.sessionToken ) 514 | -- end 515 | -- end 516 | -- parse:loginUser( { ["username"] = "Chris", ["password"] = "strongpw" }, onLoginUser ) 517 | function Parse:loginUser( objDataTable, _callback ) 518 | local uri = nil 519 | 520 | if objDataTable.authData == nil then 521 | uri = Parse:getEndpoint( Parse.LOGIN ) 522 | return self:sendQuery( uri, objDataTable, Parse.LOGIN, _callback ) 523 | else --facebook/twitter/UUID login 524 | uri = Parse:getEndpoint( Parse.USER ) 525 | return self:sendRequest( uri, objDataTable, Parse.USER, Parse.POST, _callback ) 526 | end 527 | 528 | return nil 529 | end 530 | 531 | ---Update the logged in User. 532 | -- _MUST BE LOGGED IN FIRST WITH SESSION TOKEN_ 533 | -- @string objId The User object id. 534 | -- @tab objDataTable The object data table. 535 | -- @func[opt] _callback The callback function. 536 | -- @treturn int The network request ID. 537 | -- @usage 538 | --local function onUpdateUser( event ) 539 | -- if not event.error then 540 | -- print( event.response.updatedAt ) 541 | -- end 542 | --end 543 | --local dataTable = { ["password"] = "newpassword" } 544 | --parse:updateUser( "objectId", dataTable, onUpdateUser ) 545 | function Parse:updateUser( objId, objDataTable, _callback ) 546 | 547 | assert( self.sessionToken, "User must be logged in first, sessionToken cannot be nil.") 548 | 549 | local uri = Parse:getEndpoint( Parse.USER .. "/" .. objId ) 550 | return self:sendRequest( uri, objDataTable, Parse.USER, Parse.PUT, _callback ) 551 | end 552 | 553 | ---Get the logged in User. 554 | -- _MUST BE LOGGED IN FIRST WITH SESSION TOKEN_ 555 | -- @func[opt] _callback The callback function. 556 | -- @treturn int The network request ID. 557 | -- @usage 558 | -- local function onGetMe( event ) 559 | -- if event.code == parse.EXPIRED then 560 | -- print( "Session expired. Log in.") 561 | -- else 562 | -- print( "Hello", event.response.username ) 563 | -- end 564 | -- end 565 | -- parse:getUser( onGetMe ) 566 | function Parse:getMe( _callback ) 567 | 568 | assert( self.sessionToken, "User must be logged in first, sessionToken cannot be nil.") 569 | 570 | local uri = Parse:getEndpoint( Parse.USER .. "/me" ) 571 | return self:sendRequest( uri, {}, Parse.USER, Parse.GET, _callback ) 572 | end 573 | 574 | ---Delete the logged in User. 575 | -- _MUST BE LOGGED IN FIRST WITH SESSION TOKEN_ 576 | -- @string objId The object ID. 577 | -- @func[opt] _callback The callback function. 578 | -- @treturn int The network request ID. 579 | -- @usage 580 | -- local function onDeleteUser( event ) 581 | -- if not event.error then 582 | -- print( event.response.value ) 583 | -- end 584 | -- end 585 | -- parse:deleteUser( "objectId", onDeleteUser ) 586 | function Parse:deleteUser( objId, _callback ) 587 | 588 | assert( self.sessionToken, "User must be logged in first, sessionToken cannot be nil.") 589 | 590 | local uri = Parse:getEndpoint( Parse.USER .. "/" .. objId ) 591 | return self:sendRequest( uri, {}, Parse.USER, Parse.DELETE, _callback ) 592 | end 593 | 594 | ---Request a lost password reset. 595 | -- @string email The account email address. 596 | -- @func[opt] _callback The callback function. 597 | -- @treturn int The network request ID. 598 | -- @usage 599 | -- local function onRequestPassword( event ) 600 | -- if not event.error then 601 | -- print( event.response.value ) 602 | -- end 603 | -- end 604 | -- parse:requestPassword( "user@email.com", onRequestPassword ) 605 | function Parse:requestPassword( email, _callback ) 606 | local uri = Parse:getEndpoint( "requestPasswordReset" ) 607 | return self:sendRequest( uri, { ["email"] = email }, Parse.USER, Parse.POST, _callback ) 608 | end 609 | 610 | ---Analytics 611 | -- @section analytics 612 | 613 | ---Application opened event. 614 | -- @func[opt] _callback The callback function. 615 | -- @treturn int The network request ID. 616 | -- @usage 617 | -- local function onAppOpened( event ) 618 | -- if not event.error then 619 | -- print( event.response.value ) 620 | -- end 621 | -- end 622 | -- parse:appOpened( onAppOpened ) 623 | function Parse:appOpened( _callback ) 624 | local uri = Parse:getEndpoint( Parse.ANALYTICS .. "/AppOpened" ) 625 | local requestParams = {} 626 | return self:sendRequest( uri, { at = "" }, Parse.ANALYTICS, Parse.POST, _callback ) 627 | end 628 | 629 | ---Log a custom event. 630 | -- @string eventType The event type. 631 | -- @tab dimensionsTable The table of dimensions. 632 | -- @func[opt] _callback The callback function. 633 | -- @treturn int The network request ID. 634 | -- @usage 635 | -- local function onLogEvent( event ) 636 | -- if not event.error then 637 | -- print( event.response.value ) 638 | -- end 639 | -- end 640 | -- parse:logEvent( "Error", { ["type"] = "login" }, onLogEvent ) 641 | function Parse:logEvent( eventType, dimensionsTable, _callback ) 642 | dimensionsTable = dimensionsTable or {} 643 | 644 | local uri = Parse:getEndpoint( Parse.ANALYTICS .. "/" .. eventType ) 645 | local requestParams = { 646 | ["dimensions"] = dimensionsTable 647 | } 648 | return self:sendRequest( uri, requestParams, Parse.ANALYTICS, Parse.POST, _callback ) 649 | end 650 | 651 | ---Roles 652 | -- @section roles 653 | 654 | ---Create a new role. 655 | -- @tab objDataTable The object data. 656 | -- @func[opt] _callback The callback function. 657 | -- @treturn int The network request ID. 658 | -- @usage 659 | -- local function onCreateRole( event ) 660 | -- if not event.error then 661 | -- print( event.response.createdAt ) 662 | -- end 663 | -- end 664 | -- local roleDataTable = { ["name"] = "Admins", ["ACL"] = { ["*"] = { ["read"] = true } } } 665 | -- parse:createRole( roleDataTable, onCreateRole ) 666 | function Parse:createRole( objDataTable, _callback ) 667 | local uri = Parse:getEndpoint( Parse.ROLE ) 668 | return self:sendRequest( uri, objDataTable, Parse.ROLE, Parse.POST, _callback ) 669 | end 670 | 671 | ---Retrieve a role. 672 | -- @string objId The object ID. 673 | -- @func[opt] _callback The callback function. 674 | -- @treturn int The network request ID. 675 | -- @usage 676 | -- local function onGetRole( event ) 677 | -- if not event.error then 678 | -- print( event.response.ACL["*"]["read"] ) 679 | -- end 680 | -- end 681 | -- parse:getRole( "objectId", onGetRole ) 682 | function Parse:getRole( objId, _callback ) 683 | local uri = Parse:getEndpoint( Parse.ROLE .. "/" .. objId ) 684 | return self:sendRequest( uri, {}, Parse.ROLE, Parse.GET, _callback ) 685 | end 686 | 687 | ---Push 688 | -- @section push 689 | 690 | -- Special thanks Ed Moyse https://bitbucket.org/edmoyse. 691 | 692 | ---Send a push message. 693 | -- 694 | -- __NOTE: This will only work on iOS devices.__ 695 | -- @tab objDataTable The object data table for the Push message. 696 | -- @func[opt] _callback The callback function. 697 | -- @treturn int The network request ID. 698 | -- @usage 699 | -- local function onSendPush(event) 700 | -- if not event.error then 701 | -- print(event.response) 702 | -- end 703 | -- end 704 | -- local pushDataTable = { 705 | -- ["where"] = {["channels"] = "", ["deviceType"] = "ios", ["userId"] = "objectId"}, 706 | -- ["data"] = {["alert"] = "Collect your FREE cookies!"} 707 | -- } 708 | -- parse:sendPush( pushDataTable, onSendPush ) 709 | function Parse:sendPush(objDataTable, _callback) 710 | local uri = Parse:getEndpoint( Parse.PUSH ) 711 | return self:sendRequest( uri, objDataTable, Parse.PUSH, Parse.POST, _callback ) 712 | end 713 | 714 | ---Installations 715 | -- @section installations 716 | 717 | ---Create a new installation. 718 | -- @tab objDataTable The data table. 719 | -- @func[opt] _callback The callback function. 720 | -- @treturn int The network request ID. 721 | -- @usage 722 | -- local function onInstallation( event ) 723 | -- if not event.error then 724 | -- print( event.response.value ) 725 | -- end 726 | -- end 727 | -- local installationDataTable = { 728 | -- ["deviceType"] = "ios", 729 | -- ["deviceToken"] = "device-token", 730 | -- ["channels"] = { "" }, 731 | -- } 732 | -- parse:createInstallation( installationDataTable, onInstallation ) 733 | function Parse:createInstallation( objDataTable, _callback ) 734 | local uri = Parse:getEndpoint( Parse.INSTALLATION ) 735 | return self:sendRequest( uri, objDataTable, Parse.INSTALLATION, Parse.POST, _callback ) --returns requestId 736 | end 737 | 738 | ---Update an installation. 739 | -- @string objId The object ID. 740 | -- @tab objDataTable The data table. 741 | -- @func[opt] _callback The callback function. 742 | -- @treturn int The network request ID. 743 | -- @usage 744 | -- local function onUpdateInstallation( event ) 745 | -- if not event.error then 746 | -- print( event.response.value ) 747 | -- end 748 | -- end 749 | -- local installationDataTable = { 750 | -- ["channels"] = { "aNewChannel" }, 751 | -- } 752 | -- parse:updateInstallation( installationId, installationDataTable, onUpdateInstallation ) 753 | function Parse:updateInstallation( objId, objDataTable, _callback ) 754 | local uri = Parse:getEndpoint( Parse.INSTALLATION .. "/" .. objId ) 755 | return self:sendRequest( uri, objDataTable, Parse.INSTALLATION, Parse.PUT, _callback ) --returns requestId 756 | end 757 | 758 | ---Get an installation. 759 | -- @string objId The object ID. 760 | -- @func[opt] _callback The callback function. 761 | -- @treturn int The network request ID. 762 | function Parse:getInstallation( objId, _callback ) 763 | local uri = Parse:getEndpoint( Parse.INSTALLATION .. "/" .. objId ) 764 | return self:sendRequest( uri, {}, Parse.INSTALLATION, Parse.GET, _callback ) --returns requestId 765 | end 766 | 767 | ---Cloud 768 | -- @section cloud 769 | 770 | ---Run cloud code. 771 | -- @string functionName The function name. 772 | -- @tab functionParams A table of parameters. 773 | -- @func[opt] _callback The callback function. 774 | -- @treturn int The network request ID. 775 | -- @usage 776 | -- local function onRun( event ) 777 | -- if not event.error then 778 | -- print( event.response.value ) 779 | -- end 780 | -- end 781 | -- parse:run( "Hello", { ["name"] = "Chris" }, onRun ) 782 | function Parse:run( functionName, functionParams, _callback ) 783 | functionParams = functionParams or {[""] = ""} 784 | 785 | local uri = Parse:getEndpoint( Parse.CLOUD .. "/" .. functionName ) 786 | return self:sendRequest( uri, functionParams, Parse.CLOUD, Parse.POST, _callback ) --returns requestId 787 | end 788 | 789 | --------------------------------------------------------------------- 790 | -- Parse Module Internals 791 | --------------------------------------------------------------------- 792 | 793 | ---Build request parameters 794 | -- @local 795 | function Parse:buildRequestParams( withDataTable, masterKey ) 796 | local postData = json.encode( withDataTable ) 797 | return self:newRequestParams( postData, masterKey ) --for use in a network request 798 | end 799 | 800 | function Parse:sendRequest( uri, requestParamsTbl, requestType, action, _callback, masterKey ) 801 | local requestParams = self:buildRequestParams( requestParamsTbl, masterKey ) 802 | 803 | requestType = requestType or Parse.NIL 804 | action = action or Parse.POST 805 | 806 | local q = { 807 | requestId = network.request( uri, action, function(e) Parse:onResponse(e); end, requestParams ), 808 | requestType = requestType, 809 | _callback = _callback, 810 | } 811 | table.insert( self.requestQueue, q ) 812 | 813 | return q.requestId 814 | end 815 | 816 | -- QUERIES -- 817 | function Parse:buildQueryParams( withQueryTable ) 818 | local uri = "" 819 | for key, v in pairs( withQueryTable ) do 820 | if uri ~= "" then 821 | uri = uri .. "&" 822 | end 823 | 824 | local value = v 825 | if key == "where" then 826 | value = url.escape( json.encode( v ) ) 827 | end 828 | 829 | uri = uri .. tostring( key ) .. "=" .. value 830 | 831 | end 832 | return self:newRequestParams( uri ) --for use in a network request 833 | end 834 | 835 | function Parse:sendQuery( uri, queryParamsTbl, requestType, _callback ) 836 | local requestParams = self:buildQueryParams( queryParamsTbl ) 837 | 838 | requestType = requestType or Parse.NIL 839 | --action = action or Parse.GET 840 | 841 | local queryUri = uri .. "?" .. requestParams.body 842 | queryUri = string.gsub( queryUri, "%s+", '%20' ) 843 | 844 | local q = { requestId = network.request( queryUri, Parse.GET, function(e) Parse:onResponse(e); end, requestParams ), 845 | requestType = requestType, 846 | _callback = _callback, 847 | } 848 | table.insert( self.requestQueue, q ) 849 | 850 | return q.requestId 851 | end 852 | 853 | -- FILES -- 854 | function Parse:buildFileParams( withDataTable ) 855 | local postData = json.encode( withDataTable ) 856 | return self:newRequestParams( postData ) --for use in a network request 857 | end 858 | 859 | function Parse:sendFile( uri, requestParamsTbl, requestType, action ) 860 | local requestParams = self:buildRequestParams( requestParamsTbl ) 861 | 862 | requestType = requestType or Parse.NIL 863 | action = action or Parse.POST 864 | 865 | local q = { requestId = network.request( uri, action, function(e) Parse:onResponse(e); end, requestParams ), requestType = requestType } 866 | table.insert( self.requestQueue, q ) 867 | 868 | return q.requestId 869 | end 870 | 871 | ---Session 872 | -- @section session 873 | 874 | ---Set the Parse provided sessionToken for all future calls that require it. 875 | -- 876 | -- NOTE: The sessionToken is automatically set when you log a user in through Parse. 877 | -- @string sessionToken The session token ID. 878 | -- @treturn string The session token. 879 | -- @usage 880 | -- parse:setSessionToken( sessionToken ) 881 | function Parse:setSessionToken( sessionToken ) 882 | self.sessionToken = sessionToken 883 | return self.sessionToken 884 | end 885 | 886 | ---Returns the Parse sessionToken that is currently set. 887 | -- @treturn string sessionToken The session token. 888 | -- @usage 889 | -- local sessionToken = parse:getSessionToken() 890 | function Parse:getSessionToken() 891 | return self.sessionToken 892 | end 893 | 894 | ---Clears the Parse sessionToken. 895 | -- 896 | -- NOTE: This does not clear or reset a user session with Parse, it only clears the sessionToken internally, in case you need to apply a new sessionToken. 897 | -- @usage 898 | -- parse:clearSessionToken() 899 | function Parse:clearSessionToken() 900 | self.sessionToken = nil 901 | end 902 | 903 | --======================================================================-- 904 | --== RESPONSE 905 | --======================================================================-- 906 | function Parse:_debugOutput( e ) 907 | --== Show JSON flag 908 | if self.showJSON then 909 | if e.response ~= nil then 910 | print( json.encode( e.response ) ) 911 | end 912 | end 913 | --== Show Status flag 914 | if self.showStatus then 915 | if e ~= nil then 916 | if type(e) == 'table' then 917 | self:printTable( e ) 918 | else 919 | print('non-table response') 920 | end 921 | end 922 | end 923 | --== Show Alert flag 924 | if self.showAlert then 925 | local msg = string.format( "Net Status: %d \n", e.httpStatusCode ) 926 | --check error 927 | if e.error then 928 | msg = msg .. string.format( "Parse Code: %d \n", e.code ) 929 | if e.error then 930 | msg = msg .. string.format( "Error: %s", e.error ) 931 | end 932 | else 933 | msg = "Parse action was successful!" 934 | end 935 | 936 | native.showAlert( "Parsed!", msg, { "OK" } ) 937 | end 938 | 939 | end 940 | 941 | --== Parse response handler 942 | --== proceed with caution... 943 | function Parse:onResponse( event ) 944 | if event.phase == "ended" then 945 | 946 | -- Table to hold event response data 947 | local response_data = 948 | { 949 | bytesEstimated = event.bytesEstimated, 950 | bytesTransferred = event.bytesTransferred, 951 | isError = event.isError, --Network error 952 | requestId = event.requestId, 953 | response = event.response, 954 | responseHeaders = event.responseHeaders, 955 | responseType = event.responseType, 956 | status = event.status, --Network call status 957 | url = event.url --The original requested url 958 | } 959 | 960 | -- Table for return_data, initialized 961 | local return_data = 962 | { 963 | --Name of the return event 964 | name = "parseResponse", 965 | --The Parse request "type" - Parse.USER, etc. 966 | requestType = nil, --Set later 967 | 968 | response = nil, --Set later 969 | results = nil, --Set later (maybe) 970 | 971 | headers = response_data.responseHeaders or {}, --Incoming headers 972 | 973 | code = nil, --Parse response code (-1 if no network) 974 | error = nil, -- String final error code, can pass Parse errors 975 | 976 | networkError = response_data.isError, -- 404 is not a networkError (Bool) 977 | networkBytesTransferred = response_data.bytesTransferred or 0, 978 | 979 | httpStatusCode = response_data.status or 0 -- http status code 980 | } 981 | 982 | --check showStatusHeaders flag 983 | if self.showStatusHeaders == false then 984 | return_data.headers = nil; 985 | end 986 | 987 | -- Start working with the response 988 | -- first checking for Parse errors 989 | local HAS_ERROR = false 990 | if response_data and return_data then 991 | -- Make sure we something to work with, or else. 992 | assert(response_data, "response_data table missing.") 993 | assert(return_data, "return_data table missing.") 994 | 995 | local response_chk = json.decode( response_data.response ) 996 | 997 | --Check for Parse error in decoded response 998 | if response_chk and type( response_chk ) == 'table' then 999 | if response_chk.error then 1000 | return_data.error = response_chk.error 1001 | end 1002 | -- Probably has a code too. 1003 | if response_chk.code then 1004 | return_data.code = response_chk.code 1005 | end 1006 | end -- end response check if 1007 | end 1008 | 1009 | -- Do we have an error? 1010 | if return_data.error then 1011 | HAS_ERROR = true --doh! 1012 | end 1013 | 1014 | -- If error, skip the response and 1015 | -- results since we got hosed anyway. 1016 | if HAS_ERROR == false then 1017 | 1018 | --Transfer a 'tabled' response from Parse response 1019 | if response_data.response ~= nil then 1020 | return_data.response = json.decode( response_data.response ) 1021 | end 1022 | 1023 | -- Check 'resultsInResponse' flag. 1024 | -- false means do not place the multi 1025 | -- 'results' in the 'response' object. 1026 | if self.resultsInResponse == false then 1027 | if return_data.response ~= nil then 1028 | if return_data.response.results then --we have a 'results' key 1029 | return_data.results = return_data.response.results 1030 | return_data.response.results = nil 1031 | end 1032 | end 1033 | end 1034 | end 1035 | 1036 | --== Debug output 1037 | self:_debugOutput( return_data ) 1038 | 1039 | --== Start preparing to respond 1040 | local request_id = response_data.requestId 1041 | 1042 | local _callback = nil 1043 | 1044 | for r=1, #self.requestQueue do 1045 | local req = self.requestQueue[ r ] 1046 | if req.requestId == request_id then 1047 | --Add the Parse request type 1048 | return_data.requestType = req.requestType 1049 | --set session if log in 1050 | if return_data.requestType == 'login' then 1051 | if return_data.response then 1052 | if return_data.response.sessionToken then 1053 | self.sessionToken = return_data.response.sessionToken 1054 | else 1055 | self.sessionToken = nil 1056 | end 1057 | end 1058 | end 1059 | --Set up callback 1060 | _callback = req._callback 1061 | --Remove request 1062 | table.remove( self.requestQueue, r ) 1063 | --see ya. 1064 | break 1065 | end 1066 | end 1067 | 1068 | --tidy up 1069 | response_data = nil 1070 | 1071 | --== Send Response 1072 | if return_data.name == 'parseResponse' then 1073 | if _callback then 1074 | _callback( return_data ) 1075 | else --use global 1076 | self.dispatcher:dispatchEvent( return_data ) 1077 | end 1078 | end 1079 | 1080 | --======================================================================-- 1081 | --== Progress event - uploading 1082 | --======================================================================-- 1083 | elseif event.phase == "progress" then --files 1084 | 1085 | local status = event.status or nil 1086 | local requestId = event.requestId or 0 1087 | local bytesTransferred = event.bytesTransferred or 0 1088 | local url = event.url or "" 1089 | 1090 | local _callback = nil 1091 | 1092 | for r=1, #self.requestQueue do 1093 | local request = self.requestQueue[ r ] 1094 | if request.requestId == requestId then 1095 | _callback = request._callback 1096 | break 1097 | end 1098 | end 1099 | 1100 | if _callback then 1101 | local e = { 1102 | name = "parseProgress", 1103 | requestId = requestId, 1104 | response = nil, 1105 | status = status, 1106 | bytesTransferred = bytesTransferred 1107 | } 1108 | _callback( e ) 1109 | end 1110 | end 1111 | end -- end-if Parse:onResponse 1112 | 1113 | function Parse:newRequestParams( bodyData, masterKey ) 1114 | --set up headers 1115 | local headers = {} 1116 | headers["X-Parse-Application-Id"] = self.appId 1117 | headers["X-Parse-REST-API-Key"] = self.apiKey 1118 | 1119 | --session? 1120 | if self.sessionToken then 1121 | headers["X-Parse-Session-Token"] = self.sessionToken 1122 | end 1123 | 1124 | --masterkey? 1125 | if masterKey then 1126 | headers["X-Parse-Master-Key"] = masterKey 1127 | end 1128 | 1129 | headers["Content-Type"] = "application/json" 1130 | 1131 | --populate parameters for the network call 1132 | local requestParams = {} 1133 | requestParams.headers = headers 1134 | requestParams.body = bodyData 1135 | 1136 | return requestParams 1137 | end 1138 | 1139 | -- FILE PARAMS 1140 | function Parse:newFileParams( contentType ) 1141 | --set up headers 1142 | local headers = {} 1143 | headers["X-Parse-Application-Id"] = self.appId 1144 | headers["X-Parse-REST-API-Key"] = self.apiKey 1145 | 1146 | local requestParams = {} 1147 | 1148 | headers["Content-Type"] = contentType 1149 | 1150 | --populate parameters for the network call 1151 | requestParams = {} 1152 | requestParams.headers = headers 1153 | requestParams.bodyType = "binary" 1154 | requestParams.progress = true 1155 | 1156 | return requestParams 1157 | end 1158 | 1159 | function Parse:getEndpoint( typeConstant ) 1160 | return self.endpoint .. typeConstant 1161 | end 1162 | 1163 | function Parse:cancelRequest( requestId ) 1164 | network.cancel( requestId ) 1165 | end 1166 | 1167 | function Parse:getMimeType( filePath ) 1168 | 1169 | local path = string.lower( filePath ) 1170 | local mime = nil 1171 | 1172 | if string.find( path, ".txt" ) ~= nil then 1173 | mime = self.TEXT 1174 | elseif string.find( path, ".jpg" ) ~= nil then 1175 | mime = self.JPG 1176 | elseif string.find( path, ".jpeg" ) ~= nil then 1177 | mime = self.JPG 1178 | elseif string.find( path, ".png" ) ~= nil then 1179 | mime = self.PNG 1180 | elseif string.find( path, ".mov" ) ~= nil then 1181 | mime = self.MOV 1182 | elseif string.find( path, ".mp4" ) ~= nil then 1183 | mime = self.MP4 1184 | elseif string.find( path, ".m4v" ) ~= nil then 1185 | mime = self.M4V 1186 | end 1187 | 1188 | return mime 1189 | end 1190 | 1191 | function Parse:timestampToISODate( unixTimestamp ) 1192 | --2013-12-03T19:01:25Z" 1193 | unixTimestamp = unixTimestamp or os.time() 1194 | return os.date( "!%Y-%m-%dT%H:%M:%SZ", unixTimestamp ) 1195 | end 1196 | 1197 | function Parse:printTable( t, indent ) 1198 | -- print contents of a table, with keys sorted. second parameter is optional, used for indenting subtables 1199 | local names = {} 1200 | if not indent then indent = "" end 1201 | for n,g in pairs(t) do 1202 | table.insert(names,n) 1203 | end 1204 | table.sort(names) 1205 | for i,n in pairs(names) do 1206 | local v = t[n] 1207 | if type(v) == "table" then 1208 | if(v==t) then -- prevent endless loop if table contains reference to itself 1209 | print(indent..tostring(n)..": <-") 1210 | else 1211 | print(indent..tostring(n)..":") 1212 | Parse:printTable(v,indent.." ") 1213 | end 1214 | else 1215 | if type(v) == "function" then 1216 | print(indent..tostring(n).."()") 1217 | else 1218 | print(indent..tostring(n)..": "..tostring(v)) 1219 | end 1220 | end 1221 | end 1222 | end 1223 | 1224 | function Parse:init( o ) 1225 | self.appId = o.appId 1226 | self.apiKey = o.apiKey 1227 | end 1228 | 1229 | return Parse 1230 | --------------------------------------------------------------------------------