So I am attempting to build a one on one chat app with firebase using marionette and backbone. I'm also using the Firebase Backbone bindings. I want there to be many users, each user has many conversation, and each conversation has many messages. Here's how my data looks:
users: {
'user-id-1': {
email: 'test#whatever.com',
conversations: {
'conversation-id-1': true,
'conversation-id-2': true
}
},
'user-id-2': {
email: 'another#email.com',
conversations: {
'conversation-id-1': true,
}
},
'user-id-3': {
email: 'a-third#email.com',
conversations: {
'conversation-id-2': true,
}
}
}
conversations: {
'conversation-id-1': {
sender: 'user-id-1',
receiver: 'user-id-2',
messages: {
'message-id-1': true,
'message-id-2': true
}
},
'conversation-id-2': {
sender: 'user-id-1',
receiver: 'user-id-3',
messages: {
'message-id-3': true,
'message-id-4': true,
'message-id-5': true,
}
}
}
messages: {
'message-id-1': {
sender: 'user-id-1'
body: 'A message'
},
'message-id-2': {
sender: 'user-id-2'
body: 'A response'
},
'message-id-3': {
sender: 'user-id-1'
body: 'An inital message'
},
'message-id-4': {
sender: 'user-id-3'
body: 'Another response'
},
'message-id-5': {
sender: 'user-id-1'
body: 'Goodbye'
},
}
What you can see from this:
User-1 has two different conversations going, each with User-2 and User-3
On a really basic level I am able to fetch all the conversations from the user with a function like so:
class Entities.User extends Backbone.Firebase.Model
urlRoot: 'https://my-app.firebaseio.com/users'
conversations: ->
conversations = new Firebase('https://my-app.firebaseio.com/conversations')
#firebase.child('conversations').on 'child_added', (snap) =>
console.log snap.key()
conversations(snap.key()).once 'value', (value) =>
console.log value.val()
undefined
This will log all of the conversations out when they are retrieved by firebase.
The problems are:
a) There's no real way for me to use a Backbone.Collection here to fetch just this users conversations, let alone the messages for that conversation, is there? I need a collection to pass to Marionettes CollectionView
b) This just seems really messy in general.
Should I be structuring my data differently? I realize there's improvements that could be made to at least the way I am collecting the conversations so I could get an array.
What am I doing wrong? Any advice would be much appreciated.
Related
I'm a student working on a chat application for my internship, where I use socket.io.
Right now I am busy thinking of a good way to store the messages send in conversations.
As of now I do the following:
For each conversation between one user and another user, a new collection is made.
On every message sent, the message is stored in the according conversation collection in a single document.
The collections:
Where the document looks as follows:
Now I wonder if there is a good argument to be made to have just one collection "conversations", and store all the messages in multiple documents, where each conversation is a new document.
Creating a new collection for every message is very bad idea instead of that you use a simple schema as given below to store your messages
const conversation_schema = new Schema({
from: {
type: ObjectID,
ref: 'User'
},
to: {
type: ObjectID,
ref: 'User'
},
messageBody: { // body of the message(text body/ image blob/ video blob)
type: String,
},
messageType: { // type of the message(text, mp3, mp4, etc...)
type: String,
},
read: { // to boolean flag to mark whether the to user has read the message
type: Boolean,
default: false,
},
createdAt: { // when was this message goit created
type: Date,
default: new Date(),
},
});
you can fetch the conversation between the two users using the following query
conversations.find({
$or: [
{from: 'user1', TO: 'user2},
{from: 'user2', TO: 'user1},
],
}).populate({ path: 'to', model: User })
.populate({ path: 'from', model: User })
.sort({ createdAt: -1 })
I'm trying to create a typescript aws cdk to build up an API Gateway with this own swagger documentation.
There is one simple endpoint returning a list of "Supplier", but we don't know how to specify this in the cdk.
Here the code:
export function CreateSupplierMethods(apigw: apigateway.Resource,restApiId: string, scope: cdk.Construct, api: apigateway.RestApi) {
let suppliers = apigw.addResource('suppliers')
let supplierModel = new apigateway.Model(scope, "supplier-model", {
modelName: "supplier",
restApi: api,
contentType: 'application/json',
schema: {
description: "Supplier data",
title: "Supplier",
properties: {
code: { type: apigateway.JsonSchemaType.STRING, minLength: 4, maxLength: 6},
name: { type: apigateway.JsonSchemaType.STRING, maxLength: 81},
}
},
})
let getSuppliers = suppliers.addMethod('GET', new apigateway.MockIntegration(), {
methodResponses: [{
statusCode: "200",
responseModels: {
"application/json": supplierModel,
}
},
{
statusCode: "401",
}]
})
}
As you can see, the GET has the supplierModel as output.
How can I say "returns a list of supplierModel"? I wish I can use this model for both list of supplier and single instances of supplier (like a GET method with id as input).
Is this possible? If yes, how?
Looking the generated json, I'm trying to have something like this:
But what I'm getting now is quite different:
How can I get a result like the first image?
You are creating a model and assigning it to the method.
Create an array of those models and then assign that array to the method.
let supplierModelArray = new apigateway.Model(scope, "supplier-model-array", {
modelName: "supplier-array",
restApi: api,
contentType: 'application/json',
schema: {
description: "Supplier data",
title: "Supplier",
type: apigateway.JsonSchemaType.ARRAY
items: {type: supplierModel}
},
})
And change the "application/json": supplierModel in api to "application/json": supplierModelArray
When sending emails through SendGrid's Node mail client ("#sendgrid/mail": "^6.4.0") we are adding some custom_args to the JSON submitted and all of them are text values. The webhooks for processed, delivered, open, and click events return the custom_args to us as expected. But when we receive a webhook for the bounce event the custom_args are not attached.
We are submitting:
{ personalizations: [
{ to: {
email: 'recipient#example.com',
name: 'Recipient Name'
}
}
],
from:
{
email: 'sender#example.com',
name: 'Sender Name'
},
reply: {
email: 'replyto#example.com',
name: 'Reply To Name'
},
custom_args:
{ envelopeId: '4aa4f5f8-9ba4-4ec3-a6cf-3098107f498d',
messageId: '105',
eventId: '251' },
subject: 'Test Email 1234',
content:
[ { type: 'text/plain',
value:
'This is a sample email, please click here: https://example.com' },
{ type: 'text/html',
value:
'This is a sample email, please <a href=\'https://example.com\'>click here</a>' } ],
mail_settings: { sandbox_mode: { enable: false } } }```
Got an answer back from SendGrid on this one. The issue is when a mail server does a slow bounce where it takes them some time to sort out that they do not have anyone with that name locally, then they sometimes return a brand new email to the sending server and that causes SendGrid to loose all of the context that was placed in the initial email.
I am trying to push a newly created object to an array. The array is defined as
clientengagements: []
The Object is
engagement: []
So I am using a v-for to iterate through each engagement belonging to a client in my clientengagements array. Everything works fine until I submit a new engagement. It changes my clientengagements: [] array to only show the new object. Now if i refresh the page, the clientengagements: [] will go back to the array with the newly added object plus the other objects that already existed which is what I want it to do..
This is the AddEngagement component script that I use to dispatch to the store
methods: {
...mapActions(['addEngagement']),
addNewEngagement() {
if(!this.engagement.return_type || !this.engagement.year ) return;
this.addEngagement({
id: this.idForEngagement,
client_id: this.client.id,
return_type: this.engagement.return_type,
year: this.engagement.year,
assigned_to: this.engagement.assigned_to,
status: this.engagement.status,
})
.then(() => {
this.engagement = ""
this.idForEngagement++
this.$router.go(-1);
})
},
},
The action in the store is defined like below
addEngagement(context, engagement) {
axios.post(('/engagements'), {
client_id: engagement.client_id,
return_type: engagement.return_type,
year: engagement.year,
assigned_to: engagement.assigned_to,
status: engagement.status,
done: false
})
.then(response => {
context.commit('getClientEngagements', response.data)
})
.catch(error => {
console.log(error)
})
},
from there it should commit to the getClientEngagements() mutation which is where I believe I am running into my issue but I have not figured out how to resolve. Here is the code
getClientEngagements(state, clientengagements) {
state.clientengagements = clientengagements;
},
I have been recommended to use Vue.set() but I do not know how to apply it.. any help would be greatly appreciated!!!
So I have set the mutation getClientEngagements to use this now
getClientEngagements(state, engagement) {
state.clientengagements.push(engagement)
},
But by changing the mutation to this it has placed the already existing objects into a deeper nested array. see below
So the response.data for the new engagement only sends back from the backend that newly added engagement on the addEngagement action. is this a problem?
Thanks for all the help, but I actually ended up needing to add a extra mutation and use that as the commit, because I am defining to seperate lists of engagements. One for the client and one for all clients. here is the new mutation
addClientEngagement(state, engagement) {
state.clientengagements.push(engagement)
},
which I then use in my action here
addEngagement(context, engagement) {
axios.post(('/engagements'), {
client_id: engagement.client_id,
return_type: engagement.return_type,
year: engagement.year,
assigned_to: engagement.assigned_to,
status: engagement.status,
done: false
})
.then(response => {
context.commit('addClientEngagement', response.data)
})
.catch(error => {
console.log(error)
})
},
and then this mutation happens
getClientEngagements(state, clientengagements) {
state.clientengagements = clientengagements
},
Previously it was using this mutation to add the engagement to the array which was why it was replacing the array with the new object. see below for old mutation
addEngagement(state, engagement) {
state.engagements.push ({
id: engagement.id,
client_id: engagement.client_id,
return_type: engagement.return_type,
year: engagement.year,
assigned_to: engagement.assigned_to,
status: engagement.status,
done: false
})
},
Hopefully I am making sense in my explanation
.then(response => {
...// data transaction
context.commit('getClientEngagements', response.data)
})
Maybe you can do with the response data at first and then store it after that.
If the response data is an object, and you only want to store the value,
you can do something similar to this:
for( p in object ){
context.commit('getClientEngagements', object[p])
}
If the response data is an array, then you can try to use for to get the value you want and then push in the state.
You should do the transaction of data according to your needs at first for your case.
Update: I've gotten a fair bit further. Please see the bottom of the post...
I'm working on a project that is based on the sql-fullstack yeoman generator, and have been using the included example code as a guide. Things have progressed smoothly, for the most part, but I'm now in a scenario where I have two tables/models with a bidirectional n:m relationship:
TaskGroup:
module.exports = function(sequelize, DataTypes) {
var TaskGroup = sequelize.define("TaskGroup", {
taskGroupID: {
field: "TaskGroupID",
type: DataTypes.INTEGER,
allowNull: false,
unique: true,
autoIncrement: true,
primaryKey: true
},
name: {
field: "Name",
type: DataTypes.STRING,
allowNull: false
},
description: {
field: "Description",
type: DataTypes.STRING
},
modifiedBy: {
field: "ModifiedBy",
type: DataTypes.STRING
}
});
and Task:
module.exports = function(sequelize, DataTypes) {
var Task = sequelize.define("Task", {
taskID: {
field: "TaskID",
type: DataTypes.INTEGER,
allowNull: false,
unique: true,
autoIncrement: true,
primaryKey: true
},
name: {
field: "Name",
type: DataTypes.STRING,
allowNull: false
},
description: {
field: "Description",
type: DataTypes.STRING
},
isOnRunsheet: {
field: "IsOnRunsheet",
type: DataTypes.BOOLEAN
},
modifiedBy: {
field: "ModifiedBy",
type: DataTypes.STRING
}
});
Relationships:
// Tasks can belong to more than one group, and groups can belong to more than one task
db['TaskGroup'].belongsToMany(db['Task'], {as: 'Tasks', through: 'TaskGrouping'});
db['Task'].belongsToMany(db['TaskGroup'], {as: 'TaskGroups', through: 'TaskGrouping'});
On the client side, the user is able to create a new task and specify the associated task groups through a multiple select list. When the task is saved, I have both the task fields and an array of the associated task groups. A post is made with the request body containing this information, so that the server can create the task record.
Unfortunately, I can't seem to get the record created. I've been through a number of iterations, and I'm at the point where I get what appears to be a reasonable exception - I'm just stumped as to what the "reasonable" thing to do is...
Exception:
Unhandled rejection SequelizeDatabaseError: Cannot insert the value NULL into column 'TaskTaskID', table 'HelpCard
.dbo.TaskGrouping'; column does not allow nulls. INSERT fails.
at Query.formatError (C:\Projects\helpcard2\node_modules\sequelize\lib\dialects\mssql\query.js:215:10)
at Request.userCallback (C:\Projects\helpcard2\node_modules\sequelize\lib\dialects\mssql\query.js:66:25)
at Request.callback (C:\Projects\node_modules\tedious\lib\request.js:33:27)
at Connection.message (C:\Projects\node_modules\tedious\lib\connection.js:1179:27)
at Connection.dispatchEvent (C:\Projects\node_modules\tedious\lib\connection.js:519:45)
at MessageIO.<anonymous> (C:\Projects\node_modules\tedious\lib\connection.js:439:23)
at emitNone (events.js:67:13)
at MessageIO.emit (events.js:166:7)
at ReadablePacketStream.<anonymous> (C:\Projects\node_modules\tedious\lib\message-io.js:92:15)
at emitOne (events.js:77:13)
...
Here's the code on the client side:
$scope.createTask = function() {
if($scope.newTask === '') {
return;
}
$scope.newTask.modifiedBy = 'tkturney';
var taskBundle = {
task: $scope.newTask,
taskGroups: $scope.selectedGroups
};
$http.post('/api/tasks', taskBundle);
setTimeout(function() {
$scope.currentTask = $scope.newTask;
$scope.newTask = '';
$scope.addingTask = false;
refreshTasks();
}, 250);
};
...and on the server side:
exports.create = function(req, res) {
var task = Task(req).build(req.body.task);
task.setTaskGroups(req.body.taskGroups);
task
.save()
.then(function() {
return res.status(201).json(task);
})
.catch(function (err){
if(err) { return handleError(res, err); }
});
};
I'm sure that I'm missing something obvious, but the documentation that I've found has been pretty light on a scenario like this. I would appreciate any guidance; I'm just getting into sequelize, and I feel that there are times that I may have bitten off more than I can chew... :)
Update: After taking a closer look at the SQL, I discovered that the exception was being thrown when trying to insert into the join table (TaskGroupings). It was trying to insert a NULL for the task's primary ID, which is generally not a good thing. Looking at the code, I realized that I was trying to add the association before I had saved the record, leaving me with no PK. Moving the task.addTaskGroups() after the save() took care of that issue.
However, I also realized that I was passing an array of TaskGroup objects to the 'addTaskGroup()` call, instead of the actual IDs. So, I modified the client-side controller like so:
$scope.createTask = function() {
if($scope.newTask === '') {
return;
}
$scope.groupKeys = [];
angular.forEach($scope.selectedGroups, function(taskGroup) {
$scope.groupKeys.push(taskGroup.taskGroupID);
});
$scope.newTask.modifiedBy = 'tkturney';
var taskBundle = {
task: $scope.newTask,
taskGroups: $scope.groupKeys
};
$http.post('/api/tasks', taskBundle);
...
When I look at the debugger, I can see everything in the taskGroup object, but taskGroup.taskGroupID is coming back as undefined, so I'm still getting an exception because I'm not passing the PKs for the other side of the association.
Does anything leap out as to what might be screwy with this code fragment?
Ok, by changing the server-side controller from this:
exports.create = function(req, res) {
var task = Task(req).build(req.body.task);
task.setTaskGroups(req.body.taskGroups);
task
.save()
.then(function() {
return res.status(201).json(task);
})
.catch(function (err){
if(err) { return handleError(res, err); }
});
};
To this:
exports.create = function(req, res) {
var task = Task(req).build(req.body.task);
task
.save()
.then(function() {
task.setTaskGroups(req.body.taskGroups);
return res.status(201).json(task);
})
.catch(function (err){
if(err) { return handleError(res, err); }
});
};
That particular exception went away. The thing that I was missing (though it was staring me in the face) was the fact that there are two separate inserts happening - one for the task, and one for the association. I was thinking that I needed to set the association before saving the task, not realizing that setting that association caused another insert.
I still need to figure out why the PKs for the other side of the association aren't getting populated, but that's outside the scope of the original question...