Azure Mobile Service Sync with select clause not supported? - xamarin.forms

I’ve had a sync operation in place in my Xamarin Forms app for a long time now and only this pase couple of weeks has it started throwing exceptions, which makes me think maybe it’s a service change or something introduced in an update?
On startup I sync all data with m Azure Mobile Service using:
await this.client.SyncContext.PushAsync();
if (signedInUser != Guid.Empty)
{
await this.MyTable.PullAsync(
"myWorkoutsOnly",
this.MyTable.CreateQuery().Select(u => u.UserId == signedInUser));
}
And as I say, I’ve never had an issue with this code. Now though, I’m getting:
System.ArgumentException: Pull query with select clause is not supported
I only want to sync the data that matches the signed in user, so is there another way to achieve this?

I only want to sync the data that matches the signed in user, so is there another way to achieve this?
You could leverage the code below to achieve your purpose as follows:
var queryName = $"incsync_{UserId}";
var query = table.CreateQuery()
.Where(u => u.UserId == signedInUser);
await table.PullAsync(queryName, query);
For Select method, you could retrieve the specific properties into your local store instead of all properties in your online table as follows:
var queryName = $"incsync:s:{typeof(T).Name}";
var query = table.CreateQuery()
.Select(r => new { r.Text, r.Complete, r.UpdatedAt, r.Version });
await table.PullAsync(queryName, query);
For more details, you could refer to adrian hall's book about Query Management.
UPDATE:
As Joagwa commented, you could change your server side code and limit data to be retrieved only by the logged in user. For more details, you could refer to Data Projection and Queries > Per-User Data.

Related

How to check if client's contacts are using my app?

I'm currently developing an app using Firebase.
My Firestore Database looks like below:
Once the user passes the Firebase authentication procedure, I'm creating a user document with a field "Phone:" which contains his phone number. Basically, everyone who is gonna using the app will be listed in the database.
And here is my challenge:
I'm using the plugin easy_contact_picker to store all the contacts of the users device to a List.
How can I find out whether the users contacts are using the app or whether they are listed in the database?
My goal is create a contact List Widget which shows me all my contacts. But those contacts which are using the app or which are listed in the database, should be highlighted or marked particularly.
Which is the best way to realize that if we consider that millions of users (to minimize computing power)are listed in the database?
Anyone has an idea?
Thanks a lot
First of all try to awoid giving everyone access to read all users. That is something most ppl do when handling such a problem. The do it because the query over all users won't work if you don't give the rights to read all of them.
Because of security reasons I would move the logic for checking if a user exists into callable function (not a http function!). That way you can call it inside of your app and check for a single user or multiple of them in an array. That would depend how your frontend would handle it.
Very importand would be to store all phone numbers in the absolute same format. That way you could query for them. Regardless of the number of users you could always find a specific one like here:
var citiesRef = db.collection("users");
var query = citiesRef.where("Phone", "==", "+4912345679");
The numbers need to be absolutely the same without any emtpy spaces - chars and the +49 or 0049 also needs to be the same.
You could create two callable funcitons. One to check if a single user exists in your app and another where you send an array of phone numbers and you get an array back. The cloud function can use Promise.all to performe such queries in parallel so you get your responce quite fast.
I'm using a similar approach to add users in my app as admins to specific groups where you just enter the email of the user and if he is in the app he will be added. I not he get's an invitation on the email to join the App.
With the help of Tarik's answer, Ayrix and I came up with the following solution.
Important: Read Tarik's answer for more information.
Client: callable_compare_contacts.dart
import 'package:cloud_functions/cloud_functions.dart';
Future<List<Object>> getMembersByPhoneNumber(List<String> allPhoneNumbers) async {
HttpsCallable callable = FirebaseFunctions.instance.httpsCallable('membersByPhoneNumber');
final results = await callable.call(<String, dynamic>{'allPhoneNumbers': allPhoneNumbers});
return results.data;
}
Server: index.js
const functions = require("firebase-functions");
const admin = require("firebase-admin");
if (admin.apps.length === 0) {
admin.initializeApp({
credential: admin.credential.applicationDefault(),
});
}
exports.membersByPhoneNumber = functions.https.onCall((data, context) => {
return new Promise((resolve, reject) => {
if (!data || !data.allPhoneNumbers.length) return resolve([]);
const phoneNumbers = data.allPhoneNumbers;
// TODO: different scope? move vars for future use
const db = admin.firestore();
const collectionRef = db.collection("User");
let batches = [];
// because of wrong eslint parsing (dirty)
batches = [];
while (phoneNumbers.length) {
// firestore limits batches to 10
const batch = phoneNumbers.splice(0, 10);
// add the batch request to to a queue
batches.push(
new Promise((response) => {
collectionRef.where("Phone", "in", [...batch]).get()
.then((results) =>
response(results.docs.map(function(result) {
return result.data().Phone;
} )));
})
);
}
// response / return to client
Promise.all(batches).then(function(content) {
// console.log("content.flat()");
// console.log(content.flat());
return resolve(content.flat());
});
});
});
Note: This is our first callable/cloud function .. so Suggestions for changes are welcome.

Firebase realtime database returns all children instead of the single indicated one by OrderByKey().EqualTo(xxx)

I keep public profiles from my users like this:
Using the Unity SDK, I try to fetch a subset of the profiles with this query:
var profileDbRef = GetSharedDBInstance().RootReference.Child("profiles");
Query q = profileDbRef.OrderByKey();
userIds.ForEach(uId => q.EqualTo(uId));
q.GetValueAsync().ContinueWithOnSuccess(snapshot => {
var profiles = (Dictionary<string, object>)snapshot.Value;
// here profiles dictionary contains ALL my users, not the ones included in userIds list.
}
I receive all my users in the resulting snapshot. Not sure what's happening here? Is there a bug in the Unity SDK?
Turns out q.EqualTo(uId) returns a new query object. So the fix was to change this one line to update the q variable.
userIds.ForEach(uId => q = q.EqualTo(uId));

MS-Graph read tasks with admin consent

I am trying to read the Planner task for the users of my tenant. So I configured admin consent for "Tasks.Read", "Tasks.Read.Shared", "Groups.Read.All", "Groups.ReadWrite.All" for the app that is doing that.
Like mentioned here: https://learn.microsoft.com/de-de/graph/api/planneruser-list-tasks?view=graph-rest-1.0&tabs=http
I desined my code to get a token like mentioned here: https://briantjackett.com/2018/12/13/introduction-to-calling-microsoft-graph-from-a-c-net-core-application/
I get a token back and it is valid. (Checked with baerer token check tool.)
I thought that I could access the tasks from the Graph API like '/v1.0/users/{userId}/planner/tasks' but I get HTTP 401 back.
Can anyone give me the missing link? Thanks a lot.
_appId = configuration.GetValue<string>("AppId");
_tenantId = configuration.GetValue<string>("TenantId");
_clientSecret = configuration.GetValue<string>("ClientSecret");
_clientApplication = ConfidentialClientApplicationBuilder
.Create(_appId)
.WithTenantId(_tenantId)
.WithClientSecret(_clientSecret)
.Build();
var graphClient = GraphClientFactory.Create(new DelegateAuthenticationProvider(Authenticate));
var result = await graphClient.GetAsync($"/v1.0/users/{userId}/planner/tasks")
public async Task<string> GetTokenAsync()
{
AuthenticationResult authResult = await _clientApplication.AcquireTokenForClient(_scopes)
.ExecuteAsync();
return authResult.AccessToken;
}
private async Task Authenticate(HttpRequestMessage request)
{
var token = await GetTokenAsync();
request.Headers.Authorization = new AuthenticationHeaderValue("bearer", token);
}
Reading tasks for other users is currently not allowed. A user can only read their assigned tasks. As an alternative, you can read the tasks in specific Plans, and sort out the users from that data, if you want to collect assignments from a set of Plans. You can provide feedback on this behavior in Planner UserVoice with the description of what you are trying to accomplish.
Additionally, application permissions are supported now, if that works for your scenario.
/v1.0/users/{userId}/planner/tasks is for getting tasks via getting a user, and you will need User permissions (User.Read.All) to get tasks via that call.
(Also Do you really need Groups.ReadWrite.All? Are you making changes to groups? -- it's not in your original description)

Ingest from storage with persistDetails = true not save ingest status result

I'm now implement a program to migrate large amount of data to ADX base on Ingest from Storage feature of ADX and I'm need to check that status of each ingestion request each time the request finish but I'm facing an issue
Base on MS document in here
If I set the persistDetails = true for example with the command below it must save the ingestion status but currently this setting seem not work (with or without it)
.ingest async into table MigrateTable
(
h'correct blob url link'
)
with (
jsonMappingReference = 'table_mapping',
format = 'json',
persistDetails = true
)
Above command will return an OperationId and when I using it to check export status when the ingest task finish I always get this error message :
Error An admin command cannot be executed due to an invalid state: State='Operation 'DataIngestPull' does not persist its operation results' clientRequestId: KustoWebV2;
Can someone clarify for me what is the root cause relate to this? With me it seem like a bug relate to ADX
Ingesting data directly against the Data Engine, by running .ingest commands, is usually not recommended, compared to using Queued Ingestion (motivation included in the link). Using Kusto's ingestion client library allows you to track the ingestion status.
Some tools/services already do that for you, and you can consider using them directly. e.g. LightIngest, Azure Data Factory
If you don't follow option 1, you can still look for the state/status of your command using the operation ID you get when using the async keyword, by using .show operations
You can also use the client request ID to filter the result set of .show commands to view the state/status of your command.
If you're interested in looking specifically at failures, .show ingestion failures is also available for you.
The persistDetails option you specified in your .ingest command actually has no effect - as mentioned in the docs:
Not all control commands persist their results, and those that do usually do so by default on asynchronous executions only (using the async keyword). Please search the documentation for the specific command and check if it does (see, for example data export).
============ Update sample code follow suggestion from Yoni ========
Turn out, other member in my team mess up with access right with adx, after fixing it everything work fine
I just have one concern relate to PartiallySucceeded that need clarify from #yoni or someone have better knowledge relate to that
try
{
var ingestProps = new KustoQueuedIngestionProperties(model.DatabaseName, model.IngestTableName)
{
ReportLevel = IngestionReportLevel.FailuresAndSuccesses,
ReportMethod = IngestionReportMethod.Table,
FlushImmediately = true,
JSONMappingReference = model.IngestMappingName,
AdditionalProperties = new Dictionary<string, string>
{
{"jsonMappingReference",$"{model.IngestMappingName}" },
{ "format","json"}
}
};
var sourceId = Guid.NewGuid();
var clientResult = await IngestClient.IngestFromStorageAsync(model.FileBlobUrl, ingestProps, new StorageSourceOptions
{
DeleteSourceOnSuccess = true,
SourceId = sourceId
});
var ingestionStatus = clientResult.GetIngestionStatusBySourceId(sourceId);
while (ingestionStatus.Status == Status.Pending)
{
await Task.Delay(WaitingInterval);
ingestionStatus = clientResult.GetIngestionStatusBySourceId(sourceId);
}
if (ingestionStatus.Status == Status.Succeeded)
{
return true;
}
LogUtils.TraceError(_logger, $"Error when ingest blob file events, error: {ingestionStatus.ErrorCode.FastGetDescription()}");
return false;
}
catch (Exception e)
{
return false;
}

Angularfire 2.1 - How to access auto-generated ID for users (or how to make my UID the first node for each user)

Disclaimer, I am trying to self-teach myself development. I am building a hybrid mobile app using Ionic 1 and now Firebase 3 for my database and authentication.
For my scenario, in short, I'm trying to display a list of 'friends' for the user that is currently logged in. Here is the current data structure I have (the relevant part anyway):
Data Structure
I have a line of code that does return me what I want:
var friends = $firebaseArray(ref.child('users').child('-KXcxMXkKs46Xv4-JUgW').child('friends'));
Of course, that can't work because there is a nice little hard coded value in there.
So, I looked into how to retrieve the current UID so I could replace the hard coded value. But after running the following bit of code through, the first node under user is not the UID (it is some other auto generated value that I don't really know how it got there). The UID is actually within the id field.
var ref = firebase.database().ref();
authObj = $firebaseAuth();
var firebaseUser = authObj.$getAuth();
console.log(firebaseUser.uid);
So, ultimately what I would love is to be able to change the data structure so that the UID is the first node under Users, but I can't seem to find documentation to do that. I looked at this other stack thread, but it is for an outdated version and I can't seem to connect the dots. Other thread
Though, if I can't change the structure, I still need to figure out how to access that friends node for the current user, one way or another.
Thank you in advance. This is my first stackoverflow post, so be gentle.
Update:
Per Frank's comment, this is the code that I execute to create users - $add is what is creating the push id (-KXcxM...).
createProfile: function(uid, user) {
var profile = {
id: uid,
email: user.email,
registered_in: Date()
// a number of other things
};
var messagesRef = $firebaseArray(firebase.database().ref().child("users"));
messagesRef.$add(profile);
},
register: function(user) {
return auth.$createUserWithEmailAndPassword(user.email, user.password)
.then(function(firebaseUser) {
console.log("User created with uid: " + firebaseUser.uid);
Auth.createProfile(firebaseUser.uid, user);
Utils.alertshow("Success!","Your user has been registered.");
})
.catch(function(error) {
Utils.alertshow("Error.","Some helpful error message.");
console.log("Error: " + error);
});
}
Instead of creating a $firebaseArray and calling $add on it, you can just store the user using the regular Firebase JavaScript SDK:
createProfile: function(uid, user) {
var profile = {
id: uid,
email: user.email
};
firebase.database().ref().child("users").child(uid).set(profile);
}
Since AngularFire is built on top of the Firebase JavaScript SDK, the two interact nicely with each other. So if you have any existing $firebaseArray on users it will pick up the new profile too.

Resources