Property 'save' does not exist on type 'Ipub[]' | mongoose - typescript - node.js

i'm trying to update a schema in my backend app (with node/express/typescript), but when i do .save() it breaks because the type is not like type it's type[]
And in my app is happening the following error
Property 'save' does not exist on type 'Ipub[]'
This is the schema
import { Schema, model } from "mongoose";
import { Ipub } from "../helpers/PublicationInterfaces";
const publication = new Schema<Ipub>({
body: { type: String },
photo: { type: String },
creator: {
name: { required: true, type: String },
perfil: { type: String },
identifier: { required: true, type: String }
},
likes: [
{
identifier: { type: String }
}
],
comments: [
{
body: { type: String },
name: { type: String },
perfil: { type: String },
identifier: { type: String },
createdAt: { type: Date, default: new Date() },
likesComments: [
{
identifier: { type: String }
}
]
}
],
createdAt: { required: true, type: Date, default: new Date() }
});
export default model("Publications", publication);
This is schema's interface
import { Document } from "mongoose";
// Publication's representation
export interface Ipub extends Document {
body: string;
photo: string;
creator: {
name: string;
perfil?: string;
identifier: string;
};
likes?: [
{
identifier: string;
}
];
comments?: [
{
body: string;
name: string;
perfil: string;
identifier: string;
createdAt: Date;
likesComments: [
{
identifier: string;
}
];
}
];
createdAt: Date;
}
And this is where the code breaks, look the line of code where i put await updateSub.save()
// Look for user's publications
const usersPub: Ipub[] = await Publication.find();
const updatePub = usersPub.filter(
(p: Ipub) => p.creator.identifier === specificUser.id
);
// update photo
try {
if (banner !== "") {
specificUser.banner = banner;
} else if (perfil !== "") {
specificUser.perfil = perfil;
updatePub.map((pub: Ipub) => (pub.creator.perfil = perfil));
await updatePub.save();
}
await specificUser.save();
return res.json(specificUser);
} catch (err) {
console.log(err);
return res.status(500).json({ Error: "The API Failed" });
}
It's really weird, it looks like it's because of the Ipub[] type definition, what can i do about it !
Thanks for your help !

Related

How to update a sub document in two levels in MongoDB (mongoose)

Purpose: Update a comment
"mongoose": "^6.7.0",
I've searched a few places for different ways to solve this problem, but to no avail.
My schema: (GalleryModule)
Here is the mongoose model creation
{
user: {
type: {
_id: {
type: String,
required: true,
},
name: {
type: String,
required: true,
},
},
required: true,
},
list: {
type: Array<{
_id: {
type: String;
required: true;
};
name: {
type: String;
required: true;
};
references: {
type: Array<{
type: {
type: string;
required: true;
};
body: {
type: string;
required: true;
};
}>;
};
comments: {
type: Array<{
_id: {
type: string;
required: true;
};
body: {
type: string;
required: true;
};
createdAt: {
type: Number;
required: true;
};
updatedAt: {
type: Number;
required: true;
};
}>;
};
createdAt: {
type: Number;
required: true;
};
updatedAt: {
type: Number;
required: true;
};
}>,
},
}
`The execution:
I look for a user with the ID, then I update some data
I only update the data that came in the body of the request
const User = await GetOneId(UserModule, req.body.UserId);
if (!User.body) {
return res.status(404).json({
message: "Usuário não existe no banco de dados",
});
}
var body = {};
if (req.body?.body) {
body["list.$[item].comments.$[score].body"] = req.body.body;
}
body["list.$[item].comments.$[score].user.name"] = User.body.name;
body["list.$[item].comments.$[score].user.image"] = User.body.image_small;
const response = await UpdateSubDoc<GalleryResponse>(
GalleryModule,
{
"list.$[item].comments.$[score].updatedAt": Date.now(),
},
[
{ "item._id": req.params.itemId },
{ "score._id": req.params.commentId },
],
{ _id: req.params.galleryId }
);
return res.status(response.status).json(response.body);
function: (UpdateSubDc)
Here is a reusable function in several pieces of code to update sub documents
export const UpdateSubDoc = async <S, TSchema = any, T = any>(
Schema: Model<S>,
Body: AnyKeys<TSchema> & AnyObject,
Filter: { [key: string]: any }[],
Id?: FilterQuery<T>
) => {
const response = await Schema.updateMany(
Id || {},
{ $set: Body },
{ arrayFilters: Filter }
);
return {
status: 200,
body: response,
};
};
Error:
Error: Could not find path "list.0.comments.0._id" in schema

Any better way for me to select between three models based on some variable?

I am currently working on an Express.js API that handles getting and saving various content types, storing one record per piece of content. My issue that I encountered a while ago was how do I select which Mongoose model to use for queries (mainly find and save) depending on the content type specified to the route handler. I want to do this the right way (or rather, I want to use best practices).
Long story short I looked for help and I used an object instead of a map, which led to more issues due to the following object having the following type:
const contentTypeModels = {
"image": imageModel,
"video": videoModel,
"letter": letterModel
};
Type info of contentTypeModels as declared and initialized
const contentTypeModels: {
image: Model<IImage, {}, {}, {}, any>;
video: Model<IVideo, {}, {}, {}, any>;
letter: Model<ILetter, {}, {}, {}, any>;
}
The issue here was that the type expected in the route handler was
Model<IImage | IVideo | ILetter>
Figured this out sometime yesterday (don’t remember exactly how tbh)
I was able to get a working solution with further assistance but I want to know if there’s a better way to dynamically select a model to perform queries on, or how to resolve the type issues I’m currently having if there is no better way than to use an object.
Here’s the interfaces and models for each content type, as well as one route handler and some accompanying code from the top of the file (imports excluded)
const contentTypeModels = {
'image': imageModel,
'video': videoModel,
'letter': letterModel
};
type contentNames = 'image' | 'video' | 'letter' | null;
// other code
// …
const getOneFromDb = (req: Request, res: Response, next: NextFunction) => {
// get object from Db
/*
make request at the URL <contentType>/<mongoId>
*/
const mongoId = req.params.mongoId;
// was getting an error before with this cause i didn’t limit the values of strings that could be accepted by the rest of the code
/*
Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ image: Model<IImage, {}, {}, {}, any>; video: Model<IVideo, {}, {}, {}, any>; letter: Model<ILetter, {}, {}, {}, any>; }'.
*/
const content = req.params.contentType;
// uncommenting the line below yields the error ‘string’ is not assignable to contentNames
// const content:contentNames = req.params.contentType;
// resolved that error with the following
/*
let content:contentNames;
if (req.params.contentType === 'image' || req.params.contentType === 'video' || req.params.contentType === 'letter') {
content = req.params.contentType;
} else {
content = null;
throw 'bad request: please specify a content type';
}
*/
const contentType = contentTypeModels[content];
if (mongoId.length > 0) {
// the error i was getting before was on the findById() function, it was something like:
/* This expression is not callable.
Each member of the union type '{ <ResultDoc = Document<unknown, any, ILetter> & ILetter & { _id: ObjectId; }>(id: any, projection?: ProjectionType<ILetter> | null | undefined, options?: QueryOptions<...> | ... 1 more ... | undefined, callback?: Callback<...> | undefined): Query<...>; <ResultDoc = Document<...> & ... 1 more ... & { ...; }>(id: any...' has signatures, but none of those signatures are compatible with each other.
*/
contentType.findById(mongoId)
.then(fileFound => {
if (fileFound) {
res.status(200).send(fileFound);
} else {
next(createHttpError(404, 'file not found in db'));
}
})
.catch((err: HttpError) => {
next(createHttpError(500, err));
});
} else {
next(createHttpError(400, 'please make sure your URL is of the format <contentType>/<mongoId>'));
}
};
The interfaces:
IImage
interface IImage {
// interface IImage extends IPieceofContent {
firstName: string;
lastName: string;
email: string;
phoneNumber: string;
IP: string;
customData?: object;
URL: string;
caption: string;
}
export {
IImage
};
ILetter
interface ILetter {
// interface ILetter extends IPieceofContent {
firstName: string;
lastName: string;
email: string;
phoneNumber: string;
IP: string;
customData?: object;
twitterHandle: string;
title: string;
description: string;
town: string;
}
export {
ILetter
};
IVideo
interface IVideo {
// interface IVideo extends IPieceofContent {
firstName: string;
lastName: string;
email: string;
phoneNumber: string;
IP: string;
customData?: object;
URL: string;
caption: string;
}
export {
IVideo
};
And the schemas:
imageModel
import {Schema, model} from 'mongoose';
import { IImage } from '../interfaces/IImage';
// For video and image they can use the same schema, but they are different models
const imageSchema = new Schema<IImage>({
firstName: {
required: true,
type: String
},
lastName: {
required: true,
type: String
},
email: {
required: true,
type: String
},
phoneNumber: {
required: true,
type: String
},
URL: {
required:true,
type: String
},
caption: {
required:true,
type: String
},
IP: {
required:true,
type: String
},
// has a type of Mixed
customData: {},
},
{ timestamps: true }
);
const imageModel = model<IImage>('images', imageSchema);
export {
imageModel
};
letterModel
import { ILetter } from '../interfaces/ILetter';
import { Schema, model } from 'mongoose';
const letterSchema = new Schema<ILetter>({
firstName: {
required: true,
type: String
},
lastName: {
required: true,
type: String
},
email: {
required: true,
type: String
},
phoneNumber: {
required: true,
type: String
},
twitterHandle: {
required: false,
type: String
},
title: {
required: true,
type: String
},
description: {
required: true,
type: String
},
town: {
required: true,
type: String
},
IP: {
required: true,
type: String
},
// has a type of Mixed
customData: {},
},
{ timestamps: true }
);
const letterModel = model<ILetter>('letters', letterSchema);
export {
letterModel
};
videoModel
import {Schema, model} from 'mongoose';
import { IVideo } from '../interfaces/IVideo';
const videoSchema = new Schema<IVideo>({
firstName: {
required: true,
type: String
},
lastName: {
required: true,
type: String
},
email: {
required: true,
type: String
},
phoneNumber: {
required: true,
type: String
},
URL: {
required:true,
type: String
},
caption: {
required:true,
type: String
},
IP: {
required:true,
type: String
},
// has a type of Mixed
customData: {},
},
{ timestamps: true }
);
const videoModel = model<IVideo>('videos', videoSchema);
export {
videoModel
};
TL:DR: trying to select between three content types based on info in request, idk how best to do this, plz help.

TypeScript : MongoDB set method doesn't exist

I am working on an application in TypeScript and this is the first time I have gotten this error.
I am unable to use set method on an instance of a model.
Here is the model:
import mongoose from 'mongoose';
import { OrderStatus } from '#karantickets/common';
import { updateIfCurrentPlugin } from 'mongoose-update-if-current';
interface OrderAttrs {
id: string;
version: number;
userId: string;
price: number;
status: OrderStatus;
};
interface OrderDoc extends mongoose.Document {
version: number;
userId: string;
price: number;
status: OrderStatus;
};
interface OrderModel extends mongoose.Model<OrderDoc> {
build(attrs: OrderAttrs): OrderDoc;
};
const orderSchema = new mongoose.Schema({
userId: { type: String, required: true },
price: { type: Number, required: true },
status: { type: String, required: true }
}, {
toJSON: {
transform(doc, ret) {
ret.id = ret._id;
delete ret._id;
}
}
});
orderSchema.set('versionKey', 'version');
orderSchema.plugin(updateIfCurrentPlugin);
orderSchema.statics.build = (attrs: OrderAttrs) => {
return new Order({
_id: attrs.id,
version: attrs.version,
userId: attrs.userId,
price: attrs.price,
status: attrs.status
});
};
const Order = mongoose.model<OrderDoc, OrderModel>('Order', orderSchema);
export { Order };
And here is where I am using the <object.set()> method:
export class OrderCancelledListener extends Listener<OrderCancelledEvent> {
queueGroupName = queueGroupName;
subject: Subjects.OrderCancelled = Subjects.OrderCancelled;
async onMessage(data: OrderCancelledEvent['data'], msg: Message) {
const order = Order.findOne({
_id: data.id,
version: data.version - 1
});
if (!order) throw new Error('Order not found');
order.set({ // < -------------------------------- ERROR !!
status: OrderStatus.Cancelled
});
msg.ack();
}
}
Error: Property 'set' does not exist on type 'DocumentQuery<OrderDoc | null, OrderDoc, {}>'
Thanks in advance!

Mongoose schema lost a number value while saving object to database

I have an issue with mongoose + nest.js API. I created a model as this:
const pagesSchema = new mongoose.Schema({
name: { type: String },
timeOn: { type: Number},
});
const cartItemsSchema = new mongoose.Schema({
itemName: { type: String },
itemAction: { type: String },
});
const buyedItemsSchema = new mongoose.Schema({
itemName: { type: String },
itemQuantity: { type: Number },
});
export const SessionSchema = new mongoose.Schema(
{
userId: { type: String },
sessionId: { type: String, required: true },
userIp: { type: String, required: true },
visitsCounter: { type: Number },
visitDate: { type: String },
device: { type: String },
browser: { type: String },
location: { type: String },
reffer: { type: String },
pages: [ pagesSchema ],
cartItems: [ cartItemsSchema ],
buyedItems: [ buyedItemsSchema ],
didLogged: { type: Boolean },
didContacted: { type: Boolean }
}
);
export interface Session extends mongoose.Document {
userId: string,
sessionId: string,
userIp: string,
visitCounter: number,
visitDate: string,
device: string,
browser: string,
location: string,
reffer: string,
pages: [
{
name: string,
timeOn: number
}],
cartItems: [
{
itemName: string,
itemAction: string
}],
buyedItems: [
{
itemName: string,
itemQuantity: number
}],
didLogged: boolean,
didContacted: boolean
}
That sends data to database with post service:
#Injectable()
export class SessionsService {
constructor(#InjectModel('Session')
private readonly sessionModel: Model<Session>) {}
async insertSession(
sessionId: string,
userIp: string,
visitDate: string,
device: string,
browser: string,
location: string,
reffer: string
) {
let userId: string;
let visitCounter: number;
await this.getUserId(userIp).then(
id => { userId = id as string; }
);
await this.getVisitCounter(userIp).then(
counter => { visitCounter = counter as number; }
);
const newSession = new this.sessionModel({
userId: userId,
sessionId: sessionId,
userIp: userIp,
visitCounter: visitCounter,
visitDate: visitDate,
device: device,
browser: browser,
location: location,
reffer: reffer,
pages: [],
cartItems: [],
buyedItems: [],
didLogged: false,
didContacted: false,
});
console.log(visitCounter);
console.log(newSession);
newSession.visitCounter = visitCounter;
console.log(newSession.visitCounter);
// w tym momencie gubi mi zmienną visitCounter
const result = await newSession.save();
return result.visitCounter;
};
private async getUserId(usIp: string): Promise<string> {
const session = await this.sessionModel.findOne( { userIp: usIp });
if (!session) return '_' + Math.random().toString(36).substr(2, 9);
else return session.userId as string;
};
private async getVisitCounter(usIp: string): Promise<number> {
const sessions = await this.sessionModel.find({ userIp: usIp });
return sessions.length as number;
#Post()
async insertSession(
#Body('sessionId') sessionId: string,
#Body('userIp') userIp: string,
#Body('visitDate') visitDate: string,
#Body('device') device: string,
#Body('browser') browser: string,
#Body('location') location: string,
#Body('reffer') reffer: string
) {
const ssId = await this.sessionsService.insertSession(
sessionId,
userIp,
visitDate,
device,
browser,
location,
reffer
);
return ssId;
};
But as result I get object with database with no visitCounter:
{
_id:5ed7a19817754d55f071b62d,
userId:"_satr",
sessionId:"_satr",
userIp:"11.1111.11",
visitDate:"12/12/09",
device:"desktop",
browser:"chrome",
location:"portugal",
reffer:"none",
pages:Array,
cartItems:Array,
buyedItems:Array,
didLogged:false,
didContacted:false,
__v:0
}
As a result I get post data in the database, but the save function lost my visitCounter value. console.logs prints that the visitCounter exists in the item, but not in the schema. Why while saving object to database by mongoose I got all data except visitCounter saved?

Node js + Objection js + Postgresql. Argument of type '{ token: string }' is not assignable to parameter of type 'PartialUpdate<User>'

Environment:
node js
ES6
knex: ^0.16.3
objection: ^1.5.3
pg: ^7.8.0 ~ postgresql
Problem:
I can't update user token in the database. I get an error message from typescript.
Typescript error message:
Argument of type '{ token: string; }' is not assignable to parameter
of type 'PartialUpdate<User>'. Object literal may only specify known
properties, and 'token' does not exist in type 'PartialUpdate<User>'.
Problem method
If I write #ts-ignore, the method will work, but I can't understand.
Why does it give me an error?
import { User } from '#database/models';
...
const setToken = async (id: any, token: string) => {
try {
await transaction(User.knex(), trx =>
User.query(trx)
// An error appears on this line
.update({ token })
.where({ id }),
);
} catch (e) {
throw e;
}
};
My user model
'use strict';
import { Model } from 'objection';
export default class User extends Model {
static get tableName() {
return 'users';
}
static get jsonSchema() {
return {
type: 'object',
properties: {
id: { type: 'uuid' },
full_name: { type: 'string', minLength: 1, maxLength: 255 },
email: { type: 'string', minLength: 1, maxLength: 255 },
avatar: { type: 'string' },
provider_data: {
type: 'object',
properties: {
uid: { type: 'string' },
provider: { type: 'string' },
},
},
token: { type: 'string' },
created_at: { type: 'timestamp' },
updated_at: { type: 'timestamp' },
},
};
}
}
The problem was that I did not define the types of variables in my model. An example from the official library that gave me know about what I did wrong - https://github.com/Vincit/objection.js/tree/master/examples/express-ts
Updated model
export default class User extends Model {
readonly id!: v4;
full_name?: string;
email?: string;
avatar?: string;
provider_data?: {
uid: string;
provider: string;
};
token?: string;
static tableName = 'users';
static jsonSchema = {
type: 'object',
properties: {
id: { type: 'uuid' },
full_name: { type: 'string', minLength: 1, maxLength: 255 },
email: { type: 'string', minLength: 1, maxLength: 255 },
avatar: { type: 'string' },
provider_data: {
type: 'object',
properties: {
uid: { type: 'string' },
provider: { type: 'string' },
},
},
token: { type: 'string' },
created_at: { type: 'timestamp' },
updated_at: { type: 'timestamp' },
},
};
}
Updated method
const setToken = async (id: any, token: string) => {
try {
User.query()
.update({ token })
.where({ id });
} catch (e) {
throw e;
}
};
**please provide code with variable partialUpdate. because you are having an error because of the wrong declaration of the type of variable partialUpdate. TypeScript fully focused on variable Type and if the Type of the variable does not match the content you are providing to that variable type error generates. you are passing object type value {token:string} to your variable partialUpdate which can only hold string type variable when you declared it. **
PartialUpdate:Object
or
PartialUpdate = {}
will solve the problem.

Resources