I am working on an app that requires a sync to the server after logging in to get all the activities the user has created and saved to the server. Currently, when the user logs in a getActivity() function that makes an API request and returns a response which is then handled.
Say the user has 4 activities saved on the server in this order (The order is determined by the time of the activity being created / saved) ;
Test
Bob
cvb
Testing
looking at the JSONHandler.getActivityResponse , it appears as though the the results are in the correct order. If the request was successful, on the home page where these activities are to be displayed, I currently loop through them like so;
WebAPIHandler.shared.getActivityRequest(completion:
{
success, results in DispatchQueue.main.async {
if(success)
{
for _ in (results)!
{
guard let managedObjectContext = self.managedObjectContext else { return }
let activity = Activity(context: managedObjectContext)
activity.name = results![WebAPIHandler.shared.idCount].name
print("activity name is - \(activity.name)")
WebAPIHandler.shared.idCount += 1
}
}
And the print within the for loop is also outputting in the expected order;
activity name is - Optional("Test")
activity name is - Optional("Bob")
activity name is - Optional("cvb")
activity name is - Optional("Testing")
The CollectionView does then insert new cells, but it seemingly in the wrong order. I'm using a carousel layout on the home page, and the 'cvb' object for example is appearing first in the list, and 'bob' is third in the list. I am using the following
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?)
{
switch (type)
{
case .insert:
if var indexPath = newIndexPath
{
// var itemCount = 0
// var arrayWithIndexPaths: [IndexPath] = []
//
// for _ in 0..<(WebAPIHandler.shared.idCount)
// {
// itemCount += 1
//
// arrayWithIndexPaths.append(IndexPath(item: itemCount - 1, section: 0))
// print("itemCount = \(itemCount)")
// }
print("Insert object")
// walkThroughCollectionView.insertItems(at: arrayWithIndexPaths)
walkThroughCollectionView.reloadData()
}
You can see why I've tried to use collectionView.insertItems() but that would cause an error stating:
Invalid update: invalid number of items in section 0. The number of items contained in an existing section after the update (4) must be equal to the number of items contained in that section before the update (4), plus or minus the number of items inserted or deleted from that section (4 inserted, 0 deleted)
I saw a lot of other answers mentioning how reloadData() would fix the issue, but I'm real stuck at this point. I've been using swift for several months now, and this has been the first time I'm truly at a loss. What I also realised is that the order displayed in the carousel is also different to a separate viewController which is passed the same data. I just have no idea why the results return in the correct order, but are then displayed in an incorrect order. Is there a way to sort data in the collectionView after calling reloadData() or am I looking at this from the wrong angle?
Any help would be much appreciated, cheers!
The order of the collection view is specified by the sort descriptor(s) of the fetched results controller.
Usually the workflow of inserting a new NSManagedObject is
Insert the new object into the managed object context.
Save the context. This calls the delegate methods controllerWillChangeContent, controller(:didChange:at: etc.
In controller(:didChange:at: insert the cell into the collection view with insertItems(at:, nothing else. Do not call reloadData() in this method.
Related
I have spent the last hour trying to look for a solution to rate limit my api.
I want to limit a path /users for example. But most rate limits work on 1 rate limit for everyone. I want to use api keys that can be generated by a user. People can generate free api let's say 1000 requests per day. Then if they pay some money they can get 5000 requests per day.
I would like to store these api keys in a mysql database.
Does anyone have any solution for this?
One way to structure your project would be:
user_keys table, includes the api key, the user, time of creation and number of uses so far.
When a user tries to generate a key, check that one doesn't exist yet, and add it to the DB.
When a request arrives, check if the key exists, if it does, do the following:
1: if it has been 24 hours since creation date, set number of uses to 0
2: increment the uses count
if you find the API key and it's at 1k the user reached his limit.
This is a basic implementation, and isn't very efficient, you'll want to cache the API keys in memory, either just in a hashmap in nodejs or using memcached/redis. But, get it working first before trying to optimize it.
EDIT: some code examples
//overly simple in memory cache
const apiKeys = {}
//one day's worth in milliseconds, used later on
const oneDayTime = 1000 * 60 * 60 * 24
//function to generate new API keys
function generateKey(user) {
if (apiKeys[user]) {
throw Error("user already has a key")
}
let key = makeRandomHash(); // just some function that creates a random string like "H#4/&DA23#$X/"
//share object so it can be reached by either key or user
//terrible idea, but when you save this in mysql you can just do a normal search query
apiKeys[user] = {
key: key,
user: user,
checked: Date.Now(),
uses: 0
}
apiKeys[key] = apiKeys[user]
}
// a function that does all the key verification for us
function isValid(key) {
//check if key even exists first
if (!apiKeys[key]) throw Error("invalid key")
//if it's been a whole day since it was last checked, reset its uses
if (Date.now() - apiKeys[key].checked >= oneDayTime) {
apiKeys[key].uses = 0
apiKeys[key].checked = Date.now()
}
//check if the user limit cap is reached
if (apiKeys[key].uses >= 1000) throw error("User daily qouta reached");
//increment the user's count and exit the function without errors
apiKeys[key].uses++;
}
//express middleware function
function limiter(req, res, next) {
try {
// get the API key, can be anywhere, part of json or in the header or even get query
let key = req.body["api_key"]
// if key is not valid, it will error out
isValid(key)
// pass on to the next function if there were no errors
next()
} catch (e) {
req.send(e)
}
}
this is an overly simplified implementation of a simpler idea, but I hope it gets the idea across.
the main thing you want to change here is how the API keys are saved and retrieved
I want to scan my Dynamo db table with pagination applied to it. In my request I want to send the number from where I want pagination to get start. Say, e.g. I am sending request with start = 3 and limit = 10, where start is I want scan to start with third item in the table and limit is upto 10 items. Limit however I can implement with .withLimit() method(I am using java). I followed this aws document. Following is the code of what I want to achieve:
<Map<String, AttributeValue>> mapList = new ArrayList<>();
AmazonDynamoDB client =AmazonDynamoDBClientBuilder.standard().build();
Gson gson = new GsonBuilder().serializeNulls().create();
Map<String, AttributeValue> expressionAttributeValues = new
HashMap<String,AttributeValue>();
expressionAttributeValues.put(":name",
newAttributeValue().withS(name));
List<ResponseDomain> domainList = new ArrayList<>();
ResponseDomain responseDomain = null;
//lastKeyEvaluated = start
Map<String, AttributeValue> lastKeyEvaluated = null;
do {
ScanRequest scanRequest = new
ScanRequest().withTableName(STUDENT_TABLE)
.withProjectionExpression("studentId, studentName")
.withFilterExpression("begins_with(studentName, :name)")
.withExpressionAttributeValues(expressionAttributeValues).
withExclusiveStartKey(lastKeyEvaluated);
ScanResult result = client.scan(scanRequest);
for (Map<String, AttributeValue> item : result.getItems()) {
responseDomain = gson.fromJson(gson.toJson(item),
ResponseDomain.class);
domainList.add(responseDomain);
} lastKeyEvaluated = result.getLastEvaluatedKey();
} while (lastKeyEvaluated!= null);
//lastKeyEvaluated = size
return responseDomain;
In the above code I am stuck at 3 places:
How can I set lastKeyEvaluated as my start value i.e 3
In the while condition how can I specify my limit i.e 10
When I try to map item from Json to my domain class, I encounter error.
Am I misinterpreting the concept of pagination in dynamodb or doing something wrong in the code. Any guidance will be highly appreciated as I am a newbie.
You can only start reading from some place by the ExclusiveStartKey. This key is the primary key of your table. If you know your item key, you can use it like this (e.g. your table primary key is studentId):
Map<String, AttributeValue> lastKeyEvaluated = new HashMap<String,AttributeValue>();
lastKeyEvaluated.put("studentId", new AttributeValue(STUDENTID));
When you specify the limit = N in dynamo you are setting that it should only read N items from the table. Any filtering is applied after the N items have been read. See Limiting the Number of Items in the Result Set.
That might leave you with less results than expected. So you could create a variable in your code to send requests until you hit your expected limit and cutoff the extra results.
int N = 10;
List<Map<String, AttributeValue>> itemsList = new ArrayList<>();
do {
// scanRequest.withLimit(N)
...
itemList.addAll(result.getItems());
if(itemsList.size() >= N) {
itemsList = itemsList.subList(0, N);
break;
}
} while (lastKeyEvaluated != null && itemsList.size() < N);
// process the itemsList
Dynamo uses it’s own json structure. See Dynamo response syntax.
You can get the value of an attribute the way you stored it in dynamo. If studentId is a string then it could be something like this:
for (Map<String, AttributeValue> item : result.getItems()) {
responseDomain = new ResponseDomain();
responseDomain.setId(item.get("studentId").getS());
domainList.add(responseDomain);
}
Pagination doesn't quite work the way you are thinking.
Use ScanRequest.withLimit(X) to choose the number of items in each page of results. For example, setting ScanRequest.withLimit(10) means that each page of results you get will have 10 items in it.
The lastKeyEvaluated is not a number of a page, it is the actual key of an item in the table. Specifically it is the key of the last item in the last set of results you retrieved.
Think about it like this. Imagine your results are:
Dog
Chicken
Cat
Cow
Rhino
Buffalo
Now lets say I did ScanRequest.withLimit(2) and lastKeyEvaluated = null, so each page of results has 2 items and I will retrive the first page of results. My first scan returns
Dog
Chicken
And
lastKeyEvaluated = result.getLastEvaluatedKey();
Returns
Chicken
And now to get the next page of results I would use ScanRequest.withExclusiveStartKey(Chicken). And the next set of results would be
Cat
Cow
Your code above uses a do/while loop to retrieve every page of results and print it out. Most likely you will want to remove that do/while loop so that you can handle one page at a time, then retrieve the next page when you are ready.
I am trying to automate API requests using postman. So first in POST request I wrote a test to store all created IDs in Environment : Which is passing correct.
var jsondata = JSON.parse(responseBody);
tests["Status code is 201"] = responseCode.code === 201;
postman.setEnvironmentVariable("BrandID", jsondata.brand_id);
Then in Delete request I call my Environment in my url like /{{BrandID}} but it is deleting only the last record. So my guess is that environment is keeping only the last ID? What must I do to keep all IDs?
Each time you call your POST request, you overwrite your environment variable
So you can only delete the last one.
In order to process multiple ids, you shall build an array by adding new id at each call
You may proceed as follows in your POST request
my_array = postman.getEnvironmentVariable("BrandID");
if (my_array === undefined) // first time
{
postman.setEnvironmentVariable("BrandID", jsondata.brand_id); // creates your env var with first brand id
}
else
{
postman.setEnvironmentVariable("BrandID", array + "," + jsondata.brand_id); // updates your env var with next brand id
}
You should end up having an environment variable like BrandId = "brand_id1, brand_id2, etc..."
Then when you delete it, you delete the complete array (but that depends on your delete API)
I guess there may be cleaner ways to do so, but I'm not an expert in Postman nor Javascript, though that should work (at least for the environment variable creation).
Alexandre
I was wondering if someone could help me out.
I have 2 sets of data being returned from an API which i need to chart to a line graph, the problem is i dont know which set of results will come at any given time, so i need something that will be able to chart both sets of the results.
The first set has the following info, ( taken from a var_dump )
'series' => string '[{name: 'Data'}]'
'data_lines' => string '[[[1473731108000,3.4804],[1473731406000,1.7047],[1473731704000,1.7559],[1473732004000,1.2774],[1473732304000,1.9295]]]'
The first number is a timestamp, and the second number is the plot point in GB, and the series name is the series name obviously.
The above set of results only needs one line, the second set of data is an averaging set of results and needs 3 lines, its response is as follows:
'series' => string '[{name: 'Data (average)'},{name: 'Data (maximum)'},{name: 'Data (minimum)'}]'
'data_lines' => string '[[[1473638400000,1.5094]],[[1473638400000,6.7825]],[[1473638400000,1.0546]]]'
and this one obviously has 3 data values for the 3 labels per timestamp.
Any help in getting this going would be greatly appreciated.
This is my sugestion: Create a function that will act like a router for your results. I don't know how you are processing your results (PHP or javascript), BTW:
function chartSelector($data) {
if (count($data['series']) == 3) {
//put the logic to process the averaging set of results here
} else {
//put the logic for the other set here
}
}
This function will direct the processing according to the given set of data :)
I have a select that I get Json post with http, but I try to sets initially selected index but there is nothing in the list do not select anything. because the json is great.
public AppMainScreen() {
loadLists();
MySelect = new ObjectChoiceField( "Select: ", new Object[0], 3 );
VerticalFieldManager vfm = new VerticalFieldManager(Manager.VERTICAL_SCROLL);
vfm.add(MySelect);
add(vfm);
}
This statement appears wrong to me:
new ObjectChoiceField( "Select: ", new Object[0],3);
The second parameter to this constructor is supposed to be an array of objects whose .toString() method will be used to populate the choices. In this case, you have given it a 0 length array, i.e. no Objects. So there is nothing to choose. And then you have asked it to automatically select the 3rd item, and of course there is no 3rd item.
You should correct the code to actually supply an object array.
One option to make it easy is have your JSON load actually create a String array with one entry per selectable item. Then you use the index selected to identify the chosen item.