Transaction for collection - bookshelf.js

How can I do using transacting t, I want to make sure the row is successful remove before saving the record:
var Roles = bookshelf.Collection.extend({
model: Role
);
Role.where('name', '=', 'Staff').destroy();
var roles = Roles.forge([{name: 'Staff'}, {name: 'Guest'}]);
Promise.all(roles.invoke('save')).then(function(role) {
resolve(role);
}).catch(function (err) {
reject({"status":"error", "data": err});
});

You may just use Bookshelf's transaction() method.
But first your save() MUST be in the context of the destroy() promise, so ensuring proper sequence, otherwise you risk having your saved data being also deleted by the destroy.
So it may look like:
var Roles = bookshelf.Collection.extend({
model: Role
});
bookshelf.transaction(function(t) {
return Role
.where('name', '=', 'Staff')
.destroy({transacting: t})
.then(function() {
var roles = Roles.forge([{name: 'Staff'}, {name: 'Guest'}]);
return roles
.invokeThen('save', null, {transacting: t});
});
});

Related

Create documents in different firestore collections, with same reference ID

My question is actually twofold, so I m not sure I should ask both in one post or create another post. Anyway, here it is:
I am creating users in firestore database. I do not want to put all details in a single document because it will be requested a lot, and all details will be retrieved, even if not needed. So I decided to create a collection members_full with all details of users I may not need often, and another collection called members_header to keep the few most important details. On creation of a new user, I want reference ID in both collections to be the same for a specific user.
- members_full -+
|
+ --- abnGMbre --- +
|
+ --- mother : 'His mom'
+ --- Father: 'daddy'
- members_header+
|
+ ---- abnGMbre -- +
|
+ ---- fullname: 'john Doe'
+ ---- pictURL: 'path to his profile pic'
I want something looking like the above.
So this is what I did in the cloud function:
/** Create / Update a member
* ------------------------- */
exports.updateMember = functions.https.onCall( (data, context) =>{
// root member and secretaries are allowed to update members
const authParams:any = {
uid: context.auth.uid,
email: context.auth.token.email,
};
// Check if user is allowed to perform operation
return checkPermission(authParams, ['root', 'secretary']).then(res => {
if(res==false){
return { // Permission denied
status: STATUS.permission_denied,
}
}
// set object to add/ update
const member:any = data;
// Check if uid of member object is present (true:update, false: create)
var fullRef : admin.firestore.DocumentReference;
var headRef : admin.firestore.DocumentReference;
var countRef: admin.firestore.DocumentReference;
var createNewMember = false;
if(member.uid!==undefined && member.uid!==null){ // update
fullRef = fsDB.collection('members_full').doc(member.uid);
headRef = fsDB.collection('members_header').doc(member.uid);
} else {
fullRef = fsDB.collection('members_full').doc();
headRef = fsDB.collection('members_header').doc(fullRef.id);
countRef = fsDB.collection('counters').doc('members');
createNewMember = true;
}
return fsDB.runTransaction(t => {
return t.get(fullRef).then(doc => {
// Update full details
t.set(fullRef, {
surname : member.surname ,
firstName : member.firstName ,
birthDate : member.birthDate ,
birthPlace : member.birthPlace ,
email : member.email ,
phone : member.phone ,
occupation : member.occupation ,
father : member.father ,
mother : member.mother ,
spouse : member.spouse ,
children : member.children ,
addressHome : member.addressHome ,
addressLocal: member.addressLocal,
contactHome : member.contactHome ,
contactLocal: member.contactLocal,
comment : member.comment ,
regDate : member.regDate ,
});
// Update header details
t.set(headRef, {
fullName : member.fullName ,
gender : member.gender ,
active : member.active ,
picURL : member.picURL ,
});
// Increment number of members
if(createNewMember ){
t.update(countRef, {count: admin.firestore.FieldValue.increment(1)});
}
}).then(() => {
return { status : STATUS.ok }
}).catch(err => {
return {
status: STATUS.fail,
message: err.message,
error: err
}
});
}).then(() => {
return { status : STATUS.ok }
}).catch(error =>{
return {
status: STATUS.fail,
message: error.message,
debug: 'run transaction err',
error: error
}
});
}).catch(err => {
return {
status: STATUS.fail,
message: err.message,
debug: 'check permission err',
error: err
}
});
});
/** Check if authenticated user's roles are among the ones allowed
* --------------------------------------------------------------- */
function checkPermission(authParams:any, allowedRoles:any[]):Promise<boolean>{
// Check if authenticated user as any of the roles in array 'allowedRoles'
return new Promise((resolve, reject) => {
// If one of allowed roles is root, check against global variables
if(allowedRoles.indexOf('root')>=0 &&
( root_auth.email.localeCompare(authParams.email)==0 ||
root_auth.uid.localeCompare(authParams.uid)==0)){
resolve(true);
}
// Get autID
const uid = authParams.uid;
// Get corresponding user in collection roles
admin.firestore().collection('userRoles').doc(uid).get().then(snap => {
// Get roles of user and compare against all roles in array 'allowedRoles'
const memRoles = snap.data().roles;
var found = false;
var zz = memRoles.length;
for(let z=0; z<zz; z++){
if(allowedRoles.indexOf(memRoles[z])){
found = true;
break;
}
}
resolve(found);
}).catch(err => {
reject(err);
});
});
}
When I call this cloud function, it only writes in document members_full, and increment number of members. It does not create entry in members_header.
My first question: where did I go wrong? the way I' m getting ID from the first document to create second document, isn't it valid?
The second question, will it be better to create subcollections rather than having 2 collections? if yes, how to do I do that in a transaction?
Help much appreciated
You need to chain the method calls in the Transaction. It is not extremely clear in the documentation, but if you look at the reference document for a Transaction (https://firebase.google.com/docs/reference/node/firebase.firestore.Transaction) you will see that the update() and set() methods return a Transaction, which is
the "Transaction instance. [and is] used for chaining method calls".
So you should adapt your code along these lines:
return fsDB.runTransaction(t => {
return t.get(fullRef)
.then(doc => {
t.set(fullRef, {
surname : member.surname ,
firstName : member.firstName
//....
})
.set(headRef, {
//....
gender : member.gender
//....
})
.update(countRef, {count: admin.firestore.FieldValue.increment(1)});
});
});
You also need to correctly chain all the different promises, as follows:
return checkPermission(authParams, ['root', 'secretary'])
.then(res => {
//...
return fsDB.runTransaction(t => {
//.....
});
.then(t => {
return { status : STATUS.ok }
})
.catch(error => {...})
However, you may use a batched write instead of a transaction, since it appears that you don't use the document returned by t.get(fullRef) in the transaction.
For your second question, IMHO there is no reason to use sub-collections instead of two (root) collections.

In sequelize, how to query the model associated with 'through'

I am very new to Sequelize and need help to create a method to query between two tables.
I have given three models named "user", "project" and "user_project".
user_project stores the relationship between users and projects. It uses two foreign keys (user_id and project_id) from other tables.
In addition, user_project has a field named "role" to specify the role of the user in a project. I am trying to figure out a way to extract user roles based on user "email". Note than user email is stored in user table.
The associations between them are as follows:
models.project.belongsToMany(models.user, {
through: models.user_project,
foreignKey: {
name: 'projectId',
field: 'project_id'
}
});
models.user.belongsToMany(models.project, {
through: models.user_project,
foreignKey: {
name: 'userId',
field: 'user_id'
}
});
Thanks in advance.
Know this is very late but hope it will someone else in future -
Using raw query and then converting the roles string to array -
User.findOne({
where: {email},
attributes: [
'id', 'email',
[Sequelize.literal("(SELECT GROUP_CONCAT(roles) FROM user_project UP WHERE UP.user_id=user.id )"), "roles"],
]
}).then( (user) => {
if(!user)
return null;
user = JSON.parse(JSON.stringify(user));
if(!user.roles)
user.roles = [];
else
user.roles = user.roles.split(",");
return user;
});
Getting roles without Using Raw -
User.findOne({
where: {email},
attributes: [
'id', 'email'
],
include: [
{
model: Db.user_project,
required: false,
attributes: ["user_id", "roles"]
}
]
}).then( (user) => {
if(!user)
return null;
user = JSON.parse(JSON.stringify(user));
if(!user.roles)
user.roles = [];
else
user.roles = user.roles.map(r => r.roles);
return user;
});

Firebase trigger on delete

Im trying to make a function where I can identify who delete, the problem is Im not getting any answer from the firebase server, someone could help me ? Thanks
exports.deleteFunction = functions.database.ref('/clientes')
.onDelete((context) => {
// Grab the current value of what was written to the Realtime Database.
console.log("delete");
console.log(context);
});
Here is the function who is deleting
confirm = (e) => {
if (id_deleta) {
firebaseDatabase.ref('/clientes/categorias/').child(id_deleta)
.remove();
notification('success', 'Excluido com sucesso');
this.callCategoria();
} else {
notification('error', 'Ocorreu um erro, tente mais tarde');
}
}
Code correction
The onDelete event handler is defined as
function(non-null functions.database.DataSnapshot, optional non-null functions.EventContext)
So in your code above, .onDelete((context) => { should be .onDelete((snapshot, context) => {.
Getting the deleted ID
Next, if you are trying to get the value of id_deleta from the onDelete event, you can use var id_deleta = snapshot.key.
exports.deleteFunction = functions.database.ref('/clientes')
.onDelete((snapshot, context) => {
var id_deleta = snapshot.key;
console.log("deleted ID %s", id_deleta); // logs "deleted ID 1234", etc.
console.log(snapshot.val()); // logs the deleted data, no need for this
console.log(context); // logs the event context
});

How do you add extra data in currentUser object?

I am trying to add an extra information that is not in profile for the currentUser object in Meteor. I am thinking it is possible and the technique should be somewhere in meteor/alanning:roles.
e.g. if I have a organizations object in users e.g.
{
_id: ...
profile: { ... }
roles: [ ... ]
organizations: [ ... ]
}
I would like to see it when I do
{{ currentUser }}
To push an orgId onto the organizations array in the user object for example:
Meteor.users.update(_id,{$push: {organizations: orgId}});
As #Blaze Sahlzen says, you'll need to publish this field:
Meteor.publish('orgUsers',function(orgId){
return Meteor.users.find({organization: orgId},{fields: {organization: 1, profile: 1}});
});
Basically currentUser is a global helper in meteor which calls returns Meteor.user()
From meteor repo
Package.blaze.Blaze.Template.registerHelper('currentUser', function () {
return Meteor.user();
});
https://github.com/meteor/meteor/blob/2ccb7467c9bb5889a3c36739d2d8b59a0656961c/packages/accounts-base/accounts_client.js#L421
Meteor.user() function returns following data
user() {
var userId = this.userId();
return userId ? this.users.findOne(userId) : null;
}
https://github.com/meteor/meteor/blob/dc3cd6eb92f2bdd1bb44000cdd6abd1e5d0285b1/packages/accounts-base/accounts_common.js#L52
Coming to your question, if you want to add extra fields to currentUser just publish the data to client.
If you want to publish that field to users by default
Meteor.publish(null, function(argument){
if(this.userId){
return Members.findOne({userIds: this.userId}, { fields: { organizations: 1,... } });
}
this.ready()
});

Most efficient way to ensure user owns document on update?

I'm using Meteor methods to update documents so I can share them easier and have more control. However i've ran into a problem with checking ownership.
How should I check to make sure the user calling the update method is the owner of the document? Currently i'm grabbing the document first then running the update.
Is there a better pattern to accomplish this?
Meteor.methods({
'Listing.update': function(docId, data) {
var doc = db.listings.findOne({_id: docId}) || {};
if (doc.userId !== this.userId) {
throw new Meteor.Error(504, "You don't own post");
}
// ensure data is the type we expect
check(data, {
title: String,
desc: String
});
return db.listings.update(docId, {$set: data});
}
});
You don't need the additional db call to fetch the original doc, just make the userId an additional criteria in the update selector. If no doc exists with the correct _id and userId no update will be done. update returns the number of docs updated so it will return 1 on success and 0 on failure.
like this:
'Listing.update': function(docId, data) {
var self = this;
check(data, {
title: String,
desc: String
});
if ( ! self.userId )
throw new Meteor.Error(500, 'Must be logged in to update listing');
res = db.listings.update({_id: docId, userId: self.userId}, {$set: data});
if ( res === 0 )
throw new Meteor.Error( 504, "You do not own a post with that id" );
return res;
}
Also, if you use findOne to check a document's existence, use the fields option to limit what you return from the db. Usually just {fields: {_id:1}}.

Resources