according to graphApi's documentation :
Cursor-based Pagination
Cursor-based pagination is the most efficient method of paging and should always be used where possible. A
cursor refers to a random string of characters which marks a specific
item in a list of data. Unless this item is deleted, the cursor will
always point to the same part of the list, but is be invalidated if an
item is removed. Therefore, your app shouldn't store any older cursors
or assume that they will still be valid.
When reading an edge that supports cursor pagination, you will see the
following JSON response:
{
"data": [
... Endpoint data is here
],
"paging": {
"cursors": {
"after": "MTAxNTExOTQ1MjAwNzI5NDE=",
"before": "NDMyNzQyODI3OTQw"
},
"previous": "https://graph.facebook.com/me/albums?limit=25&before=NDMyNzQyODI3OTQw"
"next": "https://graph.facebook.com/me/albums?limit=25&after=MTAxNTExOTQ1MjAwNzI5NDE="
}
}
but i have no idea what's going on up here, anyone can point me on how we do Cursor-based Pagination here? i've done similar thing by passing the Max_id but in here its not the case
this is how i'm making my first call
accessKey = "\(appID)|\(appSecret)"
let connection = GraphRequestConnection()
let request = GraphRequest.init(graphPath:"/myPageName/posts", parameters: [ "access_token" : accessKey, "fields" : "message,full_picture,created_time,story,type,permalink_url" , "limit": "10"], accessToken: nil, httpMethod: .GET, apiVersion: "2.8")
connection.add(request) { httpResponse, result in
switch result {
case .success(let response):
//FETCHED DATA HERE///
case .failed(let error):
print("Graph Request Failed: \(error)")
}
}
connection.start()
(Posted on behalf of the OP).
Passing offset in parameter did the job.
For this lets first understand Cursor paging with an example :
Let’s assume we want to paginate from the most recent user to the oldest user.When client request for the first time , suppose we select the first page through query:
SELECT * FROM users
WHERE team_id = %team_id
ORDER BY id DESC
LIMIT %limit
Where limit is equal to limit plus one, to fetch one more result than the count specified by the client. The extra result isn’t returned in the result set, but we use the ID of the value as the next_cursor.
The response from the server would be:
{
"users": [...],
"next_cursor": "1234", # the user id of the extra result
}
The client would then provide next_cursor as cursor in the second request.
SELECT * FROM users
WHERE team_id = %team_id
AND id <= %cursor
ORDER BY id DESC
LIMIT %limit
Coming back to Facebook Implementation : As the name suggests the next cursor will fetch you the next set of results and the previous cursor will fetch you the previous set of results.
If you still have problem understanding Cursors and why they are used you can check out this article. The above example is quoted from the same.
Related
I'm designing a RESTful API, returns result with pagination and total number.
Here is my approach
Endpoint
[GET] /items
Parameters
Parameter
Description
before_cursor
Return results before cursor
after_cursor
Return results after cursor
limit
Limit of result
Response
{
"items": [
...
],
"pagination": {
"prev_cursor": string, // cursor before the range, for backward navigation
"next_cursor": string, // cursor after the range, for forward navigation
"total": number, // total number of items
"total_before": number, // item count before prev_cursor helps client positioning
"total_after": number // item count before next_cursor helps client positioning
}
}
Example
Raw data
Item_0, Item_1, ... Item_99
Get first page
[GET] /items?limit=10
{
"items": [item_0, ..., item_9],
"pagination": {
"next_cursor": "item_10",
"total": 100,
"total_before": 0,
"total_after": 90
}
}
Navigate to next page
[GET] /items?limit=10&after_cursor=item_10
{
"items": [item_10, ..., item_19],
"pagination": {
"before_cursor": "item_9",
"next_cursor": "item_20",
"total": 100,
"total_before": 10,
"total_after": 80
}
}
Navigate back to previous page
[GET] /items?limit=10&before_cursor=item_9
{
"items": [item_0, ..., item_9],
"pagination": {
"next_cursor": "item_10",
"total": 100,
"total_before": 0,
"total_after": 90
}
}
Is it a good approach? Or is there a neater way to design API for this requirement?
There are multiple ways to design this endpoint with a rest approach. I found these 2 the best when you need to deal with collections.
Approach 1
GET /items?page=2 & page_size=10
This should return only 10 elements from your resource items and skip the first page (it is up to you to decide if you start your collection with a 0 or with 1)
A response should look like this
Response 1:
[
item1,
item2,
...
item10
]
Response Header: RecordCount or X-Total-Count = 530
The total count in this case is included in the headers.
Now if the user wants to paginate the next page or the previous they can do the following request:
next: GET /items?page=3 & page_size=10
previous: GET /items?page=1 & page_size=10
Including this request as part of the payload and trying to comply with Rest for HATEOAS is what I think can be the second approach
Response 2:
{
TotalCount=530,
Result:
[
item1,
item2,
...
item10
],
Next: '/items?page=3 & page_size=10'
Previous: 'GET /items?page=1 & page_size=10'
}
In this case, the total count is included in the response and the items on the result and you build the next and previous URLs
I am making a react app that searches for a book by title and returns the results.
It's mostly working fine, but for some titles searched (such as "hello") it can't get the results because the parameters are missing.
Specially, the "amount" value is missing, and it can get me e-books that are not for sale even if I add the filter=paid-ebooks param while fetching the api. Using projection=full doesn't help either.
For example, when I call the api with
https://www.googleapis.com/books/v1/volumes?printType=books&filter=paid-ebooks&key=${APIKEY}
and use the fetched data inside books array in reactjs:
this.props.books.map((book, index) => {
return (
<CardItem
key={index}
title={book.volumeInfo.title}
authors={book.volumeInfo.authors ?
book.volumeInfo.authors.join(', ') :
"Not provided"}
price={book.saleInfo.listPrice.amount}
publisher={book.volumeInfo.publisher}
addToCart={() =>
this.props.addItem(this.props.books[index])}
/>
)
})
One of the results it gets is like this:
"saleInfo": {
"country": "TR",
"saleability": "NOT_FOR_SALE",
"isEbook": false
}
While it should be like, what's expected is :
"saleInfo": {
"country": "TR",
"saleability": "FOR_SALE",
"isEbook": true,
"listPrice": {
"amount": 17.23,
"currencyCode": "TRY"
}
And trying to search with this api answer throws the error :
TypeError: Cannot read property 'amount' of undefined
price={book.saleInfo.listPrice.amount}
As you can see in react code's authors, this issue comes up with authors parameter too, which I've bypassed as seen in the code. But I cannot do the same with amount. Is this a known error in Google Books API or is there a way to prevent this? I don't understand why it still returns me e-books that are not for sale even with filter=paid-ebooks param.
I have not dug into the API documentation. An ideal solution would be a query param that only sends back books with a list price (like you tried with filter=paid-ebooks). Because that's not working, a simple fix would be to filter your results once you get them.
Assuming the response contains an array of book objects, it would look something like this:
const paidBooks = apiResponse.data.filter(book => book.listPrice)
This code will take the response from the API, and filter out all books that do not contain a truthy value for listPrice
That totally right, actually i never used react but the same logic try using try{ }catch(error){} for those missing data
I'm fairly new to REST. All of our legacy webservices were SOAP based with enterprise (ORACLE or DB2) databases. We are now moving to REST/couchbase.
Our team is looking into implementing a complex query method. We already have implemented simple query methods using GET, for example GET returns all entries and a GET/067e6162-3b6f-4ae2-a171-2470b63dff00 would return the entry for 067e6162-3b6f-4ae2-a171-2470b63dff00.
We want to support a query method that would support receiving several query parameters such a list of Ids and date ranges. The number of Ids can number into a few thousand and because of this, we realize we cannot pass these query parameters in a GET HTTP header since there is a limit on header size.
We are starting to look into passing our query parameters into the JSON body of a POST request. For example, we could have client pass in a few thousand Ids as an array and also pass in a date range, so we'd have each query param/filter be an object. The JSON body would then be an array of objects. For example:
{
"action" : "search",
"queryParameters" : {
[
{
“operation”: “in”,
"key" : "name.of.attribute.Id",
"value" : "[{ "id: "067e6162-3b6f-4ae2-a171-2470b63dff00"}, {"id": "next id"....}],
},
{
“operation”: “greater”,
"key" : "name.of.attribute “,
"value" : "8/20/2016"
},
{
“operation”: “less”,
"key" : "name.of.attribute “,
"value" : "8/31/2016"
}
]
}
The back end code would then receive POST and read the body. It would see action is a search and then look for any entries in the list that are in the list of Ids that are in the date range of > 8/20/2016 and < 8/31/2016.
I've been trying to look online for tips/best practices on how best to structure the JSON body for complex queries but have not found much. So any tips, guidance or advice would be greatly appreciated.
thanks.
I'm using Postman to make REST API calls to a server. I want to make the name field dynamic so I can run the request with a unique name every time.
{
"location":
{
"name": "Testuser2", // this should be unique, eg. Testuser3, Testuser4, etc
"branding_domain_id": "52f9f8e2-72b7-0029-2dfa-84729e59dfee",
"parent_id": "52f9f8e2-731f-b2e1-2dfa-e901218d03d9"
}
}
In Postman you want to use Dynamic Variables.
The JSON you post would look like this:
{
"location":
{
"name": "{{$guid}}",
"branding_domain_id": "52f9f8e2-72b7-0029-2dfa-84729e59dfee",
"parent_id": "52f9f8e2-731f-b2e1-2dfa-e901218d03d9"
}
}
Note that this will give you a GUID (you also have the option to use ints or timestamps) and I'm not currently aware of a way to inject strings (say, from a test file or a data generation utility).
In Postman you can pass random integer which ranges from 0 to 1000, in your data you can use it as
{
"location":
{
"name": "Testuser{{$randomInt}}",
"branding_domain_id": "52f9f8e2-72b7-0029-2dfa-84729e59dfee",
"parent_id": "52f9f8e2-731f-b2e1-2dfa-e901218d03d9"
}
}
Just my 5 cents to this matter. When using randomInt there is always a chance that the number might eventually be present in the DB which can cause issues.
Solution (for me at least) is to use $timestamp instead.
Example:
{
"username": "test{{$timestamp}}",
"password": "test"
}
For anyone who's about to downvote me this post was made before the discussion in comments with the OP (see below). I'm leaving it in place so the comment from the OP which eventually described what he needs isn't removed from the question.
From what I understand you're looking for, here's a basic solution. It's assuming that:
you're developing some kind of script where you need test data
the name field should be unique each time it's run
If your question was more specific then I'd be able to give you a more specific answer, but this is the best I can do from what's there right now.
var counter = location.hash ? parseInt(location.hash.slice(1)) : 1; // get a unique counter from the URL
var unique_name = 'Testuser' + counter; // create a unique name
location.hash = ++counter; // increase the counter by 1
You can forcibly change the counter by looking in the address bar and changing the URL from ending in #1 to #5, etc.
You can then use the variable name when you build your object:
var location = {
name: unique_name,
branding_domain_id: 'however-you-currently-get-it',
parent_id: 'however-you-currently-get-it'
};
Add the below text in pre-req:
var myUUID = require('uuid').v4();
pm.environment.set('myUUID', myUUID);
and use the myUUID wherever you want
like
name: "{{myUUID}}"
It will generate a random unique GUID for every request
var uuid = require('uuid');
pm.globals.set('unique_name', 'testuser' + uuid.v4());
add above code to the pre-request tab.
this was you can reuse the unique name for subsequent api calls.
Dynamic variable like randomInt, or guid is dynamic ie : you donot know what was send in the request. there is no way to refer it again, unless it is send back in response. even if you store it in a variable,it will still be dynamic
another way is :
var allowed = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
var shuffled_unique_str = allowed.split('').sort(function(){return 0.5-Math.random()}).join('');
courtsey refer this link for more options
Given a JSON document on couchbase, for example, a milestone collections, which is similar to this:
{
"milestoneDate" : /Date(1335191824495+0100)/,
"companyId" : 43,
"ownerUserId": 475,
"participants" : [
{
"userId": 2,
"docId" : "132546"
},
{
"userId": 67,
"docId" : "153"
}
]
}
If I were to select all the milestones of the company 43 and want to order them by latest first.. my view on couchbase would be something similar to this:
function (doc, meta) {
if(doc.companyId && doc.milestoneDate)
{
//key made up of date particles + company id
var eventKey = dateToArray(new Date(parseInt(doc.milestoneDate.substr(6))));
eventKey.push(doc.companyId);
emit(eventKey, null);
}
}
I do get both dates and the company Id on rest urls.. however, being quite new to couchbase, I am unable to work out how to restrict the view to return only milestones of company 43
The return key is similar to this:
"key":[2013,6,19,16,11,25,14]
where the last element (14) is the company id.. which is quite obviously wrong.
The query parameters that I have tried are:
&descending=true&startkey=[{},43]
&descending=true&startkey=[{},43]&endKey=[{},43]
tried adding companyId to value but couldn't restrict return results by value.
And according to couchbase documentation I need the date parts in the beginning to sort them. How do I restrict them by company id now, please?
thanks.
Put the company id at the start of the array, and because you'll be limiting by company id, couchbase sorts by company id and then by date array so you will be only ever getting the one company's milestone documents
I'd modify the view to emit
emit([doc.copmanyId, eventKey], null);
and then you can query the view with
&descending=true&startkey=[43,{}]
This was what worked for me previously..
I went back and tried it with end key and this seems to work - restricts and orders as required:
&descending=true&startkey=[43,{}]&endkey=[42,{}]
or
&descending=true&startkey=[43,{}]&endkey=[43,{}]&inclusive_end=true
either specify the next incremented/decremented value (based on descending flag) with end key, or use the same endkey as startkey and set inclusiveEnd to true
Both of these options should work fine. (I only tested the one with endkey=42 but they should both work)