Structuring Data in Firebase Cloud Firestore - node.js

I had been trying to structure my database in firestore
my json looks like this
{"UID":"myusrsid","PhotoList":[{"PhotoID":333,"PhotoURL":"url1ofphone"}, {"PhotoID":332,"PhotoURL":"url2ofphoto"} ]}
Here UID is UserID and its unique to users and this UID inside this have Photos List which have photo id and its url
so whenever user upload a new photo if the userid already exist the photo should be added to Photos List
and if new user then UID with new user should be created and photo should be added in photo list
I had been scratching my head to structure this
public class Users
{
public string UID { get; set; }
public List<PhotoList> PhotoList { get; set; }
}
public class PhotoList
{
public int PhotoID { get; set; }
public string PhotoURL { get; set; }
}
this is my model which I am planing to send and receive data
I am aware of retrieving and storing the data the only thing is right now I need help with how should I structure it. I am using Node js
here is what I had been doing right now
const imageBody = {
uid,
imageBase64String,
} = req.body;
await db.collection('users').doc(imageBody.uid).collection('Photos').doc(imageBody.imageBase64String).create({
url : imageBody.imageBase64String
});

Firestore has a maximum document size, so storing binary files is not recommended. Rather use Firebase Storage and store the URL in Firestore.
If you still want to store the images in Firestore consider using a flatter structure. You can still maintain strong access control and it will also be easier for other users share photos. By putting access control in the document itself you can get really powerful sharing capability without sacrificing security. So something like:
Users
Photos
ownerUID
imageBase64String
readPrivs
And to access images for a user:
await db.collection("Photos").where("ownerUID", "==", uid).get();

Related

Azure mobile app and Xamarin

I am trying to make a mobile app, which will use the Azure database system. I am having alot of trouble making my own table, and have been running in coding circles for a couple of weeks. I just can't figure out what and how to change.
I can get the todolist up and running from azure, and i have tried to make my own table in the backend with a dataobject and a controller, but after adding the DbSet om the context, the todolist part breaks when i try to run the app.
How do i add my own stuff to the app, so that i can have a table of persons for example, instead of the todolist?
Thank you so much in advance. this is very confusing to me.
This is what i've done:
In the backend, i made a person class inhereting the EntityData class and have a firstname string property and a lastname string property
Then i added
public DbSet<Person> Persons { get; set; }
and then a Personcontroller through the Add -> Controller -> Azure Mobile
Apps Table Controller in visual studio 2017
Then in the app i downloaded from azure, i made the person class
public class Person
{
[JsonProperty(PropertyName = "firstName")]
public string firstName { get; set; }
[JsonProperty(PropertyName = "lastName")]
public string lastName { get; set; }
[JsonProperty(PropertyName = "id")]
public string id { get; set; }
}
Then made the table
IMobileServiceTable<Person> PersonTable = client.GetTable<Person>();
Then tried to insert into the table
Person peter = new Person();
peter.firstName = "Peter";
peter.lastName = "Friis";
await personTable.InsertAsync(peter);
but that gives the error:
Microsoft.WindowsAzure.MobileServices.MobileServiceInvalidOperationException:
'The request could not be completed. (Internal Server Error)'
According to your description, I assumed that you are using C# backend with SQL database. I would recommend that you could add the following code under the ConfigureMobileApp method of Startup.MobileApp.cs file for collecting the detailed error message.
config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
Before inserting the new record to your table via the mobile client SDK, you could leverage the postman or fiddler to simulate the insert operation as follows to narrow this issue:
For more details about http table interface, you could refer to here.
Additionally, since you are adding your custom tables, please make sure you have manually updated your database to support your new database model or configure the Automatic Code First Migrations. For more details, you could refer to adrian hall's book about Implementing Table Controllers.

Azure mobile apps net client add new column with default value

I have already a model class called User.cs with some properties:
[JsonProperty(PropertyName = "admin")]
public bool Admin { get; set; }
[JsonProperty(PropertyName = "email")]
public string Email { get; set; }
This gives me the columns admin and email in the sqlite database.
Now I am making an update for my app. I want to add a new property and therefore a new column:
[JsonProperty(PropertyName = "owner")]
public bool Owner { get; set; }
When I add this to User.cs, the sqlite file will get a new column called owner. All existing rows in the User table of the sqlite file get the value NULL for this new column.
My question: What can I do give existing rows the value 0 (false).
There are a couple of things you can do, depending on requirements. The most obvious is:
Update the model on the server to add the Owner field,
Update the database with a default value.
On first run of your client (after an update), wipe the SQLite database and re-sync all data

Extending Identity 2.1 to assign user to company and roles to users

Ok so i am really lost now,
I have an application that works great, i have groups of data that are available to a company subscription, When the user logs in they can access the information from the data that their roles allow, If they have multiple roles they have access to multiple areas of reports, if they only subscribe to one role they only have access to that one area. now all the data is the same and updated daily, so if 100 users are subscribed to roles 1,3 and 5 they can access that data. for the last decade the users of the companies would use one login to access the data ( one login supplied to company for George, But Lisa, john, Jerry and bob all use George's credentials to download reports). Note the data does not change per company and the site does not change per company all data and themes are part of the one site they have access to.
Now the Issue.
I have been asked by many of my users (Companies that access my data) that they would like to be able to to add users to their account so they can monitor who is downloading what report as i charge subscription by 500, 1000, and 2000 reports. My application records reports downloaded and the user can view these reports from their user portal and the id that downloaded it. the issue is that the companies want to be able to administer their own users, sort of like an account admin. I have looked into multi tenant and this does not really suit my needs, but from what i am looking at i feel i need to add a company_id to the user and company_id user roles so each company can have an admin role and a user role. but not sure as i study and read more on this i see allot of different ideas. but dont really see one that works for me.
here is an example.
Each user (company) has many users, each user in that company has roles of Admin or user. Users (companies) have access to many areas that roles allow them and the users of that company would have the same access to reports tha company roles provide. (or see aggregate data across all groups they belong to).
Thinking of adding this to IdentityModels:
public class ApplicationUserRole : IdentityUserRole<string>
{
public string CompanyId { get; set; }
}
public class ApplicationUser : IdentityUser<string, ApplicationUserLogin, ApplicationUserRole, ApplicationUserClaim>//, IAppUser
{
public ApplicationUser()
{
this.Id = Guid.NewGuid().ToString();
}
public virtual string CompanyId { get; set; }
public virtual List<CompanyEntity> Company { get; set; }
public DateTime CreatedOn { get; set; }
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(ApplicationUserManager manager, string authenticationType)
{
var userIdentity = await manager.CreateIdentityAsync(this, authenticationType);
return userIdentity;
}
}
add to IdentityConfig:
public string GetCurrentCompanyId(string userName)
{
var user = this.FindByName(userName);
if (user == null)
return string.Empty;
var currentCompany = string.Empty;
if (user.Claims.Count > 0)
{
currentCompany = user.Claims.Where(c => c.ClaimType == ConcordyaPayee.Core.Common.ConcordyaClaimTypes.CurrentCompanyId).FirstOrDefault().ClaimValue;
}
else
{
currentCompany = user.CurrentCompanyId;
}
return currentCompany;
}
public override Task<IdentityResult> AddToRoleAsync(string userId, string role, string companyId)
{
return base.AddToRoleAsync(userId, role);
}
Now im not sure about this, do i need to create a new user store. and how would authentication work, will i need to write a new Authorize Attribute. should i be using the claims. or maybe ACL system. or do i need to find a different identity provider.
Any help would be appreciated greatly. as i am getting very confused.

Relation to users when these are stored in an external Identity provider service

I'm trying to create an API and a website client for it. Lately I've been reading a lot about OAuth2 as a security mechanism and companies that offers authentication as a service such as auth0.com or even Azure active Directory and I can see the advantages in using them
Because I'm used to always having the users in the same database and tables with relationships to the Users table in the form of One to Many such as below
public class User
{
public string subjectId { get; set; }
public virtual List<Invoice> Invoices { get; set; }
/*
More properties in here
*/
}
public class Invoice
{
public int InvoiceId { get; set; }
public string PaymentNumber { get; set; }
public DateTime Date { get; set; }
public double Amount { get; set; }
public string Description { get; set; }
public virtual User User { get; set; }
}
My questions is then.
If the users are stored in an external authentication service such as Auth0.com,
How the Invoice class will handle the relation to the user?
Would it be just adding a new property subjectId in the Invoice table and this will take the value of whatever id the authentication service assigned?
In the latter case, would the class Invoice be something like below?
public class Invoice
{
public int InvoiceId { get; set; }
public string PaymentNumber { get; set; }
public DateTime Date { get; set; }
public double Amount { get; set; }
public string Description { get; set; }
public string SubjectId{get;set;}
}
Also, if the users are stored someplace else, how do you make a query like,
Select * from Users u inner join Invoices i where Users.Name='John Doe' and i.Date>Somedate.
Since you have mentioned Auth0 as your Identity provider there are multiple ways to achieve the user table in your database.
1. Authenticating/ registering the user with Auth0 will send a response with Profile Object which will have all the basic profile information you need. Post this profile object back to your own API to save it to database. This API endpoint should be secured with the access token you received along with the profile object from Auth0.
2. You can create a custom rule in Auth0 that posts the user information back to your api. This rule gets executed on Auth0 server so this is a secure call.
3. Identity providers (Auth0 in our case) are required to expose an API endpoint that gives us user profile data (ex: https://yourdoamin.auth0.com/userinfo). You can make a call to this endpoint from your API to receive the user information.
When user Registers to your application, please use one of these techniques to establish a User profile information table in your database. It is always a good idea to treat the Identity Provider as a service responsible for authenticating the resource owner (the user of your application) and providing an access token for securely accessing your API/ application. If you have the profile of the user in your database, you do not have to depend on the Identity Provider once the user is authenticated.
Please let me know if you have any further questions.
Thank you,
Soma.
We have a similar setup for our website. We use Passport for our user database and our website doesn't have a user table at all. This makes life much simpler than having a a bunch of duplicate data between Passport and our website. I'll use our code as an example of what you are doing and hopefully it makes sense.
Our website has a License object that looks like this (Java not C#, but they are similar):
public class License {
public String companyName;
public List<User> users;
}
The License table looks like this (trimmed down):
CREATE TABLE licenses (
id UUID NOT NULL,
company_name VARCHAR(255) NOT NULL,
PRIMARY KEY (id)
);
The License identifies the users that are associated with it via a join table like this (Passport uses UUIDs for user ids making life simple again):
CREATE TABLE users_licenses (
users_id UUID NOT NULL,
licenses_id UUID NOT NULL,
PRIMARY KEY (users_id, licenses_id),
CONSTRAINT users_licenses_fk_1 FOREIGN KEY (licenses_id) REFERENCES licenses (id)
);
Then we can select in either direction. If we know the user id, we can ask for all their licenses like this:
select * from licenses where users_id = ?
Or if we know the license id, we can ask for all the users that have access to the license:
select * from users_licenses where licenses_id = ?
Once we have one or more user ids, we can call the Passport /api/user endpoint or the /api/user/search endpoint to retrieve one or more user objects. We are actually using the Passport Java Client (https://github.com/inversoft/passport-java-client) which makes the API call for us and then returns a List<User>. This is what is stored in the License class from above. That code looks like this:
License license = licenseMapper.retrieveById(licenseId);
List<UUID> userIds = licenseMapper.retrieveUserIdsFor(licenseId);
ClientResponse<SearchResponse, Errors> clientResponse = passportClient.searchUsers(userIds);
license.users = clientResponse.successResponse.users;
LicenseMapper is a MyBatis interface that executes the SQL and returns the License objects. C# ORMs use LINQ, but it would be similar.
The nice thing about this setup is that we don't have a user database table in our website database that we have to keep in sync. Everything is loaded from Passport via the API. We aren't ever concerned about performance either. Passport is on-premise and can do thousands of user lookups each second, so we always load the data instead of caching it.
The only piece of your question that requires additional code is handling the joins when you are searching for arbitrary users like name='John Doe'. The only way to handle this is to query your user database first, retrieve all the IDs, then load their invoices. This seems like it could be dangerous if you have a large user database, but still doable.
That could would look like this in our situation:
UserSearchCriteria criteria = new UserSearchCriteria().withName("John Doe");
ClientResponse<SearchResponse, Errors> clientResponse = passportClient.searchUsersByQueryString(criteria);
List<User> users = clientResponse.successResponse.users;
Set<License> licenses = new HashSet<>();
for (User user : users) {
licenses.addAll(licenseMapper.retrieveByUserId(user.id));
}

Can't seem to expand an Image Navigation property with Breeze JS

I am using MVC, Entity Framework, Durandal and Breeze JS. I've got a user which looks like such (simplified):
public class User : EntityBase<Guid>, IAggregateRoot
{
public Guid Id { get; set; }
public string UserName { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
[ForeignKey("UserImage")]
public virtual Guid? ImageId { get; set; }
public virtual UserImage UserImage { get; set; }
}
The UserImage class looks like such. I know I should limit the size of the Image. (Maybe this is the issue?):
public class UserImage
{
public Guid Id { get; set; }
[MaxLength]
public byte[] Image { get; set; }
}
I've got an api function on the server to get the current user:
public IQueryable<User> GetCurrentUser()
{
IPrincipal principal = HttpContext.Current.User;
var users = _uow.Users.FindBy(u => u.UserName.Equals(principal.Identity.Name));
if (!users.Any())
{
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.Unauthorized));
}
return users;
}
And two calls on the client which get the current user. The first is in the shell:
function loadCurrentUser() {
return uow.CurrentUser.all().then(function (newUser) {
log('Welcome to the Site ' + newUser[0].FullName() + '!', newUser[0], true);
config.CurrentUser(newUser[0]);
return true;
});
}
The second is in a ManageUser viewmodel:
function activate() {
return uow.CurrentUser.all(['UserImage']).then(function (user) {
self.CurrentUser(user[0]);
return $.when(init()).then(boot());
}).fail(function() {
return router.activate('accounts/login');
});
}
Now I can load an Image into the ManageUser page and save and in fiddler it shows that the ImageId and Image are being sent across to the server. Then I checked the BeforeSaveEntity intercept and shows two entities being saved.
Updated User with ImageId set
New UserImage
The data is also visible in the database. Now when I refresh the Manage User page I can see the two GetCurrentUser calls in fiddler.
From the shell call I can see that the User is being returned and an ImageId is set but no UserImage was sent over because didn't expand the query.
From the Manage User call I see the User is returned but only the ImageId is sent over and the Image object was OMITTED from the JSON.
Has anyone come across this issue with images? All my other expands appear to be working correctly. Does anyone have any examples on using breeze to save just the filepath to the image and possibly using windows azure for media storage?
I know this probably won't answer your question but I would propose not sending the byte array to the client and rather have an Image Handler on the server side that takes an ImageId as a parameter and then return the image with the relevant Content Type set. An example of this can be found here.
By using this approach you could reference your image from HTML using an img tag with the source set to the Image Hander with the relevant ImageId.
An example using knockout for data binding would be:
<a data-bind="attr: {href: '/Image/' + User.ImageId()}"></a>
This approach enables you to easily add caching on both the server and client which will improve performance. It also removed the need to convert the byte array to an image on the client side, which may or may not be a pain.
Edit:
When saving the managed user, post the Image to an Upload action on the ImageHandler (have a look at this article). This action must return the new Id of the image. After you've received the new Id, update the User.ImageId on client side and call SaveChanges on breeze.

Resources