Realm Swift: Question about Query-based public database - ios

I’ve seen all around the documentation that Query-based sync is deprecated, so I’m wondering how should I got about my situation:
In my app (using Realm Cloud), I have a list of User objects with some information about each user, like their username. Upon user login (using Firebase), I need to check the whole User database to see if their username is unique. If I make this common realm using Full Sync, then all the users would synchronize and cache the whole database for each change right? How can I prevent that, if I only want the users to get a list of other users’ information at a certain point, without caching or re-synchronizing anything?
I know it's a possible duplicate of this question, but things have probably changed in four years.

The new MongoDB Realm gives you access to server level functions. This feature would allow you to query the list of existing users (for example) for a specific user name and return true if found or false if not (there are other options as well).
Check out the Functions documentation and there are some examples of how to call it from macOS/iOS in the Call a function section
I don't know the use case or what your objects look like but an example function to calculate a sum would like something like this. This sums the first two elements in the array and returns their result;
your_realm_app.functions.sum([1, 2]) { sum, error in
if let err = error {
print(err.localizedDescription)
return
}
if case let .double(x) = result {
print(x)
}
}

Related

Firebase: Maintain a Username Directory

I am creating a Social app and want to track if a username already exists or not. The username list is supposed to grow in future and the way I was doing it now was a key value pair of <string,bolean> like this:
name1: true,
name2: true
all the above data was to be stored in a single document and whenever I want to see if a user exists I would call this document and check accordingly. But here's the problem, firebase max document size is 1MBs and as the users grow this can be problematic, so wanted to know from firebase experts that what's the best way to solve this use case in firestore or realtime database but since I need to query exists maybe realtime db won't suit that well.
Note that I don't want any of firestore querying capabilities but only to check if an entry exists in the record or not and if not just add it.
The Realtime Database doesn't have a 1MB limit (since it has no concept of a document, and everything is just a tree of JSON), so I'd typically use that for the index of user names.
Checking whether a name exists is pretty simple there too, and in JavaScript would look something like:
const usernames = firebase.database().ref('usernames');
usernames.child('name1').once((snapshot) => {
if (snapshot.exists()) {
...
}
});

Firebase: how to retrieve only changed items next time app launches?

I've got an app that uses a Firebase db containing 100,000 items. My app has to process through each of these items which takes several seconds.
What is happening is that every time the app is launched (from a terminated state) those 100,000 items are being processed each time (even if the contents of the db on the Firebase server have not changed). Obviously, I don't want the app to do this if not necessary. Here's some code:
if dbRef == nil {
FirebaseApp.configure();
Database.database().isPersistenceEnabled = true
...
let dbRef = Database.database().reference(withPath: kFirebaseDBName)
_ = spamRef.observe(DataEventType.value, with: { (theSnapshot) in
if let content = theSnapshot.value as? [String : AnyObject]
{
self.processContent(content: content)
}
Each time the app is started then the content snapshot contains the entire database reference contents.
Is there a way of, for example, getting the last date the database was updated (on the server), or only obtaining the delta of changed items between each app launch - can a query return just changed since last queried for example, or something similar?
I don't know how many items have changed so cannot call something like:
queryLimited(toLast: N))
As I don't know what value N is.
I've tried adding keepSynced as follows in the hope it might change things, but no.
if dbRef == nil {
FirebaseApp.configure();
Database.database().isPersistenceEnabled = true
...
let dbRef = Database.database().reference(withPath: kFirebaseDBName)
dbRef.keepSynced(true)
_ = dbRef.observe(DataEventType.value, with: { (theSnapshot) in
if let content = theSnapshot.value as? [String : AnyObject]
{
self.processContent(content: content)
}
I have no idea how much data might have changed so don't know what value to supply to something like toLast or similar to modify the observation parameters.
The database (which was not created nor updated with new content by me) has 100,000 items in a flat structure (i.e. one parent with 100,000 children) and any number of these children in any order might have been deleted and replaced since last time my app ran, but the total will still be 100,000. None of the children have an explicit timestamp or anything like that.
I was under the impression if Firebase kept a local cache of the data (due to isPersistenceEnabled) then next time it connects with the server it would only sync what had changed on the server. Therefore in order to do this Firebase itself must internally have some delta information somewhere, so I was hoping that delta information may available in some form to my app.
Note: My app does not need persistence to be enabled, the above code is doing so just as variations to see if anything will result in the behavior I desire with the observer.
UPDATE
So looking at the documentation more you can set a timestamp for the last time a user was connected to the server using:
lastOnlineRef.onDisconnectSetValue(ServerValue.timestamp())
Take a look at this question Frank explains some issues with persistence and listeners. The question is for Android but the principles are the same.
I still think the problem is your query. Since you already have the data persisted .value is not what you want since this returns all of the data.
I think you want to attach a .childChanged listener to your query. In this case the query will only return the data that has been changed. If you haven't heard of .childChanged before you can read about it here.
I didn't realize this problem is specifically related to persistence. I think you are looking for keepSynced(). Take a look at this.
ORIGINAL ANSWER
The problem is your query. You are asking for all of the data that's why you're getting all of the data. You want to look into limiting your queries using toFirst or toLast. Additionally, I don't think you can query for the last time the database was updated. You could check the last node in your data structure if you have the timestamp saved, but you might as well just get the newest data.
You want something like this:
ref.child("yourChild").queryLimited(toLast: 7).observeSingleEvent(of: .value, with: { snap in
// do something
})
Depending on how you're writing your data you'll want toLast or toFirst. Assuming the newest data is written last toLast is what you want. Also note that the numbers I am limiting to are arbitrary you can use any number that fits your project.
If you already have a key and you want to start querying above that key you can do something like this:
ref.child("YourChild").queryOrderedByKey().queryEnding(atValue: lastVisiblePostKey).queryLimited(toLast: 8).observeSingleEvent(of: .value, with: { snap in
// do something with more posts
})
You may also want to look into this question, this question and pagination.

Determining If CKRecord Was Created By Current User

I'm new to CloudKit and I was trying to figure out if a Record was created by the current user. I have researched this topic and have come about two methods to do this. I'm not sure which one is right or better and I don't even quite understand how the second method works.
The first way is using the following method to get the current user and then comparing it to the user who created the record:
func fetchUserRecordID(
completionHandler: (recordID: CKRecordID?, error: CKError?) -> Void
)
The second way involves an extension on CKRecord:
extension CKRecord{
var wasCreatedByThisUser: Bool{
return (creatorUserRecordID == nil) || (creatorUserRecordID?.recordName == "__defaultOwner__")
}
}
The first method is making another call to the server to fetch an additional record. The downside is that costs time, it counts against your monthly traffic quotas, and you have yet another async callback function that your code flow will have to account for. If you wind up calling this check a lot, you would generate a lot of unnecessary traffic to the server.
The second method is checking a value, creatorUserRecordID, that came with the record you already fetched. So at the time you check its value, it's all local data, no additional calls to the server and no async processing required.
Per the answer here: creatorUserRecordID.recordName contains "__defaultOwner__" instead of UUID shown in Dashboard, __defaultOwner__ is a synonym for the local user.
The second method looks to be the better choice for most scenarios I can think of.

RestKit - use identificationAttributes that are not part of the response

In RestKit is it possible to use identificationAttributes that are actually not part of the JSON response?
My case is the following - I have a service that lists all articles for the currently logged-in user like http://example.com/json/articles.json
My problem is the following - since the application allows multiple users to login, I keep the articles in the database together with the userId for each article. If I set the articleMapping.identificationattributes = #["articleId"], then I have a problem if two users using the device have the same article - it will be overwritten regardless of the userId, because it is not part of the response.
To sum up the facts:
For the JSON request I do not send the userId, it is part of the
server session only, so I think that I cannot use RKRoute
I do the mapping of the article with the user manually after RestKit mapping.
I do not have the userId property as part of the JSON response, it exists only inside the ArticleManagedObject.
Is there a way to inform RestKit that during the mapping, it should check the articleId+userId combination as an identificator? I tried using identificationPredicate with no success.
EDIT:
An example response from the server, when UserA is logged in:
{
"data":{
"articles":[
{
"articleId":1,
"title":"Objective C Basics"
},
{
"articleId":2,
"title":"Xcode Basics"
}
]
}
}
and here is the response when UserB is logged in:
{
"data":{
"articles":[
{
"articleId":1,
"title":"Objective C Basics"
},
{
"articleId":3,
"title":"Java Basics"
}
]
}
}
If UserA logs in, everything is fine. But if UserB logs in from the same device, then article 1 is mapped to UserB, and from now on, the connection between UserA and article 1 is lost.
As I understand from your suggestion, the only solution is to return also the user id from the service, set RKUnionAssignmentPolicy and let RestKit take care of the mapping (currently I am manually making the mapping between articles and users after RestKit).
Another question that I have - is it possible to set the identificationAttributes or identificationPredicate so that it makes a separation between object article 1 for UserA and object article 1 for UserB.
You currently do the user to article mapping outside RestKit, this is fine, but you will need to modify this process a little.
To begin with, I'm assuming here that the article response is the full set of articles for the user. If not then things get more tricky and you'll need to modify the below to account:
Start by getting all of the existing articles for a user. With this we're going to look at what needs to be removed and what needs to be added.
As we iterate through the articles we have received we can check the existing articles for a match, if we find one we have no work to do. If we don't find a match we need to add the relationship to the existing set, which will be a union with any relationship to any other user.
Next we want to remove the list of new articles from the list of the existing articles to get the list of deletions, for these we just need to break the link, again leaving other users unchanged.

What is available for limiting the use of extend when using Breezejs, such users cant get access to sensitive data

Basically this comes up as one of the related posts:
Isn't it dangerous to have query information in javascript using breezejs?
It was someone what my first question was about, but accepting the asnwers there, i really would appreciate if someone had examples or tutorials on how to limit the scope of whats visible to the client.
I started out with the Knockout/Breeze template and changed it for what i am doing. Sitting with a almost finished project with one concern. Security.
I have authentication fixed and is working on authorization and trying to figure out how make sure people cant get something that was not intended for them to see.
I got the first layer fixed on the root model that a member can only see stuff he created or that is public. But a user may hax together a query using extend to fetch Object.Member.Identities. Meaning he get all the identities for public objects.
Are there any tutorials out there that could help me out limiting what the user may query.?
Should i wrap the returned objects with a ObjectDto and when creating that i can verify that it do not include sensitive information?
Its nice that its up to me how i do it, but some tutorials would be nice with some pointers.
Code
controller
public IQueryable<Project> Projects()
{
//var q = Request.GetQueryNameValuePairs().FirstOrDefault(k=>k.Key.ToLower()=="$expand").Value;
// if (!ClaimsAuthorization.CheckAccess("Projects", q))
// throw new WebException("HET");// UnauthorizedAccessException("You requested something you do not have permission too");// HttpResponseException(HttpStatusCode.MethodNotAllowed);
return _repository.Projects;
}
_repository
public DbQuery<Project> Projects
{
get
{
var memberid = User.FindFirst("MemberId");
if (memberid == null)
return (DbQuery<Project>)(Context.Projects.Where(p=>p.IsPublic));
var id = int.Parse(memberid.Value);
return ((DbQuery<Project>)Context.Projects.Where(p => p.CreatedByMemberId == id || p.IsPublic));
}
}
Look at applying the Web API's [Queryable(AllowedQueryOptions=...)] attribute to the method or doing some equivalent restrictive operation. If you do this a lot, you can subclass QueryableAttribute to suit your needs. See the Web API documentation covering these scenarios.
It's pretty easy to close down the options available on one or all of your controller's query methods.
Remember also that you have access to the request query string from inside your action method. You can check quickly for "$expand" and "$select" and throw your own exception. It's not that much more difficult to block an expand for known navigation paths (you can create white and black lists). Finally, as a last line of defense, you can filter for types, properties, and values with a Web API action filter or by customizing the JSON formatter.
The larger question of using authorization in data hiding/filtering is something we'll be talking about soon. The short of it is: "Where you're really worried, use DTOs".

Resources