How to extend the User model for angular-meteor - meteor

Based on the socially project, I tried to extend the user to add a field:
export interface User extends Meteor.User {
experience: number;
}
However I get the following error:
client/imports/app/parties/party-details.component.ts (71, 7): Type 'Observable<User[]>' is not assignable to type 'Observable<User>'.
Type 'User[]' is not assignable to type 'User'.
Property 'experience' is missing in type 'User[]'.
As far as I understand that is because of the collection definition:
export const Users = MongoObservable.fromExisting(Meteor.users);
It uses Meteor.users (without experience field) and not my custom typescript defined version (with experience field).
Update
Related files:
repo
party-details.component.ts 71
user model
users collection
How can I fix that? Or in other words: How do I extend the user model for an angular-meteor project?
Update 2
I want to rephrase the question to: How can I extend the Meteor user collection?
export const Users = MongoObservable.fromExisting(Meteor.users);
Bonus question: And how can I initiate the field with 0 when creating new users?

However I get the following error:
You have to change line 31 to
users: Observable<User[]>; instead of users: Observable<User>;
Although I don't even get an error thrown without changing it, so I don't know when the error is supposed to happen exactly?
Anyhow, a Meteor Collection lookup always returns an Array of objects so users: Observable<User[]>; is correct.
How can I extend the Meteor user collection?
As for how to extend the User:
Technically you wouldn't even have to extend it just to store an extra value, all the values that you give the Collection on insert will be stored for that document.
What you mean by extending is, extending the Model / Schema for the User document that is to be inserted. With TypeScript this usually just is an interface but if you weren't using TS, there would be alternatives such as https://github.com/aldeed/meteor-simple-schema which actually offer to set a default value. I just say this to help you understand the concept.
An interface though does only shape values, it does not set default values or anything like that. AFAIK this is the same with interfaces in Java for example.
So what you do is extend that interface like you said:
export interface User extends Meteor.User {
experience: number;
}
This should be correct. You can not extend the "collection", the collection simply defines the collection name in the MongoDB, and possibly who can access it.
And how can I initiate the field with 0 when creating new users
As already said, you can't set default values for interfaces. All you could do is set values to optional using experience? : number which would set it to undefined.
You can however create a class that implements that interface and then set default values.
export class SignupUser implements User{
experience:number=0;
email: string;
password: string;
}
then in your signup.component.ts on line 29 you could do
let signupUser=new SignupUser();
signupUser.email= this.signupForm.value.email;
signupUser.password= this.signupForm.value.password;
Accounts.createUser(signupUser, err => [...])
or even create a new SignupUser right away and bind directly to those values.
Alternatively the simple way you could also just do
Accounts.createUser({
experience: 0,
email: this.signupForm.value.email,
password: this.signupForm.value.password
}
I hope this helps you.

It looks like your issue is that somewhere you are trying to assign an Observable of a User array Observable<User[]> to an Observable of a User Observable<User>. Did you incorrectly apply Observable<User[]> as the type of the MongoObservable?

Related

Kotlin: Json: conflicting annotation schemes for name transcription

I read data from Firebase database into a Kotlin/Android program. The key names in Firebase are different from those of the corresponding Kotlin variables. I test this code with flat JSON files (for good reasons) where I retain the same key names used in Firebase, so I need to transcribe them too.
Firebase wants to annotate variables with #PropertyName; but Gson, which I use to read flat files, wants #SerializedName (which Firebase doesn't understand, unfortunately.)
Through trial and error I found that this happens to work:
#SerializedName("seq")
var id: Int? = null
#PropertyName("seq")
get
#PropertyName("seq")
set
Both Firebase and Gson do their thing and my class gets its data. Am I hanging by a thin thread here? Is there a better way to do this?
Thank you!,
You can probably solve this by using Kotlin's #JvmField to suppress generation of getters and setters. This should allow you to place the #PropertyName annotation directly on the property. You can then implement a Gson FieldNamingStrategy which checks if a #PropertyName annotation is present on the field and in that case uses its value; otherwise it could return the field name. The FieldNamingStrategy has to be set on a GsonBuilder which you then use to create the Gson instance.

How to set different custom claims based on where user signs up?

I have 2 sign up pages, one for students and one for teachers.
How can I set a different custom claim for each when they sign up? (student: true OR teacher:true)
I am guessing it could be possible at .onCreate? I can set just one custom claim per onCreate function, how to make this dynamic?
exports.AddTeacherRole = functions.auth.user().onCreate(async (authUser) => {
if (authUser.email) {
const customClaims = {
teacher: true,
};
The authUser Object passed to Authentication Cloud Functions is exactly the same type of Object than the UserRecord that is returned by the Firebase Admin SDK.
Depending on how you created the user (e.g. from the front-end, with createUserWithEmailAndPassword() in JavaScript or e.g. from the Admin SDK, with createUser()) the UserRecord may hold more or less "extra" properties (e.g. displayName, photoURL, etc).
The problem is that there is no specific property of this UserRecord instance that you could use to indicate the role of the user (student or teacher). Therefore you cannot get this information in your Cloud Function, which is triggered when the user is created.
A common approach is to add this kind of extra data to a Firestore document (or a Realtime Database node) which has the same id than the user Id. Then from this document, you could trigger a Cloud Function that updates the user record.
Another approach is to use a Cloud Function that does all the job: create the user, set the custom claim and create a Firestore document. You will find in the following article some detailed explanations about this kind of approach.
To answer the question you asked in the deleted answer:
Just to verify, it could be solved by creating 2 separate https
callable cloud functions that handle the sign up for each role for
example?
Yes you could have two callable Cloud Functions, but I think it should be possible to just have one and pass different parameter values when calling this Cloud Function, depending on the user role.
Something like:
const setCustomClaim = firebase.functions().httpsCallable('setCustomClaim');
const userRole = "student" // or role = "teacher"
setCustomClaim({role: userRole, userId, bar: "foo" }).then(function(result) {
// Read result of the Cloud Function.
// ...
});

Symfony Workflow - Is it possible to use doctrine relation as state holder?

I am trying to adopt the Symfony workflow component to my app.
As documentation says marking_store points to string. I was googling for it - it can be a string or json_array-field of a Doctrine entity.
But what if I have an Entity BlogPost with a relation BlogPostStatus that have two fields: some primary id and statusName. Can I configure the workflow component to change statuses of my BlogPost (i.e set new BlogPostStatus to BlogPost entity) and persist it to database?
Now I have only one solution: Add to my BlogPost entity non-mapped field and when it's changed change status of Entity.
Do you have a better solution?
For all built-in marking_store implementations the following is true:
If the functions setMarking or getMarking exist on the object holding the state, they will be used to set or get the marking respectively.
There are 3 built-in marking stores, the SingleStateMarkingStore (using the property accessor, hence setMarking/getMarking), the MultiStateMarkingStore (same), the MethodMarkingStore (explicitly calling those functions, you can change the function via the property setting of your marking_store config).
The difference lies within the argument provided in the setMarking call, for single state (this is the state_machine type, and by default NOT the the workflow type), the argument is the place (or state) where the mark is placed. For multi state (workflow type by default), the argument is an array where the keys are places and the values are marks, usually the marks are 1, and empty places are omitted.
So, I'll assume that your BlogPost (currently) only has a single state at any given time, and what you have to do now is to transform the marking given into the status entity - I will assume your workflow has type state_machine:
/** in class BlogPost */
public function setMarking(string $marking/*, array $context*/) {
$this->status->statusName = $marking;
}
public function getMarking() {
return $this->status->statusName;
}
special cases
If the BlogPostStatus should be a different one (for example, a constant object), then you'd have to use the new interface that dbrumann linked, and hook into the event to add that to the context.
If the BlogPostStatus may not exist at the time of the setMarking/getMarking, you have to create it on the fly in the setter and check for it in the getter. But I'm sure you're capable of doing that ;o)
Also if you're not using the single state workflows but multi state instead, you have to find a way to transform the array of (places->marks) into your status object and vice versa.

Flatting FOS ElasticaBundle nested fields

I have FOSElasticaBundle in my Symfony project. I have an entity mapped with ElasticaBundle that has some "simple fields", like
mappings:
createdAt:
type: "date"
and other fields that are IDs (like owner_id) to correlate to other entities, like
owner:
type: "nested"
properties:
fullname:
type: string
index: not_analyzed
because I need to have the user fullname searchable with ES/Kibana. This works but it created of course a nested field "owner.fullname" and this kind of fields are not searchable with Kibana (it's since years there are requests about it).
So I'm asking: is there a way to flat out that field so that I have a simple plain string field in ES named "owner_fullname" with no nested data?
Thanks
Answer to self.
You can create a method (or use existing where possible) in your class to return informations about related class.
So if you have a User class related to several Address class to store user's addresses, and you want to store in ES the default one, you can create a method "getDefaultAddress" in User class, something like
public function getDefaultAddress() {
return $this->addresses->getDefault()->getFormattedFlatString()
}
then map it to a field with elastica Bundle and use the "property_path:" descriptor to tell elastica where to get the content for that field.
In this way you have a simple field in ES and not a nested one.
I'm not sure I like this philosophy because you mix up code and external persistence layers, but it works, is clear and easy to maintain especially if you create dedicated methods with same prefix in your class, like esGetAddress, esGetPlace and so on.

Trying to obtain the current logged in user's ID for tracking fields

I am following this really good tutorial to setup a BaseEntity class that will contain 5 fields:
Active, DateCreated, UserCreated, DateModified, UserModified.
All of my entities that need these tracking fields will inherit from this class.
In the small tutorial below he shows me how to override the SaveChanges() method in my dbContext so that these fields will be set properly based on Creation/Updating.
I am trying to figure out how I would store the current logged in user's ID rather than the Name to the UserCreated and UserModified fields
Please let me know if the UserID shouldn't be what I am storing. This is always what I used to do in some of the webforms apps I created back in the day.
Also, what would be the best way to setup Active to always be true when adding new records. Should this be done in the db context also or within my BaseEntity class. I'm thinking I would create a function in the BaseEntity class called Disable() that will change Active=False.
Please view the small tutorial that I am using
You could create a custom implementation of IIdentity/IPrincipal which contains the UserID. Then you can retrieve this value from the Context or Thread and cast it to the correct type.
Set HttpContext.Current.User from Thread.CurrentPrincipal

Resources