Mongoose/Typescript issue - node.js

So, i have to update some data inside my main interface, the problem is that, when i tried to do that, it complains because .save() is not defined
So i create another interface to that data in order to extends Document so i can have access to .save()
But, here's the new error....
const theComment: IComment
Type 'Comment' is missing the following properties from type 'IComment': $getAllSubdocs, $ignore, $isDefault, $isDeleted, and 47 more.
Here's my code
What i want to update ( the problem is theComment )
export const editComment = async (req: Request, res: Response) => {
const {publicationId} = req.params;
const { identifier, body, commentId } = req.body;
// Check id's
if (!mongoose.Types.ObjectId.isValid(identifier!))
return res.status(400).json({ Message: "identifier not valid" });
if (!mongoose.Types.ObjectId.isValid(publicationId!))
return res.status(400).json({ Message: "identifier not valid" });
if (!mongoose.Types.ObjectId.isValid(commentId!))
return res.status(400).json({ Message: "identifier not valid" });
// Find pub
const thePub: Ipub = await Publication.findById(publicationId);
// Find user
const theUser: Iauth = await User.findById(identifier);
// Find comment, make sure that comment is from that user
const theComment: IComment = thePub.comments!.find((f) => f.id === commentId && f.identifier === theUser.id)!;
if(!theComment) return res
.status(405)
.json({ Message: "You are not the owner of the comment || Comment doesn't exist" })
// Make sure body is not empty
if(!body) return res.status(404).json({Message: 'No data provided'})
try {
// Update comment and perfil if it has changed
theComment.body = body;
theComment.perfil = theUser.perfil;
await theComment.save()
return res.json(theComment)
} catch (err) {
console.log(err);
return res.status(500).json({ Error: "the API failed" });
}
};
Main interface
export interface Ipub extends Document {
id?: string;
body: string;
photo: string;
creator: {
name: string;
perfil?: string;
identifier: string;
};
likes?: Likes[];
comments?: Comment[];
createdAt: string;
}
Data's interface that i want to update inside my main interface
export interface IComment extends Document {
id?: string;
body: string;
name: string;
perfil?: string;
identifier: string;
createdAt: string;
likesComments?: Likes[];
}
What can i do ? how can i solve it ?
Thanks for your time comunnity !!

TS Compiler says the object described by Comment interface doesn't have .save() method. And as far as I presume it should not have because it's not a MongoDB document.
The time you inherit all props from Document interface the compiler throws the error saying that types Comment & IComment are not compatible because the second one has Document props, and the first one doesn't. To fix it you should just cast the type directly like this:
const theComment = thePub.comments!.find((f) => f.id === commentId && f.identifier === theUser.id)! as IComment;
But in order to update the comment you have to update 'whole' Publication document(for example, by using aggregate):
Publication.update(
{
"id": publicationId,
"comments.id": commentId,
"comments.identifier": theUser.id,
},
{ $inc: {
"comments.$.body": body,
"comments.$.perfil": theUser.perfil,
}},
false,
true,
);
Or the best option I think is to use relationships between Documents. Create another Document named Comment and save all related comments there. In that case you will have an ability to use .save() and other methods provided.

Related

gets undefined when i call my param in backend node controller

Here is the request type:
interface IgetProductsByGenderRequest extends express.Request {
readonly params: Readonly<{ gender: string; }>;
}
When I'm using req.params.gender I get undefined.
When I'm using req.params I get the param but in object and I want to get it in a var.
My backend controller:
const getProductsByGender = async (
req: IgetProductsByGenderRequest,
res: IgetProductsByGenderResponse
) => {
console.log(req.params)
ServerGlobal.getInstance().logger.info(
`<getProductsByGender>: Start processing request filtered by and gender ${req.params.gender}`
);
if (
!ServerGlobal.getInstance().isValidGenderValue(+req.params.gender)
) {
ServerGlobal.getInstance().logger.error(
`<getProductsByGender>: Failed to get products because of invalid gender filtered by gender ${req.params.gender}`
);
res.status(400).send({
success: false,
message: "Please provide valid gender",
});
return;
}
try {
const products = await ProductDB.find({ gender: +req.params.gender });
ServerGlobal.getInstance().logger.info(
`<getProductsByGender>: Successfully got the products filtered by gender ${req.params.gender}`
);
res.status(200).send({
success: true,
message: "Successfully retrieved products",
data: products.map((product) => ({
id: product.id as string,
gender: {
value: product.gender,
label: ServerGlobal.getInstance().getGenderLabel(product.gender)!,
},
title: product.title,
description: product.description,
price: product.price,
imageFilename: product.imageFilename,
})),
});
return;
} catch (e) {
ServerGlobal.getInstance().logger.error(
`<getProductsByGender>: Failed to get products filtered by gender ${req.params.gender} because of server error: ${e}`
);
res.status(500).send({
success: false,
message: "Server error",
});
return;
}
};
How can I access the gender param?
I suggest reading the documentation regarding routing and regarding query parameters.
You have req.query, which is for query parameters, e.g. /some/path?myVariable=test would have req.query.myVariable === 'test'.
You also have req.params which is when you're using URL parameters, e.g. /some/path/:id would have req.params.id === 'test' when the user visits /some/path/test.
Make sure you are using/accessing the correct one, as it's easy to make mistakes in this regards. And of course watch out for typos, although you should've spotted that with your console.log(req.params) statement.

Mongoose with typescript?

I have a project in nodejs and typescript. I'm using mongoose to connect to a mongoDb database. My code looks like this
import { Schema, Document, Model } from 'mongoose';
import * as mongoose from 'mongoose';
export interface IProblem extends Document {
problem: string;
solution: string;
}
const ProblemSchema = new Schema({
problem: { type: String, required: true },
solution: { type: String, required: true },
});
export async function findOneByProblem(
this: IProblemModel,
{ problem, solution }: { problem: string; solution: string }
): Promise<IProblem> {
const record = await this.findOne({ problem, solution });
return record;
}
export default mongoose.model('Problem', ProblemSchema);
ProblemSchema.statics.findOneByProblem = findOneByProblem;
export interface IProblemModel extends Model<IProblem> {
findOneByProblem: (
this: IProblemModel,
{ problem, solution }: { problem: string; solution: string }
) => Promise<IProblem>;
}
However, at these lines
const record = await this.findOne({ problem, solution });
return record;
I get a compiler error saying this
TS2322: Type 'IProblem | null' is not assignable to type 'IProblem'.   Type 'null' is not assignable to type 'IProblem'.
Am I missing something?
Your type for findOneByProblem is wrong – after all, it's possible that you don't find an IProblem instance, and the result is null.
The correct type is
Promise<IProblem | null>
– or you could internally if(problem === null) throw new Error("No Problem found"); or similar in the function if you don't want to change the type.

GraphQL error Cannot perform update query

I'm working on a ToDo list application in NodeJS, Koa, and GraphQL.
I wrote an update card mutation but when I run the query to update I get the following error:
Cannot perform update query because update values are not defined. Call "qb.set(...)" method to specify updated values.
The mutation:
import { getRepository } from 'typeorm';
import { Card } from '../../entities/card';
export const updateCardMutation = {
async updateCard(_, { id, patch }): Promise<Card> {
const repository = getRepository(Card);
const card = await repository.findOne({ id });
const result = await repository.update(id, patch);
return {
...card,
...patch,
};
},
};
I would like to know what I'm doing wrong and if something more is needed it please notify me so I will edit the question accordingly
card entity:
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm';
#Entity('cards')
export class Card {
#PrimaryGeneratedColumn('uuid')
id: string;
#CreateDateColumn()
created_at: Date;
#UpdateDateColumn()
updated_at: Date;
#Column('text')
title: string;
#Column('text', {
nullable: true,
})
description: string;
#Column('boolean', {
default: 'false',
})
done: boolean;
}
You need to spread the update Object.
export const updateCardMutation = {
async updateCard(_, { id, patch }): Promise<Card> {
const repository = getRepository(Card);
const card = await repository.findOne({ id });
const result = await repository.update(id, {...patch}); // here
return {
...card,
...patch,
};
},
};
The issue was when I was calling the updateMutation, it was creating the patch object of anonymous type. So it just needed to be clean before going to the DB engine
I resolved my issues by adding the following code:
{ ...patch }
Inside the next script:
export const updateCardMutation = {
async updateCard(_, { id, patch }): Promise<Card> {
const repository = getRepository(Card);
const card = await repository.findOne({ id });
const result = await repository.update(id, { ...patch }); // Added here
return {
...card,
...patch,
};
},
};
In this way, I was able to update my card.
https://github.com/typeorm/typeorm/blob/master/docs/update-query-builder.md
As an error Call qb.set() , typeorm query builder are different with other orm
await getRepository().createQueryBuilder().update(Card).set(patch)
.where("id = :id", { id })
.execute();
some how patch object may stringify [object], so you can spread it like set({...patch})
I have had this error before with my update query is nestjs & graqhql
Cannot perform update query because update values are not defined
I have fixed it by using the save() function from the repository on the same id, so I have changed from this
async update(
id: number,
updateEmployeeInput: UpdateEmployeeInput,
): Promise<Employee> {
await this.employeeRepository.update(id, updateEmployeeInput);
return this.employeeRepository.findOneOrFail(id);
}
to this
async update(
id: number,
updateEmployeeInput: UpdateEmployeeInput,
): Promise<Employee> {
await this.employeeRepository.save(updateEmployeeInput)
return this.employeeRepository.findOneOrFail(id);
}

typescript jwt.verify cannot access data

I'm trying to use JWT with nodejs.
My problem is that I can't read the data from the JWT verify function.
I'm using it like this :
//encode when logging in
const token = jwt.sign(
{ user: user },
'secret'
);
// decode when fetching the user from token
const decoded = jwt.verify(req.body.jwtToken, 'secret');
return res.send({
user: decoded.user // <-- error here
});
Here are the typings for the verify method:
export declare function verify(
token: string,
secretOrPublicKey: string | Buffer,
): object | string;
linter Error is :
Property user does not exists on typeof "object|string".
How am I supposed to get the data from the decoded token?
Link to the documentation of the library
When using Typescript, you have to remember everything is typed as in e.g. Java or C#.
object is a superclass that has no knowledge of the property user.
While this code is valid in javascript (you are looking at javascript documentation), it is not in typescript.
To fix this error, cast the decoded token using any.
return res.send({
user: (<any>decoded).user
});
You need to cast the decoded token. Although casting to any will work, you'll also lose type checking on that variable.
A more robust approach is to declare an interface that captures the structure of your decoded token and cast using it.
// token.ts
export interface TokenInterface {
user: {
email: string;
name: string;
userId: number;
};
}
and then you can cast using
decoded as TokenInterface
or more exactly in your case
return res.send({
user: (decoded as TokenInterface).user
});
Notes:
The casting is done at compile time, not runtime
Creating an interface has the added benefit that you keep your type definition in one place. This is especially useful if you want to add a field of that particular type to an existing object. An example of this is adding the token as a field on object of the express.Request type.
Create a user payload interface
interface UserPayload {
id: string;
}
interface JwtExpPayload {
expiresIn: string;
exp: number;
}
Cast to UserPaylod
try {
const jwtPayload = jwt.decode(
req.header('authorization')!
) as JwtExpPayload;
req.jwtPayload = jwtPayload;
const payload = jwt.verify(
req.header('authorization')!,
process.env.JWT_KEY!
) as UserPayload;
req.currentUser = payload;
} catch (err) {
console.log(err);
}
middleware function
export const requireAuth = (
req: Request,
res: Response,
next: NextFunction
) => {
if (req.jwtPayload && Date.now() >= req.jwtPayload!.exp * 1000) {
throw new TokenExpiredError();
}
if (!req.currentUser) {
throw new NotAuthorizedError();
}
next();
};
const userJwt = jwt.sign(
{
id: existingUser.id,
},
process.env.JWT_KEY!,
{ expiresIn: 30 }
);

what would be an extensible structure for node.js with native mongodb driver in Typescript

using node.js with the native mongodb driver is there a way to enforce a schema/schemaless structure by using classes or interfaces with Typescript (ES6). For example, if we have a collection called users. and we do something along the lines of
//Interface Library
interface INewUser {
name: string,
age: number,
location: string
}
//DAL
function insertUser(data: INewUser, cb: MongoCallBack < InsertOneWriteOpResult > ) {
//for best practices we would have an instance connected to db already
//so we do not have to open a new connection on every request
MongoClient.connect(uri, function(err, db) {
//handle err first
db.collection("users", function(err, col) {
//handle err first
col.insertOne(data, function(err, response: InsertOneWriteOpResult) {
//MongoCallBack expects (MongoError, <T>) T = InsertOneWriteOpResult
return cb(null, response)
});
});
});
}
//api endpoint
app.post('/user', function(req, res) {
//do some checks to make sure the req.body is valid -- i.e not empty no xss attacks..
let newUser: INewUser = req.body;
insertUser(newUser, function(err, res) {
if (res.res.ok == 1) {
res.json({
success: true
});
}
});
});
/*example data being posted to /user
{
name: "test",
age: 33,
location: "NA"
} */
I'm looking to find some of the best styles/techniques that could be used to structure data flow throughout an application utilizing typescript.
I have an application which is in production and works in a similar way that you described.
Though It's not mongodb and it uses an ORM sequelize with MySQL. Back in the past I've used mongoose, but this was not the requirement with the application I'm working on right now.
Nevertheless I use something similar :
// models/User.js
export interface UserAttributes {
name: string;
age: number;
location: string;
}
interface UserInstance extends UserAttributes {
id: number;
}
Then in my AJAX end-points I use Joi, for validating the schema
import { UserAttributes } from "../models/User";
const schema = Joi.object( {
name : Joi.string().required(),
age: Joi.string().required(),
location: Joi.string().required()
} );
server.post("/user", function( req, res ) {
const body = req.body;
Joi.validate( body, schema, function( errors ) {
if ( !errors ) {
const user : UserAttributes = req.body;
db.User.create( user ).then( user => res.json( { user } ) );
} else {
// handle error logic
}
});
});
This has the benefit that you validate the model before you assign it to your interface. Making sure that you will always have a valid user after the AJAX call.

Resources