Mongo Cursor update - javascript

I am using TypeScript/Javascript with Meteor/Mongo.
I am trying to update a cursor with a timestamp on its lastMessageCreatedAt field.
updateChat(senderId: string, chatId: string): void {
const chatExists = !!Chats.find(chatId).count();
if (!chatExists) throw new Meteor.Error('chat-not-exists',
'Chat doesn\'t exist');
const chat1 = Chats.find(chatId);
const receiverId = chat1.memberIds.find(memberId => memberId != senderId); // <=== error TS2339: Property 'memberIds' does not exist on type 'Cursor<Chat>'.
const chat = {
memberIds: [senderId, receiverId],
lastMessageCreatedAt: new Date()
};
Chats.update(chat); // <=== error TS2346: Supplied parameters do not match any signature of call target.
},
model
interface Chat {
_id?: string;
memberIds?: string[];
title?: string;
picture?: string;
lastMessage?: Message;
lastMessageCreatedAt?: Date;
receiverComp?: Tracker.Computation;
lastMessageComp?: Tracker.Computation;
}
Question
However, I get the above errors. How do I update the cursor to have the timestamp? I am new to Meteor/Mongo, so I may be approaching this entirely wrong.

The update code is incorrect. It should be something like this
var date=new Date();
Chats.update({membersIds:{$all:[senderId,receiverId]}},{$set:{lastMessageCreatedAt: date}});
Refer docs for more information.

First if there is a single chat, its preferable to use the mongo findOne(), rather than find(), especially if you are using record _id. Remember the find(), or findOne() takes a query json object, and an optional projection (Mongo speak for the fields to return) json object. If the projection is omitted it returns all fields.
const chatFound = Chats.findOne(
{ '_id':
{ $eq: chatId},
});
Similarly you can use the update() or updateOne() mongo methods. Just read the docs as they are slightly different, but essentially take a query, update, & options json object.
Chats.update(
{ '_id':
{ $eq: chatId},
},
{ 'memberIds': memberIds,
'lastMessageCreatedAt': new Date()
}
);
One Mongo feature that is very useful, but doesn't appear to be needed for your situation, is the upsert, which either inserts or updates a record....
Chats.update(
{ '_id':
{ $eq: chatId},
},
{ 'memberIds': memberIds,
'lastMessageCreatedAt': new Date()
},
{ upsert: true }
);
Finally remember you can use the Mongo command line to test your query before coding. Run Meteor Mongo from your terminal window.

Related

Graphql Date of the event is passed, how to update automaticly is Completed?

I have no idea how to phrase this question and to be honest, when I think about it, this should have a really simple answer but for some reason, I couldn't find the best way to do it.
I'm building a React Native app with a Prisma backend. I don't want to involve the front end at all to solve this. I have a session model that has a date of the event and isCompleted param. I would like to be able to update isCompleted when the date of the event is in the past automatically. Backend is running on Heroku server, without setting up a cron job or something else, is this possible to do?
here is the model and update code I have, but not sure where to tie this to make sure it always updates all the sessions based on the time.
export const SessionUpdate = {
updateSessionCompletion: async (parent, args, context): Promise<any> => {
try {
const session = await prisma.session.findUnique({
where: {
id: args.id,
},
});
if (session && session.date < new Date()) {
console.log(session.date, new Date());
return await prisma.session.update({
where: {
id: args.id,
},
data: {
isCompleted: true,
},
});
}
} catch (error) {
console.log(error);
}
},
}
model Session {
id String #id #default(dbgenerated()) #map("_id") #db.ObjectId
createdAt DateTime #default(now())
// updatedAt DateTime #updatedAt
isCompleted Boolean #default(false)
title String
date DateTime
isCanceled Boolean #default(false)
}

Mock Relay-style pagination with Apollo Server

I have a mock server using Apollo Server to return GraphQL responses. One of the queries is to get teams which has an array of metrics (see schema below):
const mocks = {
Query: () => ({
teams: (/*parent, args, context, info*/) => teamsFixture,
}),
};
const graphServer = new ApolloServer({ typeDefs: schema, mocks });
graphServer.applyMiddleware({ app });
And my query used to be (fields redacted):
teams {
bpxId
metrics {
timestamp
}
}
The real (java) server has changed this to allow me to query (Relay style) only the first item in the metrics array as it wasn't performant:
teams {
bpxId
metrics(first: 1) {
edges {
node {
timestamp
}
}
}
}
With the response in this shape:
metrics: {
edges: [
{
node: [Team]
__typename: "TeamMetricsConnectionEdge"
}
]
__typename: "TeamMetricsConnection"
}
I want to add this capability to my Apollo Server mocks but not sure how?
New schema (relevant bits):
type TeamMetrics {
timestamp: Int
# etc
}
type TeamMetricsConnection {
edges: [TeamMetricsConnectionEdge]
pageInfo: PageInfo
}
type PageInfo {
hasPreviousPage: Boolean!
hasNextPage: Boolean!
startCursor: String
endCursor: String
}
type Query {
teams(bpxId: Int): [Team]
}
type Team {
bpxId: Int!
metrics(first: Int! = 5, after: String): TeamMetricsConnection
}
How can I adjust my mock response to handle Relay pagination? Thanks
You can use graphql-relay to create mock resolver results. This could be an easy way to create pagination for static mock arrays. Use connectionFromArray to wrap an array of mocks to automatically create an object structure that fits the connection type. It is possible to access all the field arguments in the mock similarly how you would do it in a real resolver and pass them into the function.
I am not sure if this works when you call the function on a MockList though. Pagination is a bit tricky as well if the length of the results changes all the time (the pageInfo.hasNextPage might be mixed up and you might violate a lot of assumptions that Relay has according to the spec). So it might be fine to start with a simple static array if that satisfies your mocking needs.

TypeError: elements.get is not a function error in firebase functions

I am getting the below error log when running a firebase function, i am trying to get documents and values inside recentPosts array field.
Error: Unknown error status: Error: Unknown error status: TypeError: elements.get is not a function
at new HttpsError (/srv/node_modules/firebase-functions/lib/providers/https.js:90:19)
at admin.firestore.collectionGroup.where.get.then.catch.error (/srv/lib/index.js:71:15)
at <anonymous>
at process._tickDomainCallback (internal/process/next_tick.js:229:7)
Class:
class PostTable {
public commentCount : number;
public dateTime : number;
public docId : string;
public post : string;
public userId : string;
public userName : string;
constructor(commentCount: number, dateTime: admin.firestore.Timestamp, docId: string, post : string, userId : string, userName : string) {
this.commentCount = commentCount
this.dateTime= dateTime.toDate().getTime()
this.docId=docId
this.post=post
this.userId=userId
this.userName=userName
}
}
function:
export const getPosts = functions.https.onCall((data, context) => {
if (!context.auth) {
// Throwing an HttpsError so that the client gets the error details.
throw new functions.https.HttpsError('failed-precondition', 'The function must be called ' +
'while authenticated.');
}
let responseCollection : PostTable[] = []
admin.firestore().collectionGroup('recentPostColl').where('users', "array-contains",context.auth.token.name)
.get()
.then(collectionOfPosts => {
if(!collectionOfPosts.empty)
{
collectionOfPosts.forEach(element => {
let collection : Map<String, Object>[] = element.get('recentPosts')
collection.forEach(elements => {
try {
const p : PostTable = new PostTable(elements.get('commentCount')as number, elements.get('dateTime') as admin.firestore.Timestamp
,elements.get('docId') as string,elements.get('post') as string, elements.get('userId') as string, elements.get('userName') as string);
const stamp : admin.firestore.Timestamp = elements.get('dateTime') as admin.firestore.Timestamp
const date : Date = stamp.toDate()
if(date.getTime() > new Date(data.date).getTime())
{
responseCollection.push(p)
}
} catch (error) {
throw new functions.https.HttpsError(error, 'Constructor error');
}
});
});
}
})
.catch(error =>{
throw new functions.https.HttpsError(error, 'code error');
})
return responseCollection
})
Document:
In your code, collectionOfPosts is a QuerySnapshot object. You're using its forEach method to iterate the QueryDocumentSnapshot objects contained within it. Then, you're using get() to grab a specific field out of that snapshot. That field is going to be represented as plain JavaScript array type, since the field is an array. It's not a strongly typed array - you should assume that all the field types are plain JavaScript types and not TypeScript collection types. You can't simply cast a plain JavaScript types to a TypeScript collection types.
You'll need to provide a specific, safe assertion you can make about that field (assuming it contains exactly what you think) in JavaScript terms:
let collection : object[] = element.get('recentPosts')
You can then index into collection as if it was a normal array:
let item = collection[0]
or iterate it as you would any other JavaScript array.
The individual items will be object type, and you also deal with them as if they were plain old JavaScript objects.
If you want to deal with TypeScript collection types instead, you will have to write some code to convert all those JavaScript arrays and objects to their TypeScript collection type equivalents.

Function with dynamic return in typescript

I am trying to create an API on NodeJS with typescript
I have the following interfaces :
export interface ISqlResonse<T> {
success?: string
error?: string
data: Array<T> //Nothing | array of object when are db operations
}
export interface IApiResponse<T> {
status: 'error' | 'success'
message: string
data: Array<T>
}
Each api call call a function that call an generic class name DB that select/insert/update/delate data from an database
For example the update function look like :
async updateData(input: IUpdateParam) : Promise<ISqlResonse<object>> {
...
...
}
API function call DB and look like :
async update(req): Promise<IApiResponse<IAccessPointsTableStructure>> {
let data = req.body ;
let updateObj = {
data ,
table: 'accessPoints',
excludeColumns: 'loggedUserId',
additionalColumns: { modifiedBy: '1', modifiedAt: crtDate },
validationRules,
where: `id=${data.id}`,
returningData: true
}
let sqlResults = await db.updateData(updateObj) ; // !!!
if(typeof sqlResults.error==="string") {
logger.log('error','Error on updating Access Points!',{sql: db.getQuery(), error: sqlResults.error});
return({status:'error',message: 'Error on updating Access Points!',data: sqlResults.data});
}
logger.log('success', 'Access Points data updated with success!');
return({status: 'error', message: 'Access Points data updated with success!', data: sqlResults.data})
}
My question is : how can I call the function db.updateData() and tell this function that I want to receive in data from ISqlResponse an array with objects like interface IAccessPointsTableStructure.
With other words i want to control the returning type of function. I teste several times with different approaches . (Replace wit in db.updateData(...) <..>...
Thank you in advice.
You haven't included the definition of IUpdateParam, but I will assume that its table property is what decides the type of thing updateData() returns. Everywhere I've commented "guess" is just for example; you should change them to fit your use cases.
You should be able to modify the signature for the updateData() to reflect the relationship between the type of IUpdateParam passed in and the type of Promise<ISqlResponse<{}>> returned. Here's one way to do it, using generics (you could use overloads instead). First, declare a type to represent the mapping between the table names and the data type for each table. For example:
export type TableNameToTypeMapping = {
accessPoints: IAccessPointsTableStructure,
otherThings: IOtherThingsTableStructure, // guess
// ...
}
Now, you can change the definition of IUpdateParam so that it only accepts the right values for table:
export interface IUpdateParam<K extends keyof TableNameToTypeMapping> {
data: any, // guess
table: K,
excludeColumns: string, // guess
additionalColumns: any, // guess
validationRules: any, // guess
where: string // guess
}
So an IUpdateParam<'accessPoints'> object is meant to deal with the accessPoints table, and it is different from an IUpdateParam<'otherThings'> object.
Now the signature for updateData() can be changed to:
async updateData<K extends keyof TableNameToTypeMapping>(
input: IUpdateParam<K>
): Promise<ISqlResonse<TableNameToTypeMapping[K]>> {
// implement this! The implementation is likely not
// type-safe unless you use runtime type guards
}
This means if you call updateData with a parameter of type IUpdateParam<'accessPoints'>, you will get back a Promise<ISqlResponse<TableNameToTypeMapping['accessPoints']>>. But TableNameToTypeMapping['accessPoints'] is just IAccessPointsTableStructure, so you are getting back a Promise<ISqlResponse<IAccessPointsTableStructure>> as desired.
Note that the object literal updateObj will have its table property inferred as type string, which is too wide. To make sure the call to updateData() works as desired, you will either need to assert that the updateObj.table property is of literal type 'accessPoints', like this:
let updateObj = {
data,
table: 'accessPoints' as 'accessPoints', // assertion
excludeColumns: 'loggedUserId',
additionalColumns: { modifiedBy: '1', modifiedAt: crtDate },
validationRules,
where: `id=${data.id}`,
returningData: true
}
or you will need to declare that updateObj is of type IUpdateParam<'accessPoints'>, like this:
// type declaration
let updateObj:IUpdateParam<'accessPoints'> = {
data ,
table: 'accessPoints',
excludeColumns: 'loggedUserId',
additionalColumns: { modifiedBy: '1', modifiedAt: crtDate },
validationRules,
where: `id=${data.id}`,
returningData: true
}
Either way should work.
Hope that helps; good luck!

Sequelize not returning instance of model

The sequelize documentation (http://docs.sequelizejs.com/en/v3/docs/raw-queries/) states:
If you pass a model the returned data will be instances of that model.
// Callee is the model definition. This allows you to easily map a query to a predefined model
sequelize.query('SELECT * FROM projects', { model: Projects }).then(function(projects){
// Each record will now be a instance of Project
})
I have defined a model for a resource called agent.
module.exports = function(sequelize, DataTypes) {
let Agent = sequelize.define('Agent', {
responseStatus: DataTypes.STRING,
agentnum: {
type: DataTypes.STRING,
primaryKey: true,
allowNull: false,
field : 'agentno'
},
fname : {
type: DataTypes.STRING,
allowNull : false,
field: 'fname'
},
lname : {
type: DataTypes.STRING,
allowNull: false,
field : 'lname'
},
fullname : {
type: DataTypes.STRING,
allowNull : false,
field: 'full_name'
},
status : {
type: DataTypes.STRING,
allowNull: false,
field: 'business_status'
},
loginDate: DataTypes.DATE
}, {
freezeTableName: false,
timestamps: false
});
return Agent;
};
And when calling sequelize.query with my query and specifying model:Agent, I get an error thrown from sequelize:
TypeError: this.model.bulkBuild is not a function
the stack points to sequelize\lib\dialects\abstract\query.js:675.
This error persists until I apply a QueryType of sequelize.QueryTypes.RAW. At this point the query completes and I get a JSON response but it is not an instance of my Agent model. The JSON response from the sequelize query contains field names that should be mapped to.
I have imported my model (its just one) according to the directions found in their express sample (https://github.com/sequelize/express-example/blob/master/models/index.js). The models collection shows that my Agent model is included.
import Sequelize from 'sequelize';
import config from './config';
export default callback => {
const sequelize = new Sequelize(config.database, config.username, config.password, config.params);
sequelize.sync().then(function() {
let db = { }
let agentModel = sequelize.import('model/agent.js');
db[agentModel.name] = agentModel;
db.sequelize = sequelize;
db.Sequelize = Sequelize;
db.sequelize.authenticate().then(function() {
console.log('CONNECTION OK');
});
callback(db);
}).catch(function(err) {
console.log('FAILED TO CONNECT: ', err.message);
});
}
I want the query to return an instance of Agent when that query is run (invoked from a POST to my api). I am using MS SQL Server 2008 R2.
Any input is appreciated. Thanks.
EDIT 1/30 Here is the code generating the sequelize object, and passing in the model. The model collection shows my item is added, but it has no properties.
connectDb: (function () {
var sequelize;
function createInstance() {
var sequelizeInstance, connectedAndAuthenticated;
sequelizeInstance = new Sequelize(config.database, config.username, config.password, config.params);
connectedAndAuthenticated = sequelizeInstance.authenticate();
connectedAndAuthenticated.sequelize = sequelizeInstance;
connectedAndAuthenticated.Sequelize = Sequelize;
var model = sequelizeInstance.import('../src/model/agent.js');
return connectedAndAuthenticated;
}
return {
getInstance : function () {
if (!sequelize) {
sequelize = createInstance();
}
return sequelize;
}
};
}())
EDIT 1/26 After manipulating the QueryTypes, I discovered two things - that I inadvertently created a table in the database with the name of the model (Agent), and that the object returned has a tablename property value of empty. The schema and tablename are specified by me, but the query, being a stored procedure that joins a number of queries and tables, does not directly map to an object in my database named Agent. That being said, the documentation to me seems to suggest that this does not and should not matter, as I am creating my own model that is bound to the query result.
sequelize doc is confusing .i'm explaining you clean way to use sequelize
put
var models = require('../models');
in your code file be sure models directory contain index.js as you told me in your question and also Project model. be careful, other then correctly configured model there must not anything.
Now put
models.sequelize.query("select 1 as val").then(function(result){
console.log(result)
})
in your code
to check connection also you should use find query like
models.Projects.findAll().then(function(result){
console.log(result)
})
It seems like a simple typo. I don't think Agent is actually defined in your scope. I think you should be passing agentModel or whatever you bound the import to.
let agentModel = sequelize.import('model/agent.js');
db.sequelize.query( "DECLARE #response VARCHAR(256); EXEC API_Login #agentnum = N'" + agentNum + "', #hashedPassword = '" + password + "', #response = #response OUTPUT; SELECT #response AS N'response'",{ model: agentModel, type: sequelize.QueryTypes.RAW}) .spread(function(Agent) { res.status(200).json(Agent); }) .catch(function(err) { handleError(err, res); });
Note I'm using {model: agentModel, ...} not {model: Agent, ...} because Agent is undefined outside of the callback.
Your error TypeError: this.model.bulkBuild is not a function makes perfect sense if Agent is not actually a model but something else (or undefined).
UPDATE
You mention in comments on the post below that: "I have synced the model - the query attempts to create a table, instead of binding to the passed in agent model" and "It should not be creating a table".
Why do you think that is that is the case? Creating the table during sync() is normal behaviour for Sequelize.
I think you misunderstand how Sequelize works. It creates a table for every registered model during sync. If it can't create that table it might be returning an invalid model and that is why you are getting errors.
Models are explicity tied to individual database tables, that's the fundamental behavior of Sequelize. Each model instance represents a row of that table. If you are working with stored proceedures then you are probably better off using using the native database library and defining your own abstraction layer.
I'm sure you can disable and/or override all the default synchronization between the model and the underlying DB table but at a certain point or complexity you'd have basically just written your own abstraction library and it would be cleaner to just do it from scratch.
Maybe you can use Sequelize the way you want but at the very least you should not be calling sync(). What side-effects that leads to I can't say but I can say that unless you define your own beforeSync hook sync() will always create a table in your schema:
Model.prototype.sync = function(options) {
options = options || {};
options.hooks = options.hooks === undefined ? true : !!options.hooks;
options = Utils._.extend({}, this.options, options);
var self = this
, attributes = this.tableAttributes;
return Promise.try(function () {
if (options.hooks) {
return self.runHooks('beforeSync', options);
}
}).then(function () {
if (options.force) {
return self.drop(options);
}
}).then(function () {
return self.QueryInterface.createTable(self.getTableName(options), attributes, options, self);
})
...
}).return(this);
};

Categories

Resources