HapiJS Catbox: How to search a key using some value's fields as search criteria? - caching

In my app when a user is authenticated, I store his session data (including his email) in the server cache, and I create a sessionId that I use as the key.
When a user is deleted from the database, I want to check if he was logged in, that is, if there is a session in the cache that comes from his account, so I can drop that entry from the server cache too.
The problem is that the sessionId is not part of the User model, so I have to lookup his entry from the cache using his email, get the associated key, and drop the entry. Is that possible ?
Thanks in advance.

Catbox is just a key/value store and it doesn't look like there is a way to iterate through cache items like you want and find a user by another property. You need to know the key. You can either make the key the users' email or store sessionId in the database in another table.
You might be doing more work than you have to for session management. Yar is a hapi plugin that provides session management for you. Invalidating a session is pretty simple as well.
When the user logs out use yar.reset() to clear out the session.
Hapi-auth-cookie is another plugin for cookie-based session management.

I finally created a pre that return all the sessionIds associated to the account to be deleted, so I can delete them normally with server.cache.drop(key, cb). Error handling removed for brevity.
function currentSessionIds(request, reply) {
const sessionIds = [];
User.findOne({ _id: request.params.id }, (err, user) => {
const cacheDB = request.server.app.sessionsCache._cache.connection.settings.partition;
MongoClient.connect(`mongodb://host:port/${cacheDB}`, (err, db) => {
db.collection('sessions').find({ 'value.account.email': user.email }, { _id: 1 }).toArray((err, sessions) => {
sessions.forEach(session => sessionIds.push(session._id));
reply(sessionIds);
});
});
});
}
But of course this solution is too tied to mongodb and the way catbox-mongodb strategy store the data. If they change it my function is down.

Related

Apollo client: Making optimistic updates while creation is still in progress

I want to be able to do updates on an object while it is still being created.
For example: Say I have a to-do list where I can add items with names. I also want to be able to edit names of items.
Now say a user with a slow connection creates an item. In that case I fire off a create item mutation and optimistically update my UI. That works great. So far no problem
Now let's say the create item mutation is taking a bit of time due to a slow network. In that time, the user decides to edit the name of the item they just created. For an ideal experience:
The UI should immediately update with the new name
The new name should eventually be persisted in the server
I can achieve #2 by waiting for the create mutation to finish (so that I can get the item ID), then making an update name mutation. But that means parts of my UI will remain unchanged until the create item mutation returns and the optimistic response of the update name mutation kicks in. This means #1 won't be achieved.
So I'm wondering how can I achieve both #1 and #2 using Apollo client.
Note: I don't want to add spinners or disable editing. I want the app to feel responsive even with a slow connection.
If you have access to the server you can implement upsert operations, and you can reduce all queries to the such one:
mutation {
upsertTodoItem(
where: {
key: $itemKey # Some unique key generated on client
}
update: {
listId: $listId
text: $itemText
}
create: {
key: $itemKey
listId: $listId
text: $itemText
}
) {
id
key
}
}
So you will have a sequence of identical mutations differing only in variables. An optimistic response accordingly, can be configured to this one mutation. On the server you need to check if an item with such a key already exists and create or update an item respectively.
Additionally you might want to use apollo-link-debounce to reduce number of requests when user is typing.
I think the easiest way to achieve your desired effect is to actually drop optimistic updates in favor of managing the component state yourself. I don't have the bandwidth at the moment to write out a complete example, but your basic component structure would look like this:
<ApolloConsumer>
{(client) => (
<Mutation mutation={CREATE_MUTATION}>
{(create) => (
<Mutation mutation={EDIT_MUTATION}>
{(edit) => (
<Form />
)}
</Mutation>
)}
</Mutation>
)}
</ApolloConsumer>
Let's assume we're dealing with just a single field -- name. Your Form component would start out with an initial state of
{ name: '', created: null, updates: null }
Upon submitting, the Form would do something like:
onCreate () {
this.props.create({ variables: { name: this.state.name } })
.then(({ data, errors }) => {
// handle errors whichever way
this.setState({ created: data.created })
if (this.state.updates) {
const id = data.created.id
this.props.update({ variables: { ...this.state.updates, id } })
}
})
.catch(errorHandler)
}
Then the edit logic looks something like this:
onEdit () {
if (this.state.created) {
const id = this.state.created.id
this.props.update({ variables: { name: this.state.name, id } })
.then(({ data, errors }) => {
this.setState({ updates: null })
})
.catch(errorHandler)
} else {
this.setState({ updates: { name: this.state.name } })
}
}
In effect, your edit mutation is either triggered immediately when the user submits (since we got a response back from our create mutation already)... or the changes the user makes are persisted and then sent once the create mutation completes.
That's a very rough example, but should give you some idea on how to handle this sort of scenario. The biggest downside is that there's potential for your component state to get out of sync with the cache -- you'll need to ensure you handle errors properly to prevent that.
That also means if you want to use this form for just edits, you'll need to fetch the data out of the cache and then use that to populate your initial state (i.e. this.state.created in the example above). You can use the Query component for that, just make sure you don't render the actual Form component until you have the data prop provided by the Query component.

How to refresh variables in react-apollo

Here is the scenario :
USER 1
1) Goes to login page
2) Writes email and password which are sent to the server by a mutation
3) Authentication OK -> the server returns a token and user informations (id, firstName, lastName)
4)The token and each user information are stored in a separate key in local storage
5) The user is redirected to the HomePage
6) The user goes to Profile Page
7) A query is sent to the server to retrieve all the informations about that user (thanks to the user id stored in local storage)
Here is the query :
const PROFILE_QUERY = gql`
query profileQuery($id: ID!) {
getUser(id: $id) {
firstName
lastName
email
}
}
`;
export default graphql(PROFILE_QUERY, {
options: {
variables: {
id: localStorage.getItem("id")
},
errorPolicy: "all"
}
})(ProfilePage);
8) The server returns the informations and the user can see them in the Profile Page
9) The user decides to logout
So everything is working for the first user, now a second user arrives at the same computer and the same browser.
USER 2
Same steps than the previous user from 1 to 7
The query sent to the server to retrieve user informations is not working because the id sent by the query is the id of the previous user (and a user is not allowed to retrieve informations about an other user)
If I do a browser refresh the query is sent with the good user id...
So why the id variable is not refreshed (seems like the id value in local storage is not read) at the first attempt ? How can I resolve this ?
Thank you for your help.
That happens because your options field is static and is evaluated when the file containing the query is first loaded. (I'm assuming somewhere, perhaps steps 3 or 4, the id in local storage is updated correctly for the second user.)
config.options can be an object like the one you are passing now or a function that is evaluated when needed by the query.
So to load the id from the localStorage each time instead of just once, you can do something like this:
options: () => ({
variables: {
id: localStorage.getItem("id")
},
errorPolicy: "all"
})
Then first user logged out, you need to reset Apollo store and clear local storage.

Send 2 different types of mails using mailchimp

I have a set of internal users for my project. Admin can activate/deactivate them. I want to send them a mail saying "your account has been deactivated" when their account is deactivated by admin. Similarly they should receive a mail saying "your account has been activated" when admin activates their account. How can I do this?
I am trying by creating 2 separate lists in mailchimp and two separate campaigns. but when I'm writing mailchimps credentials in my development.js with 2 separate list ids and then trying to get it in my javascript file,it is getting undefined (checked by console.log)..
Is there a way to do it by just single campaign/list?
Here's my development.js code of mailchimp credentials:
mailchimp: {
api_key: "***************-***",
list_id1: "*********", //internal users
list_id2: "*********" //internal deactivated users
},
my user.helper.js
const config = require('../../config/environment');
const Mailchimp = require('mailchimp-api-3');
const mailchimp = new Mailchimp(config.mailchimp.api_key);
exports.addToDeactivatedList = function (email, name) {
console.log(mailchimp.list_id1);
mailchimp.members.create(config.mailchimp.list_id1, {
email_address: email,
merge_fields: {
FNAME: name
},
status: 'subscribed'
}).then(user => { }).catch(e => {
console.log("deactivate list me add ho gya");
})
}
exports.addToActivatedList = function (email, name) {
console.log(mailchimp.list_id2);
mailchimp.members.create(config.mailchimp.list_id2, {
email_address: email,
merge_fields: {
FNAME: name
},
status: 'subscribed'
}).then(user => { }).catch(e => {
console.log("activate list me add ho gya");
})
}
and my user.controller.js (selective part only)
var helper = require('./user.helper');
.
.
if(req.body.status != user.status){
(req.body.status == "active") ? helper.addToActivatedList(user.email, user.name) : helper.addToDeactivatedList(user.email, user.name);
}
All the help will be appreciated. THANKS
I'd try to put everyone in the same list, and then create segments based on that list. After that, create a campaign based on that segment.
You could for instance create a custom list attribute that records wether or not an account is activated and create a segment based on that attribute. The campaign should then be based on that segment.
Perhaps also record the date an account has been activated or deactivated by the admin in another custom attribute and use that to check if a user already had an activation/deactivation mail.
MailChimp offers a feature for situations like this called automations. Automations allow you to send individual emails to subscribers when an event is triggered. So instead of creating separate campaigns every time a user is activated or deactivated, you can use just two automations and a single list.
Whether a user is active or not can be tracked with list merge fields. To do this, you'll need to add a new text merge field to your list. Let's name the field label 'Active'. Uncheck the 'Visible' checkbox so the user can't see it, and name your merge field something like 'ACTIVE'. You can use values like yes/no or true/false to identify the users by their active status.
Next, create your automations, one for activated users and one for deactivated users. You can set a trigger to send the email when a list field value is changed. So just make each of your two automations send the emails when the 'Active' list field values change to either 'yes' or 'no'.
Then all you need to do with the API is subscribe users to a single list whenever their accounts are activated or deactivated. Just make sure the new 'ACTIVE' merge field is set to 'yes' or 'no' when you do this, and any addresses already subscribed will be updated with the new value. So your mailchimp.members.create() would look something like this, based on the example from here:
mailchimp.members.create(<list_id>, {
email_address: <user_email>,
merge_fields: {
FNAME: name,
ACTIVE: 'yes' //Or 'no' if being used for deactivated users
},
status: 'subscribed'
})

How to manage new Breeze entities which are aggregate roots?

I have a domain model which has a Customer, which in turn has 1 Address (1:1) and 1 or more Phone numers (1:M).
Customer has user supplied PK (a string), while Address and Phone use identity column (server generated).
I am struggling in trying to understand how to manage Breeze entity creation for a "Add new Customer" screen.
The form on the screen allows user to enter Customer, Address, and Phone data.
I am using Durandal and Knockout so my "customeradd.js" viewmodel looks something like this:
// -- snip ---
var customer = ko.observable(),
hasChanges = ko.computed(function () {
return datacontext.hasChanges();
});
var vm = {
hasChanges: hasChanges,
customer: customer,
activate: activate
};
return vm;
function activate() {
customer(datacontext.createCustomer());
}
// -- snip ---
and my "/services/datacontext.js" :
// -- snip ---
breeze.NamingConvention.camelCase.setAsDefault();
var manager = new breeze.EntityManager(config.remoteServiceName);
var hasChanges = ko.observable(false);
manager.hasChangesChanged.subscribe(function (eventArgs) {
hasChanges(eventArgs.hasChanges);
});
function createVehicle() {
return manager.createEntity("Customer");
}
// -- snip ---
My questions are following:
Once I create a Customer, do I need to create Address and list of Phones and add them to Customer entity before making it a KO observable? Or is this done automatically by createEntity() method?
How do I create a Customer but without having to specify the Id? If I set the key to null or '', Breeze complains ("Error: Cannot attach an object to an EntityManager without first setting its key or setting its entityType 'AutoGeneratedKeyType' property to something other than 'None'"). However, if I generate the temp key (using either breeze.core.getUuid() or something else), then it shows up in my Id form field, and I really want the end user to specify it....Do I have to resort to extending the entity with extra field and then do the swapping and validation before saving (I don't like this idea at all)? Is there a better way?
In order to enable/disable buttons on my form I am tracking if there are changes in EntityManager. But every time entity is created, it is automatically in 'added' state so hasChanges is true. What I want is for changes to be picked up only if user edits the form (and therefore makes changes to underlaying entity). What is the best way to approach this?
BTW, I have seen this recommendation to register custom constructor for entity (I have already implemented it but I am still not clear how to let user supply their own id and to flag entity as modified only when user edits it...)
I realize this has been up for a while, but here are my thoughts (in case anyone comes looking).
If you use the entityManager to create your customerl and everything is specified correctly in the metadata, you can just create the customer and add phone numbers/addresses as needed. Breeze automatically makes an entity's properties observable (if specified correctly and if breeze knows that KO is being used)
If you can only do it the way that you say, then you are stuck. Ideally, you would have a user-entered ID which is NOT the key (though you could still force it to be unique) and a database-generated key, which Breeze will manage behind the scenes (assigning a negative key until it is saved to the data store, then updating the key and all related keys without any input from you).
if you use the 2nd approach for answer 2, then your buttons can easily be enabled and disabled using ko data-binding. When you create the entity, save its value to the viewmodel (custSource). Then you can add to the save button the data-bind="disable: custSource == Customer(), enable: custSource != Customer()". (You might need to play around with the syntax -- I haven't tested that part yet)
I don't think you need a custom constructor unless you are doing something different from what I understand.
PS. you should be aware that I believe Breeze wants Knockout defined as 'ko', while Durandal definitely expects it to be 'knockout', so you will probably need a 'map' property in your require.config
I think you could solve some of your problems by taking a slightly different approach to your entity creation. Here's your current approach:
Create a customer entity
User modifies that entity
Save the changes
Instead, try this:
User enters customer information
Create and save the customer entity
I realize that this doesn't really answer your questions, but I've found the second approach to be much easier to implement. Just have the user enter all the information you need to create a customer, and then supply those values to createEntity.
customeradd.js
// -- snip ---
var vm = {
customerId: ko.observable(),
address: ko.observable(""),
phoneNumbers: ko.observableArray([]),
submit: submit
};
return vm;
function submit() {
datacontext.createCustomer(
vm.customerId(),
vm.address(),
vm.phoneNumbers());
}
// -- snip ---
/services/datacontext.js
// -- snip ---
/**
* Creates a new customer.
* #param {int} id - The customer's id number.
* #param {string} address - The customer's address.
* #param {string[]} phoneNumbers - An array of the customer's phone numbers.
*/
function createCustomer(id, address, phoneNumbers) {
return manager.createEntity("Customer",
{
id: id,
address: address,
phoneNumber: phoneNumbers
});
}
// -- snip ---

Something weird about express session store

If I store an object in session like this:
user.name = "Kelvin"; // user is an object pass by "mongoose" findOne's callback.
req.session.user = user;
console.log(req.session.user.name); // Kelvin
and after that, I access "user" in other routes of express:
app.get("/somepath", function(req, resp) {
console.log(req.session.user.name); // undefined
});
I want to know why req.session.user.name is undefined besides the function I set it?
After looking into mongoose source code I can say that this is because how mongoose models and session works. When you call req.session.user = user then req.session.user points to the object but actually the data needs to be stored somewhere (like memory or Redis).
In order to do that Express calls JSON.stringify(sess) and string is stored in memory. Here's where mongoose enters. Models are constructed in such a way, that when you stringify them only attributes predefined in Schema are included. So when you set req.session.user = user Express stringifies user (you loose name attribute) and saves the data. When you call req.session.user in another route Express parses stringified data and you obtain object without name attribute.
Now how to fix this? There are several ways.
Add name attribute to the Schema;
Define new literal object for user:
var newuser = { id: user.id, name : "Kelvin", pwd: user.pwd, etc. };
req.session.user = newuser;
Store name in another field: req.session.name = "Kelvin"; (the best solution imho)
By the way: You shouldn't hold the user object in session. What if some other user like administrator makes changes to the user object? You won't see them at all. I advice holding only the id of the user in session and make custom middleware to load user from DB and store it in request object.

Resources