The REST API lets you interact with Plugy from anything that can send an HTTP request. There are many things you can do with the REST API. For example:
All API access is over HTTPS, and accessed via the http://api.plugy.com
domain. The relative path prefix /1/
indicates that we are currently using version 1 of the API.
| URL | HTTP Verb | Functionality |
|---|---|---|
/1/classes/<className> |
POST | Creating Entity |
/1/classes/<className>/<EntityId> |
GET | Retrieving Entity |
/1/classes/<className>/<EntityId> |
PUT | UpdatingO Entity |
/1/classes/<className> |
GET | Queries |
/1/classes/<className>/<EntityId> |
DELETE | Deleting Entity |
| URL | HTTP Verb | Functionality |
|---|---|---|
/1/users |
POST |
Signing Up
Linking Users |
/1/login |
GET | Logging In |
/1/users/<EntityId> |
GET | Retrieving Users |
/1/users/me |
GET |
Validating Session Tokens
Retrieving Current User |
/1/users/<EntityId> |
PUT |
Updating Users
Linking Users Verifying Emails |
/1/users |
GET | Querying Users |
/1/users/<EntityId> |
DELETE | Deleting Users |
| URL | HTTP Verb | Functionality |
|---|---|---|
/1/files/<fileName> |
POST | Uploading Files |
| URL | HTTP Verb | Functionality |
|---|---|---|
/1/push |
POST | Push Notifications |
| URL | HTTP Verb | Functionality |
|---|---|---|
/1/installations |
POST | Uploading Installation Data |
/1/installations/<EntityId> |
GET | Retrieving Installations |
/1/installations/<EntityId> |
PUT | Updating Installations |
/1/installations |
GET | Querying Installations |
/1/installations/<EntityId> |
DELETE | Deleting Installations |
For POST and PUT requests,
the request body must be JSON,
with the Content-Type
header
set to application/json
.
Authentication is done via HTTP headers.
The Application-Id
header identifies which application you are accessing,
and the REST-API-Key
header authenticates the endpoint.
In the examples with curl
that follow,
the headers are stored in shell variables APPLICATION_ID
and REST_API_KEY
,
so to follow along in the terminal,
export these variables:
export APPLICATION_ID="your application id" export REST_API_KEY="your REST API key"
The response format for all requests is a JSON object.
Whether a request succeeded is indicated by the HTTP status code. A 2xx status code indicates success, whereas a 4xx status code indicates failure. When a request fails, the response body is still JSON, but always contains the fields code
and error
which you can inspect to use for debugging. For example, trying to save an object with invalid keys will return the message:
{ "code": 105, "error": "invalid field name: bl!ng" }
Storing data through the Plugy REST API is built around a JSON encoding of the object's data. This data is schemaless, which means that you don't need to specify ahead of time what keys exist on each object. You simply set whatever key-value pairs you want, and the backend will store it.
For example, let's say you're tracking high scores for a game. A single object could contain:
{ "score": 1337, "playerName": "Sean Plott", "cheatMode": false }
Keys must be alphanumeric strings. Values can be anything that can be JSON-encoded.
Each object has a class name that you can use to distinguish different sorts of data. For example, we could call the high score object a GameScore
. We recommend that you NameYourClassesLikeThis and nameYourKeysLikeThis, just to keep your code looking pretty.
When you retrieve objects from Plugy, some fields are automatically added: createdAt
, updatedAt
, and EntityId
. These field names are reserved, so you cannot set them yourself. The object above could look like this when retrieved:
{ "score": 1337, "playerName": "Sean Plott", "cheatMode": false, "createdAt": "2011-08-20T02:06:57.931Z", "updatedAt": "2011-08-20T02:06:57.931Z", "_id": "Ed1nuqPvcm" }
createdAt
and updatedAt
are UTC timestamps stored in ISO 8601 format with millisecond precision: YYYY-MM-DDTHH:MM:SS.MMMZ
. EntityId
is a string unique to this class that identifies this object.
In the REST API, the class-level operations operate on a resource based on just the class name. For example, if the class name is GameScore
, the class URL is:
http://api.plugy.io/1/classes/GameScore
Users have a special class-level url:
http://api.plugy.io/1/users
The operations specific to a single object are available a nested URL. For example, operations specific to the GameScore
above with EntityId
equal to Ed1nuqPvcm
would use the object URL:
http://api.plugy.io/1/classes/GameScore/Ed1nuqPvcm
To create a new object on Plugy, send a POST request to the class URL containing the contents of the object. For example, to create the object described above:
import json,httplib connection = httplib.HTTPSConnection('api.plugy.io', 80) connection.connect() connection.request('POST', '/1/classes/GameScore', json.dumps({ "score": 1337, "playerName": "Sean Plott", "cheatMode": False }), { "Application-Id": "${APPLICATION_ID}", "REST-API-Key": "${REST_API_KEY}", "Content-Type": "application/json" }) result = json.loads(connection.getresponse().read()) print result
curl -X POST \ -H "Application-Id: ${APPLICATION_ID}" \ -H "REST-API-Key: ${REST_API_KEY}" \ -H "Content-Type: application/json" \ -d '{"score":1337,"playerName":"Sean Plott","cheatMode":false}' \ http://api.plugy.io/1/classes/GameScore
When the creation is successful, the HTTP response is a 201 Created
and the Location
header contains the object URL for the new object:
Status: 201 Created Location: http://api.plugy.io/1/classes/GameScore/Ed1nuqPvcm
The response body is a JSON object containing the EntityId
and the createdAt
timestamp of the newly-created object:
{ "createdAt": "2011-08-20T02:06:57.931Z", "_id": "Ed1nuqPvcm" }
Once you've created an object, you can retrieve its contents by sending a GET request to the object URL returned in the location header. For example, to retrieve the object we created above:
import json,httplib connection = httplib.HTTPSConnection('api.plugy.io', 80) connection.connect() connection.request('GET', '/1/classes/GameScore/Ed1nuqPvcm', '', { "Application-Id": "${APPLICATION_ID}", "REST-API-Key": "${REST_API_KEY}" }) result = json.loads(connection.getresponse().read()) print result
curl -X GET \ -H "Application-Id: ${APPLICATION_ID}" \ -H "REST-API-Key: ${REST_API_KEY}" \ http://api.plugy.io/1/classes/GameScore/Ed1nuqPvcm
The response body is a JSON object containing all the user-provided fields, plus the createdAt
, updatedAt
, and EntityId
fields:
{ "score": 1337, "playerName": "Sean Plott", "cheatMode": false, "skills": [ "pwnage", "flying" ], "createdAt": "2011-08-20T02:06:57.931Z", "updatedAt": "2011-08-20T02:06:57.931Z", "_id": "Ed1nuqPvcm" }
When retrieving objects that have pointers to children, you can fetch child objects by using the include
option. For instance, to fetch the object pointed to by the "game" key:
import json,httplib,urllib connection = httplib.HTTPSConnection('api.plugy.io', 80) params = urllib.urlencode({"include":"game"}) connection.connect() connection.request('GET', '/1/classes/GameScore/Ed1nuqPvcm?%s' % params, '', { "Application-Id": "${APPLICATION_ID}", "REST-API-Key": "${REST_API_KEY}" }) result = json.loads(connection.getresponse().read()) print result
curl -X GET \ -H "Application-Id: ${APPLICATION_ID}" \ -H "REST-API-Key: ${REST_API_KEY}" \ -G \ --data-urlencode 'include=game' \ http://api.plugy.io/1/classes/GameScore/Ed1nuqPvcm
To change the data on an object that already exists, send a PUT request to the object URL. Any keys you don't specify will remain unchanged, so you can update just a subset of the object's data. For example, if we wanted to change the score field of our object:
import json,httplib connection = httplib.HTTPSConnection('api.plugy.io', 80) connection.connect() connection.request('PUT', '/1/classes/GameScore/Ed1nuqPvcm', json.dumps({ "score": 73453 }), { "Application-Id": "${APPLICATION_ID}", "REST-API-Key": "${REST_API_KEY}", "Content-Type": "application/json" }) result = json.loads(connection.getresponse().read()) print result
curl -X PUT \ -H "Application-Id: ${APPLICATION_ID}" \ -H "REST-API-Key: ${REST_API_KEY}" \ -H "Content-Type: application/json" \ -d '{"score":73453}' \ http://api.plugy.io/1/classes/GameScore/Ed1nuqPvcm
The response body is a JSON object containing just an updatedAt
field with the timestamp of the update.
{ "updatedAt": "2011-08-21T18:02:52.248Z" }
To help with storing counter-type data, Plugy provides the ability to atomically increment (or decrement) any number field. So, we can increment the score field like so:
import json,httplib connection = httplib.HTTPSConnection('api.plugy.io', 80) connection.connect() connection.request('PUT', '/1/classes/GameScore/Ed1nuqPvcm', json.dumps({ "score": { "__op": "Increment", "amount": 1 } }), { "Application-Id": "${APPLICATION_ID}", "REST-API-Key": "${REST_API_KEY}", "Content-Type": "application/json" }) result = json.loads(connection.getresponse().read()) print result
curl -X PUT \ -H "Application-Id: ${APPLICATION_ID}" \ -H "REST-API-Key: ${REST_API_KEY}" \ -H "Content-Type: application/json" \ -d '{"score":{"__op":"Increment","amount":1}}' \ http://api.plugy.io/1/classes/GameScore/Ed1nuqPvcm
To decrement the counter, use the Increment
operator with a negative number:
import json,httplib connection = httplib.HTTPSConnection('api.plugy.io', 80) connection.connect() connection.request('PUT', '/1/classes/GameScore/Ed1nuqPvcm', json.dumps({ "score": { "__op": "Increment", "amount": -1 } }), { "Application-Id": "${APPLICATION_ID}", "REST-API-Key": "${REST_API_KEY}", "Content-Type": "application/json" }) result = json.loads(connection.getresponse().read()) print result
curl -X PUT \ -H "Application-Id: ${APPLICATION_ID}" \ -H "REST-API-Key: ${REST_API_KEY}" \ -H "Content-Type: application/json" \ -d '{"score":{"__op":"Increment","amount":-1}}' \ http://api.plugy.io/1/classes/GameScore/Ed1nuqPvcm
To help with storing array data, there are three operations that can be used to atomically change an array field:
Add
appends the given array of objects to the end of an array field.AddUnique
adds only the given objects which aren't already contained in an array field to that field. The position of the insert is not guaranteed.Remove
removes all instances of each given object from an array field.Each method takes an array of objects to add or remove in the "objects" key. For example, we can add items to the set-like "skills" field like so:
import json,httplib connection = httplib.HTTPSConnection('api.plugy.io', 80) connection.connect() connection.request('PUT', '/1/classes/GameScore/Ed1nuqPvcm', json.dumps({ "skills": { "__op": "AddUnique", "objects": [ "flying", "kungfu" ] } }), { "Application-Id": "${APPLICATION_ID}", "REST-API-Key": "${REST_API_KEY}", "Content-Type": "application/json" }) result = json.loads(connection.getresponse().read()) print result
curl -X PUT \ -H "Application-Id: ${APPLICATION_ID}" \ -H "REST-API-Key: ${REST_API_KEY}" \ -H "Content-Type: application/json" \ -d '{"skills":{"__op":"AddUnique","objects":["flying","kungfu"]}}' \ http://api.plugy.io/1/classes/GameScore/Ed1nuqPvcm
To delete an object from the Plugy Cloud, send a DELETE request to its object URL. For example:
import json,httplib connection = httplib.HTTPSConnection('api.plugy.io', 80) connection.connect() connection.request('DELETE', '/1/classes/GameScore/Ed1nuqPvcm', '', { "Application-Id": "${APPLICATION_ID}", "REST-API-Key": "${REST_API_KEY}" }) result = json.loads(connection.getresponse().read()) print result
curl -X DELETE \ -H "Application-Id: ${APPLICATION_ID}" \ -H "REST-API-Key: ${REST_API_KEY}" \ http://api.plugy.io/1/classes/GameScore/Ed1nuqPvcm
You can delete a single field from an object by using the Delete
operation:
import json,httplib connection = httplib.HTTPSConnection('api.plugy.io', 80) connection.connect() connection.request('PUT', '/1/classes/GameScore/Ed1nuqPvcm', json.dumps({ "opponents": { "__op": "Delete" } }), { "Application-Id": "${APPLICATION_ID}", "REST-API-Key": "${REST_API_KEY}", "Content-Type": "application/json" }) result = json.loads(connection.getresponse().read()) print result
curl -X PUT \ -H "Application-Id: ${APPLICATION_ID}" \ -H "REST-API-Key: ${REST_API_KEY}" \ -H "Content-Type: application/json" \ -d '{"opponents":{"__op":"Delete"}}' \ http://api.plugy.io/1/classes/GameScore/Ed1nuqPvcm
To reduce the amount of time spent on network round trips, you can create, update, or delete up to 50 objects in one call, using the batch endpoint.
Each command in a batch has method
, path
, and body
parameters that specify the HTTP command that would normally be used for that command. The commands are run in the order they are given. For example, to create a couple of GameScore
objects:
import json,httplib connection = httplib.HTTPSConnection('api.plugy.io', 80) connection.connect() connection.request('POST', '/1/batch', json.dumps({ "requests": [ { "method": "create", "class": "GameScore", "entity": { "score": 1337, "playerName": "Sean Plott" } }, { "method": "create", "class": "GameScore", "entity": { "score": 1337, "playerName": "Sean Plott" } } ] }), { "Application-Id": "${APPLICATION_ID}", "REST-API-Key": "${REST_API_KEY}", "Content-Type": "application/json" }) result = json.loads(connection.getresponse().read()) print result
curl -X POST \ -H "Application-Id: ${APPLICATION_ID}" \ -H "REST-API-Key: ${REST_API_KEY}" \ -H "Content-Type: application/json" \ -d '{ "requests": [ { "method": "create", "class": "GameScore", "entity": { "score": 1337, "playerName": "Sean Plott" } }, { "method": "create", "class": "GameScore", "entity": { "score": 1338, "playerName": "ZeroCool" } } ] }' \ http://api.plugy.io/1/batch
The response from batch will be a list with the same number of elements as the input list. Each item in the list with be a dictionary with either the success
or error
field set. The value of success
will be the normal response to the equivalent REST command:
{ "success": { "createdAt": "2012-06-15T16:59:11.276Z", "_id": "YAfSAWwXbL" } }
The value of error
will be an object with a numeric code
and error
string:
{ "error": { "code": 101, "error": "object not found for delete" } }
Other commands that work in a batch are update
and delete
.
import json,httplib connection = httplib.HTTPSConnection('api.plugy.io', 80) connection.connect() connection.request('POST', '/1/batch', json.dumps({ "requests": [ { "method": "create", "class": "GameScore", "entity": { "score": 1338, "playerName": "ZeroCool" } }, { "method": "delete", "class": "/1/classes/GameScore", "_id": "Cpl9lrueY5" } ] }), { "Application-Id": "${APPLICATION_ID}", "REST-API-Key": "${REST_API_KEY}", "Content-Type": "application/json" }) result = json.loads(connection.getresponse().read()) print result
curl -X POST \ -H "Application-Id: ${APPLICATION_ID}" \ -H "REST-API-Key: ${REST_API_KEY}" \ -H "Content-Type: application/json" \ -d '{ "requests": [ { "method": "create", "class": "GameScore", "_id": "Cpl9lrueY5", "entity": { "score": 999999 } }, { "method": "delete", "class": "/1/classes/GameScore", "_id": "Cpl9lrueY5" } ] }' \ http://api.plugy.io/1/batch
Note that N requests sent in a batch will still count toward your request limit as N requests.
So far we have only used values that can be encoded with standard JSON. The Plugy mobile client libraries also support dates, binary data, and relational data. In the REST API, these values are encoded as JSON hashes with the __type
field set to indicate their type, so you can read or write these fields if you use the correct encoding.
The Date
type contains a field iso
which contains a UTC timestamp stored in ISO 8601 format with millisecond precision: YYYY-MM-DDTHH:MM:SS.MMMZ
.
{ "__type": "Date", "iso": "2011-08-21T18:02:52.249Z" }
Dates are useful in combination with the built-in createdAt
and updatedAt
fields. For example, to retrieve objects created since a particular time, just encode a Date in a comparison query:
import json,httplib,urllib connection = httplib.HTTPSConnection('api.plugy.io', 80) params = urllib.urlencode({"where":json.dumps({ "createdAt": { "$gte": { "__type": "Date", "iso": "2011-08-21T18:02:52.249Z" } } })}) connection.connect() connection.request('GET', '/1/classes/GameScore?%s' % params, '', { "Application-Id": "${APPLICATION_ID}", "REST-API-Key": "${REST_API_KEY}" }) result = json.loads(connection.getresponse().read()) print result
curl -X GET \ -H "Application-Id: ${APPLICATION_ID}" \ -H "REST-API-Key: ${REST_API_KEY}" \ -G \ --data-urlencode 'where={"createdAt":{"$gte":{"__type":"Date","iso":"2011-08-21T18:02:52.249Z"}}}' \ http://api.plugy.io/1/classes/GameScore
The Bytes
type contains a field base64
which contains a base64 encoding of binary data. The specific base64 encoding is the one used by MIME, and does not contain whitespace.
{ "__type": "Bytes", "base64": "VGhpcyBpcyBhbiBlbmNvZGVkIHN0cmluZw==" }
The Pointer
type is used when mobile code sets a PFEntity
or PlugyEntity
as the value of another object. It contains the className
and EntityId
of the referred-to value.
{ "__type": "Pointer", "className": "GameScore", "_id": "Ed1nuqPvc" }
Pointers to user objects have a className
of _User
. Prefixing with an underscore is forbidden for developer-defined classes and signifies the class is a special built-in.
The Relation
type is used for many-to-many relations when the mobile uses PFRelation
or PlugyRelation
as a value. It has a className
that is the class name of the target objects.
{ "__type": "Relation", "className": "GameScore" }
When querying, Relation
objects behave like arrays of Pointers. Any operation that is valid for arrays of pointers (other than include
) works for Relation
objects.
When more data types are added, they will also be represented as hashes with a __type
field set, so you may not use this field yourself on JSON objects.
You can retrieve multiple objects at once by sending a GET request to the class URL. Without any URL parameters, this simply lists objects in the class:
import json,httplib connection = httplib.HTTPSConnection('api.plugy.io', 80) connection.connect() connection.request('GET', '/1/classes/GameScore', '', { "Application-Id": "${APPLICATION_ID}", "REST-API-Key": "${REST_API_KEY}" }) result = json.loads(connection.getresponse().read()) print result
curl -X GET \ -H "Application-Id: ${APPLICATION_ID}" \ -H "REST-API-Key: ${REST_API_KEY}" \ http://api.plugy.io/1/classes/GameScore
The return value is a JSON object that contains a results
field with a JSON array that lists the objects.
{ "results": [ { "playerName": "Jang Min Chul", "updatedAt": "2011-08-19T02:24:17.787Z", "cheatMode": false, "createdAt": "2011-08-19T02:24:17.787Z", "_id": "A22v5zRAgd", "score": 80075 }, { "playerName": "Sean Plott", "updatedAt": "2011-08-21T18:02:52.248Z", "cheatMode": false, "createdAt": "2011-08-20T02:06:57.931Z", "_id": "Ed1nuqPvcm", "score": 73453 } ] }
There are several ways to put constraints on the objects found, using the where
URL parameter. The value of the where
parameter should be encoded JSON. Thus, if you look at the actual URL requested, it would be JSON-encoded, then URL-encoded. The simplest use of the where
parameter is constraining the value for keys. For example, if we wanted to retrieve Sean Plott's scores that were not in cheat mode, we could do:
import json,httplib,urllib connection = httplib.HTTPSConnection('api.plugy.io', 80) params = urllib.urlencode({"where":json.dumps({ "playerName": "Sean Plott", "cheatMode": False })}) connection.connect() connection.request('GET', '/1/classes/GameScore?%s' % params, '', { "Application-Id": "${APPLICATION_ID}", "REST-API-Key": "${REST_API_KEY}" }) result = json.loads(connection.getresponse().read()) print result
curl -X GET \ -H "Application-Id: ${APPLICATION_ID}" \ -H "REST-API-Key: ${REST_API_KEY}" \ -G \ --data-urlencode 'where={"playerName":"Sean Plott","cheatMode":false}' \ http://api.plugy.io/1/classes/GameScore
The values of the where
parameter also support comparisons besides exact matching. Instead of an exact value, provide a hash with keys corresponding to the comparisons to do. The where
parameter supports these options:
| Key | Operation |
|---|---|
| $lt | Less Than |
| $lte | Less Than Or Equal To |
| $gt | Greater Than |
| $gte | Greater Than Or Equal To |
| $ne | Not Equal To |
| $in | Contained In |
| $nin | Not Contained in |
| $exists | A value is set for the key |
| $select | This matches a value for a key in the result of a different query |
| $dontSelect | Requires that a key's value not match a value for a key in the result of a different query |
| $all | Contains all of the given values |
For example, to retrieve scores between 1000 and 3000, including the endpoints, we could issue:
import json,httplib,urllib connection = httplib.HTTPSConnection('api.plugy.io', 80) params = urllib.urlencode({"where":json.dumps({ "score": { "$gte": 1000, "$lte": 3000 } })}) connection.connect() connection.request('GET', '/1/classes/GameScore?%s' % params, '', { "Application-Id": "${APPLICATION_ID}", "REST-API-Key": "${REST_API_KEY}" }) result = json.loads(connection.getresponse().read()) print result
curl -X GET \ -H "Application-Id: ${APPLICATION_ID}" \ -H "REST-API-Key: ${REST_API_KEY}" \ -G \ --data-urlencode 'where={"score":{"$gte":1000,"$lte":3000}}' \ http://api.plugy.io/1/classes/GameScore
To retrieve scores equal to an odd number below 10, we could issue:
import json,httplib,urllib connection = httplib.HTTPSConnection('api.plugy.io', 80) params = urllib.urlencode({"where":json.dumps({ "score": { "$in": [ 1, 3, 5, 7, 9 ] } })}) connection.connect() connection.request('GET', '/1/classes/GameScore?%s' % params, '', { "Application-Id": "${APPLICATION_ID}", "REST-API-Key": "${REST_API_KEY}" }) result = json.loads(connection.getresponse().read()) print result
curl -X GET \ -H "Application-Id: ${APPLICATION_ID}" \ -H "REST-API-Key: ${REST_API_KEY}" \ -G \ --data-urlencode 'where={"score":{"$in":[1,3,5,7,9]}}' \ http://api.plugy.io/1/classes/GameScore
To retrieve scores not by a given list of players we could issue:
import json,httplib,urllib connection = httplib.HTTPSConnection('api.plugy.io', 80) params = urllib.urlencode({"where":json.dumps({ "playerName": { "$nin": [ "Jonathan Walsh", "Dario Wunsch", "Shawn Simon" ] } })}) connection.connect() connection.request('GET', '/1/classes/GameScore?%s' % params, '', { "Application-Id": "${APPLICATION_ID}", "REST-API-Key": "${REST_API_KEY}" }) result = json.loads(connection.getresponse().read()) print result
curl -X GET \ -H "Application-Id: ${APPLICATION_ID}" \ -H "REST-API-Key: ${REST_API_KEY}" \ -G \ --data-urlencode 'where={"playerName":{"$nin":["Jonathan Walsh","Dario Wunsch","Shawn Simon"]}}' \ http://api.plugy.io/1/classes/GameScore
To retrieve documents with the score set, we could issue:
import json,httplib,urllib connection = httplib.HTTPSConnection('api.plugy.io', 80) params = urllib.urlencode({"where":json.dumps({ "score": { "$exists": True } })}) connection.connect() connection.request('GET', '/1/classes/GameScore?%s' % params, '', { "Application-Id": "${APPLICATION_ID}", "REST-API-Key": "${REST_API_KEY}" }) result = json.loads(connection.getresponse().read()) print result
curl -X GET \ -H "Application-Id: ${APPLICATION_ID}" \ -H "REST-API-Key: ${REST_API_KEY}" \ -G \ --data-urlencode 'where={"score":{"$exists":true}}' \ http://api.plugy.io/1/classes/GameScore
To retrieve documents without the score set, we could issue:
import json,httplib,urllib connection = httplib.HTTPSConnection('api.plugy.io', 80) params = urllib.urlencode({"where":json.dumps({ "score": { "$exists": False } })}) connection.connect() connection.request('GET', '/1/classes/GameScore?%s' % params, '', { "Application-Id": "${APPLICATION_ID}", "REST-API-Key": "${REST_API_KEY}" }) result = json.loads(connection.getresponse().read()) print result
curl -X GET \ -H "Application-Id: ${APPLICATION_ID}" \ -H "REST-API-Key: ${REST_API_KEY}" \ -G \ --data-urlencode 'where={"score":{"$exists":false}}' \ http://api.plugy.io/1/classes/GameScore
If you have a class containing sports teams and you store a user's hometown in the user class, you can issue one query to find the list of users whose hometown teams have winning records. The query would look like:
import json,httplib,urllib connection = httplib.HTTPSConnection('api.plugy.io', 80) params = urllib.urlencode({"where":json.dumps({ "hometown": { "$select": { "query": { "className": "Team", "where": { "winPct": { "$gt": 0.5 } } }, "key": "city" } } })}) connection.connect() connection.request('GET', '/1/classes/_User?%s' % params, '', { "Application-Id": "${APPLICATION_ID}", "REST-API-Key": "${REST_API_KEY}" }) result = json.loads(connection.getresponse().read()) print result
curl -X GET \ -H "Application-Id: ${APPLICATION_ID}" \ -H "REST-API-Key: ${REST_API_KEY}" \ -G \ --data-urlencode 'where={"hometown":{"$select":{"query":{"className":"Team","where":{"winPct":{"$gt":0.5}}},"key":"city"}}}' \ http://api.plugy.io/1/classes/_User
You can use the order
parameter to specify a field to sort by. Prefixing with a negative sign reverses the order. Thus, to retrieve scores in ascending order:
import json,httplib,urllib connection = httplib.HTTPSConnection('api.plugy.io', 80) params = urllib.urlencode({"order":"score"}) connection.connect() connection.request('GET', '/1/classes/GameScore?%s' % params, '', { "Application-Id": "${APPLICATION_ID}", "REST-API-Key": "${REST_API_KEY}" }) result = json.loads(connection.getresponse().read()) print result
curl -X GET \ -H "Application-Id: ${APPLICATION_ID}" \ -H "REST-API-Key: ${REST_API_KEY}" \ -G \ --data-urlencode 'order=score' \ http://api.plugy.io/1/classes/GameScore
And to retrieve scores in descending order:
import json,httplib,urllib connection = httplib.HTTPSConnection('api.plugy.io', 80) params = urllib.urlencode({"order":"-score"}) connection.connect() connection.request('GET', '/1/classes/GameScore?%s' % params, '', { "Application-Id": "${APPLICATION_ID}", "REST-API-Key": "${REST_API_KEY}" }) result = json.loads(connection.getresponse().read()) print result
curl -X GET \ -H "Application-Id: ${APPLICATION_ID}" \ -H "REST-API-Key: ${REST_API_KEY}" \ -G \ --data-urlencode 'order=-score' \ http://api.plugy.io/1/classes/GameScore
You can sort by multiple fields by passing order
a comma-separated list. To retrieve documents that are ordered by scores in ascending order and the names in descending order:
import json,httplib,urllib connection = httplib.HTTPSConnection('api.plugy.io', 80) params = urllib.urlencode({"order":"score,-name"}) connection.connect() connection.request('GET', '/1/classes/GameScore?%s' % params, '', { "Application-Id": "${APPLICATION_ID}", "REST-API-Key": "${REST_API_KEY}" }) result = json.loads(connection.getresponse().read()) print result
curl -X GET \ -H "Application-Id: ${APPLICATION_ID}" \ -H "REST-API-Key: ${REST_API_KEY}" \ -G \ --data-urlencode 'order=score,-name' \ http://api.plugy.io/1/classes/GameScore
You can use the limit
and skip
parameters for pagination. limit
defaults to 100, but anything from 1 to 1000 is a valid limit. Thus, to retrieve 200 objects after skipping the first 400:
import json,httplib,urllib connection = httplib.HTTPSConnection('api.plugy.io', 80) params = urllib.urlencode({"limit":200,"skip":400}) connection.connect() connection.request('GET', '/1/classes/GameScore?%s' % params, '', { "Application-Id": "${APPLICATION_ID}", "REST-API-Key": "${REST_API_KEY}" }) result = json.loads(connection.getresponse().read()) print result
curl -X GET \ -H "Application-Id: ${APPLICATION_ID}" \ -H "REST-API-Key: ${REST_API_KEY}" \ -G \ --data-urlencode 'limit=200' \ --data-urlencode 'skip=400' \ http://api.plugy.io/1/classes/GameScore
You can restrict the fields returned by passing keys
a comma-separated list. To retrieve documents that contain only the score
and playerName
fields (and also special built-in fields such as EntityId
, createdAt
, and updatedAt
):
import json,httplib,urllib connection = httplib.HTTPSConnection('api.plugy.io', 80) params = urllib.urlencode({"keys":"score,playerName"}) connection.connect() connection.request('GET', '/1/classes/GameScore?%s' % params, '', { "Application-Id": "${APPLICATION_ID}", "REST-API-Key": "${REST_API_KEY}" }) result = json.loads(connection.getresponse().read()) print result
curl -X GET \ -H "Application-Id: ${APPLICATION_ID}" \ -H "REST-API-Key: ${REST_API_KEY}" \ -G \ --data-urlencode 'keys=score,playerName' \ http://api.plugy.io/1/classes/GameScore
All of these parameters can be used in combination with each other.
For keys with an array type, you can find objects where the key's array value contains 2 by:
import json,httplib,urllib connection = httplib.HTTPSConnection('api.plugy.io', 80) params = urllib.urlencode({"where":json.dumps({ "arrayKey": 2 })}) connection.connect() connection.request('GET', '/1/classes/RandomEntity?%s' % params, '', { "Application-Id": "${APPLICATION_ID}", "REST-API-Key": "${REST_API_KEY}" }) result = json.loads(connection.getresponse().read()) print result
curl -X GET \ -H "Application-Id: ${APPLICATION_ID}" \ -H "REST-API-Key: ${REST_API_KEY}" \ -G \ --data-urlencode 'where={"arrayKey":2}' \ http://api.plugy.io/1/classes/RandomEntity
You can also use the $all
operator to find objects with an array field which contains each of the values 2, 3, and 4 by:
import json,httplib,urllib connection = httplib.HTTPSConnection('api.plugy.io', 80) params = urllib.urlencode({"where":json.dumps({ "arrayKey": { "$all": [ 2, 3, 4 ] } })}) connection.connect() connection.request('GET', '/1/classes/RandomEntity?%s' % params, '', { "Application-Id": "${APPLICATION_ID}", "REST-API-Key": "${REST_API_KEY}" }) result = json.loads(connection.getresponse().read()) print result
curl -X GET \ -H "Application-Id: ${APPLICATION_ID}" \ -H "REST-API-Key: ${REST_API_KEY}" \ -G \ --data-urlencode 'where={"arrayKey":{"$all":[2,3,4]}}' \ http://api.plugy.io/1/classes/RandomEntity
If you are limiting your query, or if there are a very large number of results, and you want to know how many total results there are without returning them all, you can use the count
parameter. For example, if you only care about the number of games played by a particular player:
import json,httplib,urllib connection = httplib.HTTPSConnection('api.plugy.io', 80) params = urllib.urlencode({"where":json.dumps({ "playerName": "Jonathan Walsh" }),"count":1,"limit":0}) connection.connect() connection.request('GET', '/1/classes/GameScore?%s' % params, '', { "Application-Id": "${APPLICATION_ID}", "REST-API-Key": "${REST_API_KEY}" }) result = json.loads(connection.getresponse().read()) print result
curl -X GET \ -H "Application-Id: ${APPLICATION_ID}" \ -H "REST-API-Key: ${REST_API_KEY}" \ -G \ --data-urlencode 'where={"playerName":"Jonathan Walsh"}' \ --data-urlencode 'count=1' \ --data-urlencode 'limit=0' \ http://api.plugy.io/1/classes/GameScore
Since this requests a count as well as limiting to zero results, there will be a count but no results in the response.
{ "results": [ ], "count": 1337 }
With a nonzero limit, that request would return results as well as the count.
Many apps have a unified login that works across the mobile app and other systems. Accessing user accounts through the REST API lets you build this functionality on top of Plugy.
In general, users have the same features as other objects, such as the flexible schema. The differences are that user objects must have a username and password, the password is automatically encrypted and stored securely, and Plugy enforces the uniqueness of the username
and email
fields.
Signing up a new user differs from creating a generic object in that the username
and password
fields are required. The password field is handled differently than the others; it is encrypted when stored in the Plugy Cloud and never returned to any client request.
You can ask Plugy to verify user email addresses in your application settings page. With this setting enabled, all new user registrations with an email
field will generate an email confirmation at that address. You can check whether the user has verified their email
with the emailVerified
field.
To sign up a new user, send a POST request to the users root. You may add any additional fields. For example, to create a user with a specific phone number:
import json,httplib connection = httplib.HTTPSConnection('api.plugy.io', 80) connection.connect() connection.request('POST', '/1/users', json.dumps({ "username": "cooldude6", "password": "p_n7!-e8", "phone": "415-392-0202" }), { "Application-Id": "${APPLICATION_ID}", "REST-API-Key": "${REST_API_KEY}", "Content-Type": "application/json" }) result = json.loads(connection.getresponse().read()) print result
curl -X POST \ -H "Application-Id: ${APPLICATION_ID}" \ -H "REST-API-Key: ${REST_API_KEY}" \ -H "Content-Type: application/json" \ -d '{"username":"cooldude6","password":"p_n7!-e8","phone":"415-392-0202"}' \ http://api.plugy.io/1/users
When the creation is successful, the HTTP response is a 201 Created
and the Location
header contains the URL for the new user:
Status: 201 Created Location: http://api.plugy.io/1/users/g7y9tkhB7O
The response body is a JSON object containing the EntityId
, the createdAt
timestamp of the newly-created object, and the sessionToken
which can be used to authenticate subsequent requests as this user:
{ "createdAt": "2011-11-07T20:58:34.448Z", "_id": "g7y9tkhB7O", "sessionToken": "pnktnjyb996sj4p156gjtp4im" }
After you allow users to sign up, you need to let them log in to their account with a username and password in the future. To do this, send a GET request to the /1/login
endpoint with username
and password
as URL-encoded parameters:
import json,httplib,urllib connection = httplib.HTTPSConnection('api.plugy.io', 80) params = urllib.urlencode({"username":"cooldude6","password":"p_n7!-e8"}) connection.connect() connection.request('GET', '/1/login?%s' % params, '', { "Application-Id": "${APPLICATION_ID}", "REST-API-Key": "${REST_API_KEY}" }) result = json.loads(connection.getresponse().read()) print result
curl -X GET \ -H "Application-Id: ${APPLICATION_ID}" \ -H "REST-API-Key: ${REST_API_KEY}" \ -G \ --data-urlencode 'username=cooldude6' \ --data-urlencode 'password=p_n7!-e8' \ http://api.plugy.io/1/login
The response body is a JSON object containing all the user-provided fields except password
. It also contains the createdAt
, updatedAt
, EntityId
, and sessionToken
fields:
{ "username": "cooldude6", "phone": "415-392-0202", "createdAt": "2011-11-07T20:58:34.448Z", "updatedAt": "2011-11-07T20:58:34.448Z", "_id": "g7y9tkhB7O", "sessionToken": "pnktnjyb996sj4p156gjtp4im" }
You can also retrieve the contents of a user object by sending a GET request to the URL returned in the location header when it was created. For example, to retrieve the user created above:
import json,httplib connection = httplib.HTTPSConnection('api.plugy.io', 80) connection.connect() connection.request('GET', '/1/users/g7y9tkhB7O', '', { "Application-Id": "${APPLICATION_ID}", "REST-API-Key": "${REST_API_KEY}" }) result = json.loads(connection.getresponse().read()) print result
curl -X GET \ -H "Application-Id: ${APPLICATION_ID}" \ -H "REST-API-Key: ${REST_API_KEY}" \ http://api.plugy.io/1/users/g7y9tkhB7O
The response body is a JSON object containing all the user-provided fields except password
. It also contains the createdAt
, updatedAt
, and EntityId
fields:
{ "username": "cooldude6", "phone": "415-392-0202", "createdAt": "2011-11-07T20:58:34.448Z", "updatedAt": "2011-11-07T20:58:34.448Z", "_id": "g7y9tkhB7O" }
With a valid session token, you can send a GET request to the /1/users/me
endpoint to retrieve the user associated with that session token:
import json,httplib connection = httplib.HTTPSConnection('api.plugy.io', 80) connection.connect() connection.request('GET', '/1/users/me', '', { "Application-Id": "${APPLICATION_ID}", "REST-API-Key": "${REST_API_KEY}", "Session-Token": "pnktnjyb996sj4p156gjtp4im" }) result = json.loads(connection.getresponse().read()) print result
curl -X GET \ -H "Application-Id: ${APPLICATION_ID}" \ -H "REST-API-Key: ${REST_API_KEY}" \ -H "Session-Token: pnktnjyb996sj4p156gjtp4im" \ http://api.plugy.io/1/users/me
The response matches the JSON object above for retrieving users. If the session token is not valid, an error object is returned:
{ "code": 101, "error": "invalid session" }
In normal usage, nobody except the user is allowed to modify their own data. To authenticate themselves, the user must add a Session-Token
header to the request with the session token provided by the signup or login method.
To change the data on a user that already exists, send a PUT request to the user URL. Any keys you don't specify will remain unchanged, so you can update just a subset of the user's data. username
and password
may be changed, but the new username must not already be in use.
For example, if we wanted to change the phone number for cooldude6
:
import json,httplib connection = httplib.HTTPSConnection('api.plugy.io', 80) connection.connect() connection.request('PUT', '/1/users/g7y9tkhB7O', json.dumps({ "phone": "415-369-6201" }), { "Application-Id": "${APPLICATION_ID}", "REST-API-Key": "${REST_API_KEY}", "Session-Token": "pnktnjyb996sj4p156gjtp4im", "Content-Type": "application/json" }) result = json.loads(connection.getresponse().read()) print result
curl -X PUT \ -H "Application-Id: ${APPLICATION_ID}" \ -H "REST-API-Key: ${REST_API_KEY}" \ -H "Session-Token: pnktnjyb996sj4p156gjtp4im" \ -H "Content-Type: application/json" \ -d '{"phone":"415-369-6201"}' \ http://api.plugy.io/1/users/g7y9tkhB7O
The response body is a JSON object containing just an updatedAt
field with the timestamp of the update.
{ "updatedAt": "2011-11-07T21:25:10.623Z" }
You can retrieve multiple users at once by sending a GET request to the root users URL. Without any URL parameters, this simply lists users:
import json,httplib connection = httplib.HTTPSConnection('api.plugy.io', 80) connection.connect() connection.request('GET', '/1/users', '', { "Application-Id": "${APPLICATION_ID}", "REST-API-Key": "${REST_API_KEY}" }) result = json.loads(connection.getresponse().read()) print result
curl -X GET \ -H "Application-Id: ${APPLICATION_ID}" \ -H "REST-API-Key: ${REST_API_KEY}" \ http://api.plugy.io/1/users
The return value is a JSON object that contains a results
field with a JSON array that lists the objects.
{ "results": [ { "username": "bigglesworth", "phone": "650-253-0000", "createdAt": "2011-11-07T20:58:06.445Z", "updatedAt": "2011-11-07T20:58:06.445Z", "_id": "3KmCvT7Zsb" }, { "username": "cooldude6", "phone": "415-369-6201", "createdAt": "2011-11-07T20:58:34.448Z", "updatedAt": "2011-11-07T21:25:10.623Z", "_id": "g7y9tkhB7O" } ] }
All of the options for queries that work for regular objects also work for user objects, so check the section on Querying Entity for more details.
To delete a user from the Plugy Cloud, send a DELETE request to its URL. You must provide the Session-Token
header to authenticate. For example:
import json,httplib connection = httplib.HTTPSConnection('api.plugy.io', 80) connection.connect() connection.request('DELETE', '/1/users/g7y9tkhB7O', '', { "Application-Id": "${APPLICATION_ID}", "REST-API-Key": "${REST_API_KEY}", "Session-Token": "pnktnjyb996sj4p156gjtp4im" }) result = json.loads(connection.getresponse().read()) print result
curl -X DELETE \ -H "Application-Id: ${APPLICATION_ID}" \ -H "REST-API-Key: ${REST_API_KEY}" \ -H "Session-Token: pnktnjyb996sj4p156gjtp4im" \ http://api.plugy.io/1/users/g7y9tkhB7O
Plugy allows you to link your users with services like Twitter and Facebook, enabling your users to sign up or log into your application using their existing identities. This is accomplished through the sign-up and update REST endpoints by providing authentication data for the service you wish to link to a user in the authData
field. Once your user is associated with a service, the authData
for the service will be stored with the user and is retrievable by logging in.
authData
is a JSON object with keys for each linked service containing the data below. In each case, you are responsible for completing the authentication flow (e.g. OAuth 1.0a) to obtain the information the the service requires for linking.
Facebook authData
contents:
{ "facebook": { "id": "user's Facebook id number as a string", "access_token": "an authorized Facebook access token for the user", "expiration_date": "token expiration date of the format: yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" } }
Twitter authData
contents:
{ "twitter": { "id": "user's Twitter id number as a string", "screen_name": "user's Twitter screen name", "consumer_key": "your application's consumer key", "consumer_secret": "your application's consumer secret", "auth_token": "an authorized Twitter token for the user with your application", "auth_token_secret": "the secret associated with the auth_token" } }
Anonymous user authData
contents:
{ "anonymous": { "id": "random UUID with lowercase hexadecimal digits" } }
Signing a user up with a linked service and logging them in with that service uses the same POST request, in which the authData
for the user is specified. For example, to sign up or log in with a user's Twitter account:
import json,httplib connection = httplib.HTTPSConnection('api.plugy.io', 80) connection.connect() connection.request('POST', '/1/users', json.dumps({ "authData": { "twitter": { "id": "12345678", "screen_name": "PlugyIt", "consumer_key": "SaMpLeId3X7eLjjLgWEw", "consumer_secret": "SaMpLew55QbMR0vTdtOACfPXa5UdO2THX1JrxZ9s3c", "auth_token": "12345678-SaMpLeTuo3m2avZxh5cjJmIrAfx4ZYyamdofM7IjU", "auth_token_secret": "SaMpLeEb13SpRzQ4DAIzutEkCE2LBIm2ZQDsP3WUU" } } }), { "Application-Id": "${APPLICATION_ID}", "REST-API-Key": "${REST_API_KEY}", "Content-Type": "application/json" }) result = json.loads(connection.getresponse().read()) print result
curl -X POST \ -H "Application-Id: ${APPLICATION_ID}" \ -H "REST-API-Key: ${REST_API_KEY}" \ -H "Content-Type: application/json" \ -d '{ "authData": { "twitter": { "id": "12345678", "screen_name": "PlugyIt", "consumer_key": "SaMpLeId3X7eLjjLgWEw", "consumer_secret": "SaMpLew55QbMR0vTdtOACfPXa5UdO2THX1JrxZ9s3c", "auth_token": "12345678-SaMpLeTuo3m2avZxh5cjJmIrAfx4ZYyamdofM7IjU", "auth_token_secret": "SaMpLeEb13SpRzQ4DAIzutEkCE2LBIm2ZQDsP3WUU" } } }' \ http://api.plugy.io/1/users
Plugy then verifies that the provided authData
is valid and checks to see if a user is already associated with this data. If so, it returns a status code of 200 OK
and the details (including a sessionToken
for the user):
Status: 200 OK Location: http://api.plugy.io/1/users/uMz0YZeAqc
With a response body like:
{ "username": "Plugy", "createdAt": "2012-02-28T23:49:36.353Z", "updatedAt": "2012-02-28T23:49:36.353Z", "_id": "uMz0YZeAqc", "sessionToken": "samplei3l83eerhnln0ecxgy5", "authData": { "twitter": { "id": "12345678", "screen_name": "PlugyIt", "consumer_key": "SaMpLeId3X7eLjjLgWEw", "consumer_secret": "SaMpLew55QbMR0vTdtOACfPXa5UdO2THX1JrxZ9s3c", "auth_token": "12345678-SaMpLeTuo3m2avZxh5cjJmIrAfx4ZYyamdofM7IjU", "auth_token_secret": "SaMpLeEb13SpRzQ4DAIzutEkCE2LBIm2ZQDsP3WUU" } } }
If the user has never been linked with this account, you will instead receive a status code of 201 Created
, indicating that a new user was created:
Status: 201 Created Location: http://api.plugy.io/1/users/uMz0YZeAqc
The body of the response will contain the EntityId
, createdAt
, sessionToken
, and an automatically-generated unique username
. For example:
{ "username": "iwz8sna7sug28v4eyu7t89fij", "createdAt": "2012-02-28T23:49:36.353Z", "_id": "uMz0YZeAqc", "sessionToken": "samplei3l83eerhnln0ecxgy5" }
Linking an existing user with a service like Facebook or Twitter uses a PUT request to associate authData
with the user. For example, linking a user with a Facebook account would use a request like this:
import json,httplib connection = httplib.HTTPSConnection('api.plugy.io', 80) connection.connect() connection.request('PUT', '/1/users/uMz0YZeAqc', json.dumps({ "authData": { "facebook": { "id": "123456789", "access_token": "SaMpLeAAibS7Q55FSzcERWIEmzn6rosftAr7pmDME10008bWgyZAmv7mziwfacNOhWkgxDaBf8a2a2FCc9Hbk9wAsqLYZBLR995wxBvSGNoTrEaL", "expiration_date": "2012-02-28T23:49:36.353Z" } } }), { "Application-Id": "${APPLICATION_ID}", "REST-API-Key": "${REST_API_KEY}", "Session-Token": "samplei3l83eerhnln0ecxgy5", "Content-Type": "application/json" }) result = json.loads(connection.getresponse().read()) print result
curl -X PUT \ -H "Application-Id: ${APPLICATION_ID}" \ -H "REST-API-Key: ${REST_API_KEY}" \ -H "Session-Token: samplei3l83eerhnln0ecxgy5" \ -H "Content-Type: application/json" \ -d '{ "authData": { "facebook": { "id": "123456789", "access_token": "SaMpLeAAibS7Q55FSzcERWIEmzn6rosftAr7pmDME10008bWgyZAmv7mziwfacNOhWkgxDaBf8a2a2FCc9Hbk9wAsqLYZBLR995wxBvSGNoTrEaL", "expiration_date": "2012-02-28T23:49:36.353Z" } } }' \ http://api.plugy.io/1/users/uMz0YZeAqc
After linking your user to a service, you can authenticate them using matching authData
.
Unlinking an existing user with a service also uses a PUT request to clear authData
from the user by setting the authData
for the service to null
. For example, unlinking a user with a Facebook account would use a request like this:
import json,httplib connection = httplib.HTTPSConnection('api.plugy.io', 80) connection.connect() connection.request('PUT', '/1/users/uMz0YZeAqc', json.dumps({ "authData": { "facebook": null } }), { "Application-Id": "${APPLICATION_ID}", "REST-API-Key": "${REST_API_KEY}", "Session-Token": "samplei3l83eerhnln0ecxgy5", "Content-Type": "application/json" }) result = json.loads(connection.getresponse().read()) print result
curl -X PUT \ -H "Application-Id: ${APPLICATION_ID}" \ -H "REST-API-Key: ${REST_API_KEY}" \ -H "Session-Token: samplei3l83eerhnln0ecxgy5" \ -H "Content-Type: application/json" \ -d '{ "authData": { "facebook": null } }' \ http://api.plugy.io/1/users/uMz0YZeAqc
When you access Plugy via the REST API key, access can be restricted by ACL just like in the iOS and Android SDKs. You can still read and modify acls via the REST API, just be accessing the "ACL"
key of an object.
The ACL is formatted as a JSON object where the keys areRelations either object ids or the special key "*"
to indicate public access permissions. The values of the ACL are "permission objects", JSON objects whose keys are the permission names and whose values are always true
.
For example, if you want the user with id "3KmCvT7Zsb"
to have read and write access to an object, plus the object should be publicly readable, that corresponds to an ACL of:
{ "3KmCvT7Zsb": { "read": true, "write": true }, "*": { "read": true } }
If you want to access your data ignoring all ACLs, you can use the master key provided on the Dashboard. Instead of the REST-API-Key
header, set the Master-Key
header. For backward compatibility, you can also do master-level authentication using HTTP Basic Auth, passing the application id as the username and the master key as the password. For security, the master key should not be distributed to end users, but if you are running code in a trusted environment, feel free to use the master key for authentication.
When you access Plugy via the REST API key, access can be restricted by ACL just like in the iOS and Android SDKs. You can still read and modify ACLs
via the REST API, just by accessing the "ACL"
key of an object.
In addition to per-user permissions as described above, you can also specify role-level permissions to your Plugy objects.
Instead of specifying an EntityId
as the key for a permission object as you do for users, you can instead specify a role's name with a
"role:"
prefix as the key for a permission object. You can use role-level permissions alongside user-level permissions to provide fine-grained
control over user access.
For example, to restrict an object to be readable by anyone in the "Members" role and writable by its creator and anyone in the "Moderators" role, you would specify an ACL like this:
{ "8TOXdXf3tz": { "write": true }, "role:Members": { "read": true }, "role:Moderators": { "write": true } }
You are not required to specify read permissions for the user or the "Moderators" role if the user and role are already children of the "Members" role, since they will inherit read permissions granted to "Members".
To upload a file to Plugy, send a POST request to the files URL, postfixed with the name of the file. The request must contain the Content-Type
header associated with the file. Keep in mind that files are limited to 10 megabytes. Here's a simple example that'll create a file named hello.txt
containing a string:
import json,httplib connection = httplib.HTTPSConnection('api.plugy.io', 80) connection.connect() connection.request('POST', '/1/files/hello.txt', 'Hello, World!', { "Application-Id": "${APPLICATION_ID}", "REST-API-Key": "${REST_API_KEY}", "Content-Type": "text/plain" }) result = json.loads(connection.getresponse().read()) print result
curl -X POST \ -H "Application-Id: ${APPLICATION_ID}" \ -H "REST-API-Key: ${REST_API_KEY}" \ -H "Content-Type: text/plain" \ -d 'Hello, World!' \ http://api.plugy.io/1/files/hello.txt
When the file upload is successful, the HTTP response is a 201 Created
and the Location
header which contains the URL for the file:
Status: 201 Created Location: http://files.parsetfss.com/bc9f32df-2957-4bb1-93c9-ec47d9870a05/tfss-db295fb2-8a8b-49f3-aad3-dd911142f64f-hello.txt
The response body is a JSON object containing the name
of the file, which is the original file name prefixed with a unique identifier in order to prevent name collisions. This means you can save files with the same name, and the files will not overwrite one another.
{ "url": "http://files.parsetfss.com/bc9f32df-2957-4bb1-93c9-ec47d9870a05/tfss-db295fb2-8a8b-49f3-aad3-dd911142f64f-hello.txt", "name": "db295fb2-8a8b-49f3-aad3-dd911142f64f-hello.txt" }
To upload an image, the syntax is a little bit different. Here's an example that will upload the image myPicture.jpg
from the current directory.
import json,httplib connection = httplib.HTTPSConnection('api.plugy.io', 80) connection.connect() connection.request('POST', '/1/files/pic.jpg', open('myPicture.jpg', 'rb').read(), { "Application-Id": "${APPLICATION_ID}", "REST-API-Key": "${REST_API_KEY}", "Content-Type": "image/jpeg" }) result = json.loads(connection.getresponse().read()) print result
curl -X POST \ -H "Application-Id: ${APPLICATION_ID}" \ -H "REST-API-Key: ${REST_API_KEY}" \ -H "Content-Type: image/jpeg" \ --data-binary<EMAIL_ADDRESS>\ http://api.plugy.io/1/files/pic.jpg
After files are uploaded, you can associate them with Plugy objects:
import json,httplib connection = httplib.HTTPSConnection('api.plugy.io', 80) connection.connect() connection.request('POST', '/1/classes/PlayerProfile', json.dumps({ "name": "Andrew", "picture": { "name": "...profile.png", "__type": "File" } }), { "Application-Id": "${APPLICATION_ID}", "REST-API-Key": "${REST_API_KEY}", "Content-Type": "application/json" }) result = json.loads(connection.getresponse().read()) print result
curl -X POST \ -H "Application-Id: ${APPLICATION_ID}" \ -H "REST-API-Key: ${REST_API_KEY}" \ -H "Content-Type: application/json" \ -d '{ "name": "Andrew", "picture": { "name": "...profile.png", "__type": "File" } }' \ http://api.plugy.io/1/classes/PlayerProfile
Note that the name of the file in the request is not the local file name, but the name in the response of the previous upload operation.
Users holding the master key are allowed to delete files using the REST API. To delete a file, send a DELETE request to the files URL, postfixed with the name of the file. Note that the name of the file must be the name in the response of the upload operation, rather than the original filename. Note that the Master-Key
must be provided in headers.
import json,httplib connection = httplib.HTTPSConnection('api.plugy.io', 80) connection.connect() connection.request('DELETE', '/1/files/...profile.png', '', { "Application-Id": "${APPLICATION_ID}", "Master-Key": "${MASTER_KEY}" }) result = json.loads(connection.getresponse().read()) print result
curl -X DELETE \ -H "Application-Id: ${APPLICATION_ID}" \ -H "Master-Key: ${MASTER_KEY}" \ http://api.plugy.io/1/files/...profile.png
Note that deleting a PFEntity with a file associated with it will not delete the file. All files stored on Plugy should be deleted by using the above explained API.
Plugy Config
is a way to configure your applications remotely by storing a single configuration object on Plugy.
It enables you to add things like feature gating or a simple "Message of the day".
To start using Plugy Config
you need to add a few key/value pairs (parameters) to your app on the Plugy Config Dashboard.
After that you will be able to fetch the config on the client by sending a GET
request to config URL.
Here is a simple example that will fetch the Plugy.Config
:
import json,httplib connection = httplib.HTTPSConnection('api.plugy.io', 80) connection.connect() connection.request('GET', '/1/config', '', { "Application-Id": "${APPLICATION_ID}", "REST-API-Key": "${REST_API_KEY}" }) result = json.loads(connection.getresponse().read()) print result
curl -X GET \ -H "Application-Id: ${APPLICATION_ID}" \ -H "REST-API-Key: ${REST_API_KEY}" \ http://api.plugy.io/1/config
The response body is a JSON object containing all the configuration parameters in the params
field.
{ "params": { "welcomeMessage": "Welcome to The Internet!", "winningNumber": 42 } }
An installation object represents an instance of your app being installed on a device. These objects are used to store subscription data for installations which have subscribed to one or more push notification channels. Installations have a flexible schema, except that the special fields below have special type and validation requirements:
badge
: is a number field representing the last
known application badge for iOS installations.
channels
:
An array of the channels to which a device is currently subscribed.timeZone
:
The current time zone where the target device is located.deviceType
:
The type of device, "ios", "android", "winrt", "winphone", or "dotnet"(readonly).pushType
:
This field is reserved for directing Plugy to the push delivery network to be used. If the device
is registered to receive pushes via GCM, this field will be marked "gcm". If this device is not using GCM,
and is using Plugy's push notification service, it will be blank (readonly).installationId
:
Universally Unique Identifier (UUID) for the device used by Plugy. It must be unique across all of an app's installations. (readonly).deviceToken
:
The Apple or Google generated token used to deliver messages to the APNs or GCM push networks respectively.channelUris
:
The Microsoft-generated push URIs for Windows devices.appName
:
The display name of the client application to which this installation belongs.appVersion
:
The version string of the client application to which this installation belongs.parseVersion
:
The version of the Plugy SDK which this installation uses.appIdentifier
:
A unique identifier for this installation's client application. In iOS, this is the Bundle Identifier.Most of the time, installation data is modified by push-related
methods in the client SDK. For example,
calling subscribeToChannel
or unsubscribeFromChannel
from the client SDK will
create an object for that installation if it doesn't yet exist and
update its channels, and calling getSubscribedChannels
from the client SDK will read subscription data from that
installation's object. The REST methods can be used to mimic these
operations. For instance, if you have an iOS device token then you
can subscribe it to push notifications by creating an installation
object for it with the desired channels
list. You can
also perform operations which aren't possible through the client
SDK, like using a query over installations to find the set of
subscribers to a given channel.
Creating an installation object is similar to creating a generic
object, but the special installation fields listed above must pass
validation. For example, if you have a device token provided by the
Apple Push Notification service and would like to subscribe it to
the broadcast channel ""
, you can use the following
command:
import json,httplib connection = httplib.HTTPSConnection('api.plugy.io', 80) connection.connect() connection.request('POST', '/1/installations', json.dumps({ "deviceType": "ios", "deviceToken": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", "channels": [ "" ] }), { "Application-Id": "${APPLICATION_ID}", "REST-API-Key": "${REST_API_KEY}", "Content-Type": "application/json" }) result = json.loads(connection.getresponse().read()) print result
curl -X POST \ -H "Application-Id: ${APPLICATION_ID}" \ -H "REST-API-Key: ${REST_API_KEY}" \ -H "Content-Type: application/json" \ -d '{ "deviceType": "ios", "deviceToken": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", "channels": [ "" ] }' \ http://api.plugy.io/1/installations
When the creation is successful, the HTTP response is a 201
Created
and the Location
header contains the URL
for the new installation:
Status: 201 Created Location: http://api.plugy.io/1/installations/mrmBZvsErB
The response body is a JSON object containing
the EntityId
and the createdAt
timestamp
of the newly-created installation:
{ "createdAt": "2012-04-28T17:41:09.106Z", "_id": "mrmBZvsErB" }
You can retrieve the contents of an installation object by sending a GET request to the URL returned in the location header when it was created. For example, to retrieve the installation created above:
import json,httplib connection = httplib.HTTPSConnection('api.plugy.io', 80) connection.connect() connection.request('GET', '/1/installations/mrmBZvsErB', '', { "Application-Id": "${APPLICATION_ID}", "REST-API-Key": "${REST_API_KEY}" }) result = json.loads(connection.getresponse().read()) print result
curl -X GET \ -H "Application-Id: ${APPLICATION_ID}" \ -H "REST-API-Key: ${REST_API_KEY}" \ http://api.plugy.io/1/installations/mrmBZvsErB
The response body is a JSON object containing all the
user-provided fields, plus
the createdAt
, updatedAt
,
and EntityId
fields:
{ "deviceType": "ios", "deviceToken": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", "channels": [ "" ], "createdAt": "2012-04-28T17:41:09.106Z", "updatedAt": "2012-04-28T17:41:09.106Z", "_id": "mrmBZvsErB" }
Installation objects can be updated by sending a PUT request to the installation URL. For example, to subscribe the installation above to the "foo" push channel:
import json,httplib connection = httplib.HTTPSConnection('api.plugy.io', 80) connection.connect() connection.request('PUT', '/1/installations/mrmBZvsErB', json.dumps({ "deviceType": "ios", "deviceToken": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", "channels": [ "", "foo" ] }), { "Application-Id": "${APPLICATION_ID}", "REST-API-Key": "${REST_API_KEY}", "Content-Type": "application/json" }) result = json.loads(connection.getresponse().read()) print result
curl -X PUT \ -H "Application-Id: ${APPLICATION_ID}" \ -H "REST-API-Key: ${REST_API_KEY}" \ -H "Content-Type: application/json" \ -d '{ "deviceType": "ios", "deviceToken": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", "channels": [ "", "foo" ] }' \ http://api.plugy.io/1/installations/mrmBZvsErB
You can retrieve multiple installations at once by sending a GET
request to the root installations URL. This functionality is not
available in the SDKs, so you must authenticate this method using
the Master-Key
header in your request instead
of the REST-API-Key
header. Your master key
allows you to bypass ACLs and should only be used from within a
trusted environment.
Without any URL parameters, a GET request simply lists installations:
import json,httplib connection = httplib.HTTPSConnection('api.plugy.io', 80) connection.connect() connection.request('GET', '/1/installations', '', { "Application-Id": "${APPLICATION_ID}", "Master-Key": "${MASTER_KEY}" }) result = json.loads(connection.getresponse().read()) print result
curl -X GET \ -H "Application-Id: ${APPLICATION_ID}" \ -H "Master-Key: ${MASTER_KEY}" \ http://api.plugy.io/1/installations
The return value is a JSON object that contains a results field with a JSON array that lists the users.
{ "results": [ { "deviceType": "ios", "deviceToken": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", "channels": [ "" ], "createdAt": "2012-04-28T17:41:09.106Z", "updatedAt": "2012-04-28T17:41:09.106Z", "_id": "mrmBZvsErB" }, { "deviceType": "ios", "deviceToken": "fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210", "channels": [ "" ], "createdAt": "2012-04-30T01:52:57.975Z", "updatedAt": "2012-04-30T01:52:57.975Z", "_id": "sGlvypFQcO" } ] }
All of the options for queries that work for regular objects also
work for installation objects, so check the section on
Querying Entity for more
details. By doing an array query over channels
, for
example, you can find the set of devices subscribed to a given push
channel.
To delete an installation from the Plugy Cloud, send a DELETE request
to its URL. This functionality is not available in the client SDKs,
so you must authenticate this method using
the Master-Key
header in your request instead
of the REST-API-Key
header. Your master key
allows you to bypass ACLs and should only be used from within a
trusted environment. For example:
import json,httplib connection = httplib.HTTPSConnection('api.plugy.io', 80) connection.connect() connection.request('DELETE', '/1/installations/mrmBZvsErB', '', { "Application-Id": "${APPLICATION_ID}", "Master-Key": "${MASTER_KEY}" }) result = json.loads(connection.getresponse().read()) print result
curl -X DELETE \ -H "Application-Id: ${APPLICATION_ID}" \ -H "Master-Key: ${MASTER_KEY}" \ http://api.plugy.io/1/installations/mrmBZvsErB


--- title: BlackBerry update bursting with spyware • The Register layout: post ---
An update pushed out to BlackBerry users on the Etisalat network in the United Arab Emirates appears to contain remotely-triggered spyware that allows the interception of messages and emails, as well as crippling battery life.
Crazytown. When will people learn not to install random shit?


| I l@ve RuBoard |
Sorted range algorithms require that the source ranges have the elements sorted according to their sorting criterion. They may have significant better performance than similar algorithms for unsorted ranges (usually logarithmic instead of linear complexity). You can use these algorithms with iterators that are not random access iterators. However, in this case, the algorithms have linear complexity because they have to step through the sequence element-b-element. Nevertheless, the number of comparisons may still have logarithmic complexity.
According to the standard, calling these algorithms for sequences that are not sorted on entry results in undefined behavior. However, for most implementations calling these algorithms also works for unsorted sequences. Nevertheless, to rely on this fact is not portable.
Associative containers provide special member functions for the searching algorithms presented here. When searching for a special value or key, you should use them.
The following algorithms search certain values in sorted ranges.
bool
binary_search (ForwardIterator beg, ForwardIterator end, const T& value)
bool
binary_search (ForwardIterator beg, ForwardIterator end, const T& value, BinaryPredicate op)
Both forms return whether the sorted range [beg,end) contains an element equal to value.
op is an optional binary predicate that is used as the sorting criterion:
op(elem1 ,elem2)
To obtain the position of an element for which you are searching, use lower_bound(), upper_bound(), or equal_range() (see page 413 and page 415).
The caller has to ensure that the ranges are sorted according to the sorting criterion on entry.
Complexity: logarithmic for random access iterators, linear otherwise (at most, log(numberOfElements) + 2 comparisons, but for other than random access iterators the number of operations to step through the elements is linear, making the total complexity linear).
The following program demonstrates how to use binary_search():
// algo/bsearch1.cpp #include "algostuff.hpp" using namespace std; int main() { list<int> coll; INSERT_ELEMENTS(coll,1,9); PRINT_ELEMENTS(coll) ; // check existence of element with value 5 if (binary_search(coll.begin(), coll end(), 5)) { cout << "5 is present" << endl; } else { cout << "5 is not present" << endl; } // check existence of element with value 42 if (binary_search(coll.begin(), coll.end(), 42)) { cout << "42 is present" << endl; } else { cout << "42 is not present" << endl; } }
The program has the following output:
1 2 3 4 5 6 7 8 9 5 is present 42 is not present
bool
includes (InputIterator1 beg, InputIterator1 end, InputIterator2 searchBeg, InputIterator2 searchEnd)
bool
includes (InputIterator1 beg, InputIterator1 end, InputIterator2 searchBeg, InputIterator2 searchEnd, BinaryPredicate op)
Both forms return whether the sorted range [beg,end) contains all elements in the sorted range [searchBeg,searchEnd). That is, for each element in [searchBeg,searchEnd) there must be an equal element in [beg,end). If elements in [searchBeg,search End) are equal, [beg,end) must contain the same number of elements. Thus, [searchBeg,searchEnd) must be a subset of [beg,end).
op is an optional binary predicate that is used as the sorting criterion:
op (elem1, elem2)
The caller has to ensure that both ranges are sorted according to the same sorting criterion on entry.
Complexity: linear (at most, 2* (numberOfElements+searchElements) - 1 comparisons).
The following program demonstrates the usage of includes():
// algo/includes.cpp #include "algostuff.hpp" using namespace std; int main() { list<int> coll; vector<int> search; INSERT_ELEMENTS(coll,1,9); PRINT_ELEMENTS(coll,"coll: "); search.push_back(3); search.push_back(4); search.push_back(7); PRINT_ELEMENTS(search,"search: "); // check whether all elements in search are also in coll if (includes (coll.begin(), coll.end(), search.begin(), search.end())) { cout << "all elements of search are also in coll" << endl; } else { cout << "not all elements of search are also in coll" << endl; } }
The program has the following output:
coll: 1 2 3 4 5 6 7 8 9 search: 3 4 7 all elements of search are also in coll
ForwardIterator
lower_bound (ForwardIterator beg, ForwardIterator end, const T& value)
ForwardIterator
lower_bound (ForwardIterator beg, ForwardIterator end, const T& value, BinaryPredicate op)
ForwardIterator
upper_bound (ForwardIterator beg, ForwardIterator end, const T& value)
ForwardIterator
upper_bound (ForwardIterator beg, ForwardIterator end, const T& value, BinaryPredicate op)
lower_bound() returns the position of the first element that has a value less than or equal to value. This is the first position where an element with value value could get inserted without breaking the actual sorting of the range [beg,end).
upper_bound() returns the position of the first element that has a value greater than value. This is the last position where an element with value value could get inserted without breaking the actual sorting of the range [beg,end).
All algorithms return end if there is no such value.
op is an optional binary predicate that is used as the sorting criterion:
op (elem1 ,elem2)
The caller has to ensure that the ranges are sorted according to the sorting criterion on entry.
To obtain the result from both lower_bound() and upper_bound(), use equal_range(), which returns both (see the next algorithm).
Associative containers (set, multiset, map, and multimap) provide equivalent member functions that provide better performance (see page 235).
Complexity: logarithmic for random access iterators, linear otherwise (at most, log(numberOfElements) + 1 comparisons, but for other than random access iterators the number of operations to step through the elements is linear, making the total complexity linear).
The following program demonstrates how to use lower_bound() and upper_bound()[8] :
[8] Older STL versions might need the file distance.hpp from page 263.
// algo/bounds1.cpp #include "algostuff.hpp" using namespace std; int main() { list<int> coll; INSERT_ELEMENTS(coll,1,9); INSERT_ELEMENTS(coll,1,9); coll.sort(); PRINT_ELEMENTS(coll); // print first and last position 5 could get inserted list<int> :: iterator pos1, pos2; pos1 = lower_bound (coll.begin(), coll.end(), 5); pos2 = upper_bound (coll.begin(), coll.end(), 5); cout << "5 could get position " << distance(coll.begin(),pos1) + 1 << " up to " << distance(coll.begin(),pos2) + 1 << " without breaking the sorting" << endl; // insert 3 at the first possible position without breaking the sorting coll.insert (lower_bound(coll.begin(), coll.end(), 3), 3); // insert 7 at the last possible position without breaking the sorting coll.insert (upper_bound(coll.begin(),coll.end(), 7), 7); PRINT_ELEMENTS(coll); }
The program has the following output:
1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 5 could get position 9 up to 11 without breaking the sorting 1 1 2 2 3 3 3 4 4 5 5 6 6 7 7 7 8 8 9 9
pair<ForwardIterator,ForwardIterator>
equal_range (ForwardIterator beg, ForwardIterator end, const T& value)
pair<ForwardIterator,ForwardIterator>
equal_range (ForwardIterator beg, ForwardIterator end, const T& value, BinaryPredicate op)
Both forms return the range of elements that is equal to value. This is the first and the last position an element with value value could get inserted without breaking the actual sorting of the range [beg,end).
This is equivalent to
make_pair (lower_bound(...), upper_bound(...))
op is an optional binary predicate that is used as the sorting criterion:
op (elem1, elem2)
The caller has to ensure that the ranges are sorted according to the sorting criterion on entry.
Associative containers (set, multiset, map, and multimap) provide an equivalent member function that has better performance (see page 236).
Complexity: logarithmic for random access iterators, linear otherwise (at most, 2*log(numberOfElements) + 1 comparisons, but for other than random access iterators the number of operations to step through the elements is linear, making the total complexity linear).
The following program demonstrates how to use equal_range()[9] :
[9] Older STL versions might need the file distance.hpp from page 263.
// algo/eqrange1.cpp #include "algostuff.hpp" using namespace std; int main() { list<int> coll; INSERT_ELEMENTS(coll,1,9); INSERT_ELEMENTS(coll,1,9); coll.sort(); PRINT_ELEMENTS(coll); // print first and last position 5 could get inserted pair<list<int>::iterator,list<int>::iterator> range; range = equal_range (coll.begin(), coll.end(), 5); cout << "5 could get position " << distance (coll.begin(),range, first) + 1 << " up to " << distance(coll.begin().range.second) + 1 << " without breaking the sorting" << endl; }
The program has the following output:
1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 5 could get position 9 up to 11 without breaking the sorting
The following algorithms merge elements of two ranges. They process the sum, the union, the intersection, and so on.
OutputIterator
merge (InputIterator source1Beg, InputIterator source1End, InputIterator source2Beg, InputIterator source2End, Output Iterator destBeg)
OutputIterator
merge (InputIterator source1Beg, InputIterator source1End, InputIterator source2Beg, InputIterator source2End, OutputIterator destBeg, BinaryPredicate op)
Both forms merge the elements of the sorted source ranges [source1Beg,source1End) and [source2Beg,source2End) so that the destination range starting with destBeg contains all elements that are in the first source range plus those that are in the second source range. For example, calling merge() for
1 2 2 4 6 7 7 9
and
2 2 2 3 6 6 8 9
results in
1 2 2 2 2 2 3 4 6 6 6 7 7 8 9 9
All elements in the destination range are in sorted order.
Both forms return the position after the last copied element in the destination range (the first element that is not overwritten).
op is an optional binary predicate that is used as the sorting criterion:
op(elem1 ,elem2)
The source ranges are not modified.
According to the standard, the caller has to ensure that both source ranges are sorted on entry. However, in most implementations this algorithm also merges elements of two unsorted source ranges into an unsorted destination range. Nevertheless, for unsorted ranges you should call copy() twice, instead of merge(), to be portable.
The caller must ensure that the destination range is big enough or that insert iterators are used.
The destination range should not overlap the source ranges.
Lists provide a special member function, merge(), to merge the elements of two lists (see page 246).
To ensure that elements that are in both source ranges end up in the destination range only once, use set_union() (see page 418).
To process only the elements that are in both source ranges, use set_intersection() (see page 419).
Complexity: linear (at most, numberOfElement1+numberOfElements2-1 comparisons).
The following example demonstrates how to use merge():
// algo/merge1.cpp #include "algostuff.hpp" using namespace std; int main() { list<int> coll1; set<int> coll2; // fill both collections with some sorted elements INSERT_ELEMENTS(coll1,l,6); INSERT_ELEMENTS(coll2,3,8); PRINT_ELEMENTS(coll1,"coll1: "); PRINT_ELEMENTS(coll2,"coll2: "); // print merged sequence cout << "merged: "; merge (coll1.begin(), coll1.end(), coll2.begin(), coll2.end(), ostream_iterator<int>(cout," ")); cout << endl; }
The program has the following output:
coll1: 1 2 3 4 5 6 coll2: 3 4 5 6 7 8 merged: 1 2 3 3 4 4 5 5 6 6 7 8
See page 421 for another example. It demonstrates how the different algorithms that are provided to combine sorted sequences differ.
OutputIterator
set_union (InputIterator source1Beg, InputIterator source1End, InputIterator source2Beg, InputIterator source2End, OutputIterator destBeg)
OutputIterator
set_union (InputIterator source1Beg, InputIterator source1End, InputIterator source2Beg, InputIterator source2End, OutputIterator destBeg, BinaryPredicate op)
Both forms merge the elements of the sorted source ranges [source1Beg,source1End) and [source2Beg,source2End) so that the destination range starting with destBeg contains all elements that are either in the first source range, in the second source range, or in both. For example, calling set_union() for
1 2 2 4 6 7 7 9
and
2 2 2 3 6 6 8 9
results in
1 2 2 2 3 4 6 6 7 7 8 9
All elements in the destination range are in sorted order.
Elements that are in both ranges are in the union range only once. However, duplicates are possible if elements occur more than once in one of the source ranges. The number of occurrences of equal elements in the destination range is the maximum of the number of their occurrences in both source ranges.
Both forms return the position after the last copied element in the destination range (the first element that is not overwritten).
op is an optional binary predicate that is used as the sorting criterion:
op(elem1,elem2)
The source ranges are not modified.
The caller has to ensure that the ranges are sorted according to the sorting criterion on entry.
The caller must ensure that the destination range is big enough or that insert iterators are used.
The destination range should not overlap the source ranges.
To obtain all elements of both source ranges without removing elements that are in both, use merge() (see page 416).
Complexity: linear (at most, 2*(numberOfElements1+numberOfElements2) - 1 comparisons).
See page 421 for an example of the use of set_union(). This example also demonstrates how it differs from other algorithms that combine elements of two sorted sequences.
OutputIterator
set_intersection (InputIterator source1Beg, InputIterator source1End. InputIterator source2Beg, InputIterator source2End, OutputIterator destBeg)
OutputIterator
set_intersection (InputIterator source1Beg, InputIterator source1End, InputIterator source2Beg, InputIterator sotirce2End, OutputIterator destBeg, BinaryPredicate op)
Both forms merge the elements of the sorted source ranges [source1 Beg,source1 End) and [source2Beg,source2End) so that the destination range starting with destBeg contains all elements that are in both source ranges. For example, calling set_intersection() for
1 2 2 4 6 7 7 9
and
2 2 2 3 6 6 8 9
results in
2 2 6 9
All elements in the destination range are in sorted order.
Duplicates are possible if elements occur more than once in both source ranges. The number of occurrences of equal elements in the destination range is the minimum number of their occurrences in both source ranges.
Both forms return the position after the last merged element in the destination range.
op is an optional binary predicate that is used as the sorting criterion:
op (elem1,elem2)
The source ranges are not modified.
The caller has to ensure that the ranges are sorted according to the sorting criterion on entry.
The caller must ensure that the destination range is big enough or that insert iterators are used.
The destination range should not overlap the source ranges.
Complexity: linear (at most, 2* (numberOfElements 1+numberOfElements2*) - 1 comparisons).
See page 421 for an example of the use of set_intersection(). This example also demonstrates how it differs from other algorithms that combine elements of two sorted sequences.
OutputIterator
set_difference (InputIterator source1Beg, InputIterator source1End, InputIterator source2Beg, InputIterator source2End, OutputIterator destBeg)
OutputIterator
set_difference (InputIterator source1Beg, InputIterator source1End, InputIterator source2Beg, InputIterator source2End, OutputIterator destBeg, BinaryPredicate op)
Both forms merge the elements of the sorted source ranges [source1Beg,source1End) and [source2Beg,source2End) so that the destination range starting with destBeg contains all elements that are in the first source range but not in the second source range. For example, calling set_difference() for
1 2 2 4 6 7 7 9
and
2 2 2 3 6 6 8 9
results in
1 4 7 7
All elements in the destination range are in sorted order.
Duplicates are possible if elements occur more than once in the first source range. The number of occurrences of equal elements in the destination range is the difference between the number of their occurrences in the first source range less the number of occurrences in the second source range. If there are more occurrences in the second source range, the number of occurrences in the destination range is zero.
Both forms return the position after the last merged element in the destination range.
op is an optional binary predicate that is used as the sorting criterion:
op(elem1 ,elem2)
The source ranges are not modified.
The caller has to ensure that the ranges are sorted according to the sorting criterion on entry.
The caller must ensure that the destination range is big enough or that insert iterators are used.
The destination range should not overlap the source ranges.
Complexity: linear (at most, 2*(numberOfElements1+numberOfElements2) - 1 comparisons).
See page 421 for an example of the use of set_difference(). This example also demonstrates how it differs from other algorithms that combine elements of two sorted sequences.
OutputIterator
set_symmetric_difference (InputIterator source1 Beg, InputIterator source1 End, InputIterator source2Beg, InputIterator source2End, OutputIterator destBeg)
OutputIterator
set_symmetric_difference (InputIterator source1Beg, InputIterator source1End, InputIterator source2Beg, InputIterator source2End, OutputIterator destBeg, BinaryPredicate op)
Both forms merge the elements of the sorted source ranges [source1Beg,source1End) and [source2Beg,source2End) so that the destination range starting with destBeg contains all elements that are either in the first source range or in the second source range, but not in both. For example, calling set_symmetric_difference() for
1 2 2 4 6 7 7 9
and
2 2 2 3 6 6 8 9
results in
1 2 3 4 6 7 7 8
All elements in the destination range are in sorted order.
Duplicates are possible if elements occur more than once in one of the source ranges. The number of occurrences of equal elements in the destination range is the difference between the number of their occurrences in the source ranges.
Both forms return the position after the last merged element in the destination range.
op is an optional binary predicate that is used as the sorting criterion:
op(elem1,elem2)
The source ranges are not modified.
The caller has to ensure that the ranges are sorted according to the sorting criterion on entry.
The caller must ensure that the destination range is big enough or that insert iterators are used.
The destination range should not overlap the source ranges.
Complexity: linear (at most, 2* (numberOfElements1+numberOfElements2) - 1 comparisons).
See the following subsection for an example of the use of set_symmetric_difference(). This example also demonstrates how it differs from other algorithms that combine elements of two sorted sequences.
The following example compares the different algorithms that combine elements of two sorted source ranges, demonstrating how they work and differ:
// algo/setalgos.cpp #include "algostuff.hpp" using namespace std; int main() { int c1[] = { 1, 2, 2, 4, 6, 7, 7, 9 }; int num1 = sizeof(c1) / sizeof(int); int c2[] = { 2, 2, 2, 3, 6, 6, 8, 9 >; int num2 = sizeof(c2) / sizeof(int); // print source ranges cout << "c1: " ; copy (c1, c1+num1, ostream_iterator<int>(cout," ")) ; cout << endl; cout << "c2: " ; copy (c2, c2+num2, ostream_iterator<int>(cout," ")); cout << '\n' << endl; // sum the ranges by using merge() cout << "merge(): "; merge (c1, c1+num1, c2, c2+num2, ostream_iterator<int>(cout," ")); cout << endl; // unite the ranges by using set_union() cout << "set_union(): "; set_union (c1, c1+num1, c2, c2+num2, ostream_iterator<int>(cout," ")); cout << endl; // intersect the ranges by using set_intersection() cout << "set_intersection(): "; set_intersection (c1, c1+num1, c2, c2+num2, ostream_iterator<int>(cout," ")); cout << endl; // determine elements of first range without elements of second range // by using set_difference() cout << "set_difference(): "; set_difference (c1, c1+num1, c2, c2+num2, ostream_iterator<int>(cout," ")); cout << endl; // determine difference the ranges with set_symmetric_difference() cout << "set_symmetric_difference(): "; set_symmetric_difference (c1, c1+num1, c2, c2+num2, ostream_iterator<int>(cout," ")); cout << endl; }
The program has the following output:
c1: 1 2 2 4 6 7 7 9 c2: 2 2 2 3 6 6 8 9 merge(): 1 2 2 2 2 2 3 4 6 6 6 7 7 8 9 9 set_union(): 1 2 2 2 3 4 6 6 7 7 8 9 set_intersection(): 2 2 6 9 set_difference(): 1 4 7 7 set_symmetric_difference(): 1 2 3 4 6 7 7 8
void
inplace_merge (BidirectionalIterator beg1, BidirectionalIterator end1beg2, BidirectionalIterator end2)
void
inplace_merge (BidirectionalIterator beg1, BidirectionalIterator end1beg2, BidirectionalIterator end2, BinaryPredicate op)
Both forms merge the consecutive sorted source ranges [beg1,end1beg2) and [end 1 beg2 ,end2) so that the range [beg1,end2) contains the elements as a sorted summary range.
Complexity: linear (numberOfElements-1 comparisons) if enough memory available, or n-log-n otherwise (numberOfElements*log (numberOfElements) comparisons).
The following program demonstrates the use of inplace_merge():
// algo/imerge1.cpp #include "algostuff.hpp" using namespace std; int main() { list<int> coll; // insert two sorted sequences INSERT_ELEMENTS(coll,1,7); INSERT_ELEMENTS(coll,1,8); PRINT_ELEMENTS(coll); // find beginning of second part (element after 7) list<int>::iterator pos; pos = find (coll.begin(), coll.end(), // range 7) ; // value ++pos; // merge into one sorted range inplace_merge (coll.begin(), pos, coll.end()); PRINT_ELEMENTS(coll); }
The program has the following output:
1 2 3 4 5 6 7 1 2 3 4 5 6 7 8 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8
| I l@ve RuBoard |


--- layout: archive title: Zulip Chat Archive permalink: /stream/113488-general/topic/Issues.20on.20windows.20for.20new.20users.html ---
Hi I had a new user today and we were trying to install lean on windows by following the mathlib instructions, there were loads of issues getting everything working however. This might just be because we didn't follow the instructions well enough though. I'm posting the issues we had just to see if there are any easy wins to fix them.
- ~/.profile
was not sourced by git bash, either in the vscode terminal or in a new git-bash window. I had to move it to ~/.bashrc
- VSCode did not manage to find the lean executable in ~/.elan/bin
. This was not rectified by running the 'install using elan' prompt. I fixed this by explicitly setting the lean executable vscode setting, but that took a couple of attempts because you had to use the windows-style path not the bash-style path.
- Running the install by elan prompt happened multiple times in desperation, and this meant that it was added to .profile multiple times so the path would have ~/.elan/bin
in it multiple times.
- Finally, the import fail 'not in LEAN_PATH' error for import topology.basic
came up but it was not clear how to fix it, I think this was solved through some combination of restarts and calling leanpkg build / configure. Does anyone know what exactly causes this error?
It's hard to reply to some of these comments if you didn't follow the instructions closely. I've installed Lean a couple of times now following the instructions closely, and they worked for me. If you see a way to make the instructions more robust, feel free to tell us.
- Finally, the import fail 'not in LEAN_PATH' error for import topology.basic came up but it was not clear how to fix it, I think this was solved through some combination of restarts and calling leanpkg build / configure. Does anyone know what exactly causes this error?
This tends to happen if you open a .lean
file directly in VSCode without opening the whole project folder:
On the main screen, or in the File menu, click "Open folder" (on a Mac, just "Open"), and choose the folder my_project (not one of its subfolders).
It can of course also happen if you have skipped other steps from the installation instructions, like calling leanpkg configure
for the first time.
I think the latest version of the VS Code extension now warns users if they open a file in a folder without a leanpkg.path
file.
Last updated: Apr 24 2020 at 19:02 UTC


Before Change
// Receive data from the server and shut down
data = b""
while True:
packet = sock.recv(4096)
if not packet:
break
data += packet
received = pickle.loads(data)
all_phrases.extend(received)
print("vocab len = " + str(len(all_phrases)))
// print("Received: {}".format(received))
After Change
all_phrases_dict[p] = p
all_cut_phrases_dict[p] = p
else:
all_phrases_dict[p] = p[:max_phrase_length] + "..."
all_cut_phrases_dict[p[:max_phrase_length] + "..."] = p
print("done. vocab count = " + str(len(all_phrases)))
// pp = pprint.PrettyPrinter()


MH17 probe releases new phone calls linking suspects to top Russians
THE HAGUE: A Dutch-led probe into the shooting-down of flight MH17 over Ukraine in 2014 released new intercepted phone calls on Thursday (Nov 14) between high-ranking Russian officials and suspects facing trial over the crash.
Investigators said they were making a "new witness appeal" based on "recorded telephone calls between the leaders of the DPR (Donetsk People's Republic, a separatist group) and high-ranking Russian officials."
READ: Malaysia cannot easily accept some of investigation team’s findings on MH17: Mahathir
READ: Key questions as flight MH17 remembered five years on
"Ties between Russian officials and DPR leaders appear to have been much closer" than originally believed, Andy Kraag, the head of Dutch police's Criminal Investigations Division, said in a video statement.
Investigators said in June that they were going to put three Russian nationals and one Ukrainian on trial in the Netherlands in March 2020 over the downing of the Malaysia Airlines jet, although they will likely be tried in absentia.
The Boeing passenger plane travelling from Amsterdam to Kuala Lumpur was shot down by a Russian-made BUK missile in 2014 over eastern Ukraine. All 298 people on board were killed.
Investigators said on Thursday they had now established that Russian officials and the Ukrainian rebels used secure communications that "appear to be provided by the Russian security service FSB".
READ: Driver who transported missile that shot down MH17 jailed, says Ukraine
The special phones were "used by Russian top officials in their contact with the fighters", said David Nelson, senior investigations officer with the Australian Federal Police.
"We have released recorded conversations they had about administrative, financial and military business," Nelson added.


|
|
|
-
#Import the ispapi library
import ispapi
#Create a connection with the URL, entity, login and password
#Use "1234" as entity for the OT&E, and "54cd" for productive use
#Don't have a HEXONET Account yet? Get one here: www.hexonet.net/sign-up
api = ispapi.connect(
url = 'https://coreapi.1api.net/api/call.cgi',
entity = '1234',
login = 'test.user',
password = 'test.passw0rd'
)
#Call a command
response = api.call({
'Command': "QueryDomainList", 'limit' : 5
})
#Display the result in the format you want
$res = response.as_list()
$res = response.as_list_hash()
$res = response.as_hash()
#Get the response code and the response description
code = response.code()
description = response.description()
|


The advertising partnership campaign for PGDay 2009 has just started. There are several opportunities for you to join the advertising Partnership Campaign. It is also possible to directly sponsor a speaker for the conference.
http://www.enterprisedb.com/
http://www.dalibo.com/
http://www.telecom-paristech.fr/
http://www.2ndquadrant.com
http://www.cityvox.fr
http://www.continuent.com/
http://www.openwide.fr
http://www.redpill-linpro.com
http://www.siriusit.co.uk
http://bitnami.org/ http://www.postgresql-support.de http://www.gtportalbase.com/ http://www.objectwiz.org/ http://www.prosilient.com/


E-Mu Proteus 1, 1 XR, 2, 2 XR, 3, 3 XR, and 1+Orchestral
Single Patch Editor
This editor will work with the E-Mu Proteus 1, 2, 3, and 1+Orchestral, and with various XR versions, but you will need to set a switch to indicate which one you are using, as this will affect which patch numbers you can load and what instruments are available.
Communicating with Edisyn
Set the MIDI ID to the same you'll be setting in Edisyn (typically 0).
Using Different ROMs/soundsets
This patch editor is designed to work with standard Proteus models, not ones with changed or additional soundsets. If you have a common configuration that uses different instrument soundsets, send me email<EMAIL_ADDRESS>and we might discuss how the patch editor could be extended to accommodate them. It's not for the faint of heart, however.
Gotchas
- The different Proteus series machines have different instruments and different numbers of instruments. If you load or receive a patch meant for a different Proteus than you have set, then the instruments will not load properly and will be set to "No Instrument". (For example, if you have set Edisyn to Proteus 1 and you load a patch meant for a Proteus 3).
- The Proteus series cannot send to the current patch -- they can only write patches, or send individual parameters. But sending to the current patch is a critical function. Instead of (slowly) sending all parameters, Edisyn instead will write to patch 127. This means that you will overwrite this patch number every time you "send" the current patch.
- While the Proteus 2 and 3 can set the Chorus to OFF or to 1-15, the Proteus 1 can only actually set the Chorus to OFF and ON.
- You cannot request the current patch on the Proteus.
- In order to change patches (to request a new one or to send or write a patch), Edisyn must modify the Proteus's Program Map table: but there is no way to do this with precision. As such, Edisyn just obliterates the table in the process. I hope you didn't have anything in there that you wanted to keep.
| By
| Sean Luke
|
| Date
| January 2020
|


Notebooks
Story: The |xam and the hyena
The |xam and the hyena
Title
The |xam and the hyena
Collection
Summary
Youths hunt and kill hyena and then the people at home make soup out of their flesh. The people sleep after eating.
Comments
1) p.315v: an insert (pasted in) with 'Cape Colony' stamp addressed to 'Dr Bleek, Mowbray', 2) This story is found in Book I
Contributors
Date
17 May 1871 (started)
Categories
Keywords
hyena (|xam eat) , hyena (the hunting of) , hyena (is made into soup) , hyena (preparation of) , hyena (killing of) , hyena (cooking of) , hyena (young boys hunt) , hyena (the ground of) , hyena (the house of) , cooking (of hyenas by the |xam) , cooking (soup is made of hyena flesh) , food (|xam eat hyena) , food (soup made of hyena flesh) , soup (made of hyena flesh) , hunting (of hyena by |xam youths) , hunting (the |xam and the hyena)
Story Pages
316-320


Cook is a fast incremental build tool intended for projects in D programming language.
In contrast to most other build automation programs, Cook by default requires no project hierarchy description - it automatically collects information about imports from D source files in project directory. Moreover, Cook caches dependencies between modules, and then uses this cache to find out which modules had been changed and need recompiling.
Cook is written in D and supports Windows and Linux. By default it works with Digital Mars D compiler (DMD), but you can use it with other compilers (and, for some extent, even with other languages!) as well by writing proper configuration file.
To get the latest version of Cook, please visit project page at GitHub
Step-by-step Tutorial for Beginners - configuring typical workspace for DMD and Cook
Basic usage
Command Line Parameters
Using Configuration Files
External Dependencies
Response Files
Cross-Compilation


--- creation_epoch:<PHONE_NUMBER> lastmod_epoch:<PHONE_NUMBER> categories: ['Computer Problems'] title: P4G533-LA motherboard, Drivers, Windows 98 --- I have this motherboard that was pulled from an HP computer. I was trying to find drivers so I searched on the Internet. One person provided a link via HP that listed the drivers I would need, but they are out of date.
I decided to grab the drivers directly from the chipset maker's website instead. For reference these are what you need for Windows 98 (XP detects most of the stuff, don't bother with Vista).


Breakfast
Brunch
Lunch
Supper
Dinner
Only Fresh


To understand how it works, read this little Guide !
Framework Settings
In index.html you can find the Framework's settings :
- FRAMEWORK_CLASS : Represents the class name of the sections to be recognized by the
Framework and put in the left menu. By default : FW_SECTION.
- SHOW_SECTION_NAME : Sets the display of the section name in the corner. If True, sections must have a display-name attribute. True by default.
- CSS_PATH : The path to the directory containing your CSS files
- JS_PATH : The path to the directory containing your JS files
Framework Sections attributes
Framework's sections have several attributes :
- display-name : The name to display in the menu. If empty, id is used instead.
- script : The name of the optionnal script to use for this section.
- css-name : The name of the CSS file linked to this section. If empty, id is used
instead.
- center : If true, centers all the content inside of the section. False by default.


An AWS Certified Solutions Architect and serverless enthusiast with professional experience in backend development and databases.
6+ years in the software development industry, building web applications using NodeJS and Spring Boot.
Currently working as a lead software engineer at PayMaya Philippines, Inc.
AWS
An expert in designing robust, reliable, and secure solutions using various AWS services
Backend Development
Highly skilled in JavaScript (NodeJS), Java (Spring Boot), and databases (MySQL, PostgreSQL, MongoDB, Oracle)
Adept in RESTful APIs, microservices, and serverless architectures
DevOps
Experienced in DevOps, CI/CD pipelines, containerized applications (Docker), and Git
Technologies:
AWS, JavaScript (NodeJS, SailsJS), Java (Spring Boot), JPA (Hibernate), MySQL, PostgreSQL, Neptune, Microservices, Serverless, RESTful
Responsibilities:
Leading a team of one software and one quality engineer maintaining core backend services that power the company’s main products:
PayMaya (consumers),
PayMaya Negosyo (merchants), and
PayMaya Business Manager (enterprise).
Started out as a software engineer and got promoted to senior software engineer (Jan 2019), software engineering specialist (Sep 2020),
and lead software engineer (Jan 2021).
Technologies:
AWS, Serverless
Responsibilities:
Responsible for maintaining ReliefAgad’s infrastructure: deploying and monitoring in production, provisioning AWS resources needed by the development team,
and extracting database records to generate reports.
Designed a web application, SAP Search, that allows SAP (social amelioration program) beneficiaries to inquire about the disbursement status of their cash assistance.
Technologies:
Android, Java, Gradle, Firebase
Responsibilities:
Taught mobile application development using Android to 2nd year college students. Developed a comprehensive course outline in lieu of the college's standards. Prepared course materials, hands-on exercises, and project-based thesis.
Technologies:
Java (Spring MVC), JPA (EclipseLink), JavaScript (ExtJS), Oracle PL/SQL, RESTful, SOAP
Responsibilities:
Worked as a full-stack developer responsible for maintaining UI & backend web services, development of core modules, and 24/7 on-call support.
Conducted onboarding and training sessions for new hires.
Technologies:
Java, Web Services
Responsibilities:
Responsible for maintaining the reference number validation module of the web service used by the bank’s bills payment service.
I'm currently taking on freelance work.
I can help you with the following:
Drop me a line at jkmariano14@gmail.com


Alexander McQueen Introduces Chunky "Tread Slick" Sneaker Boots
Offered in low-top and calf-length silhouettes.
by Nia GroceAlexander McQueen is placing its bet on the chunky shoe game for Spring/Summer 2020. The British luxury label has just debuted the “Tread Slick” model as an update to the “Tread Boots” from Fall/Winter 2018, introducing a hefty, workwear-inspired sneaker boot in an array of primary colors.
Crafted in canvas along the upper, the shoes are offered in both low-top and calf-length editions. No matter the silhouette, the sneakers are defined across the board by their oversized rubber outsole, which measures in at approximately two inches thick. The style is offered in bright shades of royal blue and red, as well as neutrals of black and white to ensure for a transitional palette. Meanwhile, a choice of tonal or contrasting white laces provides the finishing touch.
The Tread Slick boots are priced at $690 USD and available to shop now at Alexander McQueen, as well as retailers such as LUISAVIAROMA.
Elsewhere for the brand, take a look at its Fall/Winter 2019 collection lookbook.
Alexander McQueen 50mm Logo Cotton Canvas Sneakers
Release Date
November 19, 2019 - 12:00 am EST (TBC)
Price
650 USD


Maybe you'd like to edit an existing entry or add a new record?
Simply go back to the home page and select the option you want to pursue.