Mongoose Schema properties validation with Typescript NextJS - mongodb

i am trying to save new document to mongo db, the Schema validation is not working for me, i am trying ti make required true, but i still can add new document without the required field.
this is my schema:
// lib/models/test.model.ts
import { Model, Schema } from 'mongoose';
import createModel from '../createModel';
interface ITest {
first_name: string;
last_name: string;
}
type TestModel = Model<ITest, {}>;
const testSchema = new Schema<ITest, TestModel>({
first_name: {
type: String,
required: [true, 'Required first name'],
},
last_name: {
type: String,
required: true,
},
});
const Test = createModel<ITest, TestModel>('tests', testSchema);
module.exports = Test;
this is createModel:
// lib/createModel.ts
import { Model, model, Schema } from 'mongoose';
// Simple Generic Function for reusability
// Feel free to modify however you like
export default function createModel<T, TModel = Model<T>>(
modelName: string,
schema: Schema<T>
): TModel {
let createdModel: TModel;
if (process.env.NODE_ENV === 'development') {
// In development mode, use a global variable so that the value
// is preserved across module reloads caused by HMR (Hot Module Replacement).
// #ts-ignore
if (!global[modelName]) {
createdModel = model<T, TModel>(modelName, schema);
// #ts-ignore
global[modelName] = createdModel;
}
// #ts-ignore
createdModel = global[modelName];
} else {
// In production mode, it's best to not use a global variable.
createdModel = model<T, TModel>(modelName, schema);
}
return createdModel;
}
and this is my tests file:
import { connection } from 'mongoose';
import type { NextApiRequest, NextApiResponse } from 'next';
const Test = require('../../../lib/models/test.model');
import { connect } from '../../../lib/dbConnect';
const ObjectId = require('mongodb').ObjectId;
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
switch (req.method) {
case 'POST': {
return addPost(req, res);
}
}
}
async function addPost(req: NextApiRequest, res: NextApiResponse) {
try {
connect();
// const { first_name, last_name } = req.body;
const test = new Test({
first_name: req.body.first_name,
last_name: req.body.last_name,
});
let post = await test.save();
// return the posts
return res.json({
message: JSON.parse(JSON.stringify(post)),
success: true,
});
// Erase test data after use
//connection.db.dropCollection(testModel.collection.collectionName);
} catch (err) {
//res.status(400).json(err);
res.status(400).json({
message: err,
success: false,
});
}
}
in the Postman, i send a request body without the required field (first_name) and i still can add it.
any help?

Related

ConnectorError Prisma with MongoDB

I am developing an API with Nestjs and MongoDB. Well sometimes I have this problem. It doesn't always happen and that's what I have to solve because this error rarely happens.
This is the error:
{
"status": "error",
"message": "\nInvalid this.prisma.people.findMany() invocation in\nC:\ima\empresas\datec\api-official\src\modules\Events\services\utils.service.ts:57: 28\n\n 54 }),\n 55 \n 56 // Get attendees by id\n→ 57 this.prisma.people.findMany(\nError in batch request 3: Error occurred during query execution:\nConnectorError(ConnectorError { user_facing_error: None, kind: RawDatabaseError { code: "unknown", message: "An existing connection was forced to be terminated by the remote host. (os error 10054)" } })"
}
I really can't find much information to solve this error. I have followed the steps in the documentation on Nestjs when using prisma with nestjs. I need help to solve this error, since it is essential to find a solution as soon as possible. Thanks so much for read this question
UPDATE
The Prisma service
// Librarys
import { Injectable } from '#nestjs/common'
import { PrismaClient } from '#prisma/client'
// Interfaces
import { INestApplication, OnModuleInit, OnModuleDestroy } from '#nestjs/common'
#Injectable()
export class PrismaService
extends PrismaClient
implements OnModuleInit, OnModuleDestroy
{
async onModuleInit() {
await this.$connect()
}
async onModuleDestroy() {
await this.$disconnect()
}
async enableShutdownHooks(app: INestApplication) {
this.$on('beforeExit', async () => {
await app.close()
})
}
}
File that starts the application
//Libraries
import { NestFactory } from '#nestjs/core'
import { ValidationPipe } from '#nestjs/common'
import { SwaggerModule, DocumentBuilder } from '#nestjs/swagger'
import { useContainer } from 'class-validator'
// Modules
import { MainModule } from './main.module'
// config
import pk from '#root/package.json'
import { corstOptions } from '#config/cors'
import { PORT, APP_NAME, PUBLIC_URL } from '#config/env'
// Exceptions
import { HttpExceptionFilter } from '#utils/HttpExceptionFilter'
import { PrismaService } from '#root/src/services/prisma'
async function bootstrap() {
// Create application
const app = await NestFactory.create(MainModule)
// Enable cors
app.enableCors(corstOptions)
// Use global 'api' prefix, all calls will come after '/api/*'
app.setGlobalPrefix('api')
// Globally define custom response
app.useGlobalFilters(new HttpExceptionFilter())
// Enable prism on custom validations
useContainer(app.select(MainModule), { fallbackOnErrors: true })
// Get service from primsa and enable 'shutdowns'
const prismaService = app.get(PrismaService)
await prismaService.enableShutdownHooks(app)
// Use 'pipe' validation to validate the 'body' structure
app.useGlobalPipes(
newValidationPipe({
whitelist: true,
transform: true,
forbidUnknownValues: true,
forbidNonWhitelisted: true,
transformOptions: { enableImplicitConversion: true }
})
)
// Create API documentation
const config = new DocumentBuilder()
.addBearerAuth()
.setTitle(`API | ${APP_NAME}`)
.setContact(pk.author.name, pk.author.url, pk.author.email)
.setDescription(pk.description)
.setVersion(pk.version)
.build()
const document = SwaggerModule.createDocument(app, config)
SwaggerModule.setup('/', app, document)
// Listen to the application on the PORT defined in the environment variables
await app.listen(PORT || 0, () => {
// eslint-disable-next-line no-console
console.log(
'\x1b[33m%s\x1b[0m',
`[INFO] The server has been started at '${PUBLIC_URL}'`
)
})
}
bootstrap() // Run application
utils.service of Event module
// Librarys
import { Prisma } from '#prisma/client'
import { Inject, Injectable } from '#nestjs/common'
// Services
import { PrismaService } from '#services/prisma'
// DTO
import { EventBodyDTO } from '#modules/Events/events.dto'
// Arguments
import { EventsArgs } from '#modules/Events/events.args'
#Injectable()
export class UtilsService {
#Inject(PrismaService)
private readonly prisma: PrismaService
private readonly selects = {
'id.title': { id: true, title: true },
'id.fullname': { id: true, fullname: true },
'id.title.attributes': { id: true, title: true, attributes: true },
'id.code.description.attributes': {
id: true,
code: true,
description: true,
attributes: true
}
}
/**
* Get reminders, types of activities, case, client and attendees of an event
* #param {EventBodyDTO} payload Event data
*/
async getEventFields(payload: EventBodyDTO) {
const [activityType, eventCase, client, assistants, reminders] =
await Promise.all([
// Get an activity type by id
this.prisma.parameters.findUnique({
select: this.selects['id.title'],
where: { id: payload.activityType }
}),
// Get a case by id
this.prisma.expedients.findUnique({
where: { id: payload.case },
select: this.selects['id.code.description.attributes']
}),
// Get a person by id
this.prisma.people.findFirst({
select: this.selects['id.fullname'],
where: { isClient: true, id: payload.client }
}),
// Get attendees by id
this.prisma.people.findMany({
select: this.selects['id.fullname'],
where: {
isEmployee: true,
id: { in: payload.assistants }
}
}),
// Get reminders by id
this.prisma.parameters.findMany({
select: this.selects['id.title.attributes'],
where: {
id: { in: payload.reminders }
}
})
])
return {
reminders: reminders,
assistants: assistants,
client: client === null ? {} : client,
case: eventCase === null ? {} : eventCase,
activityType: activityType === null ? {} : activityType
}
/**
* Create filters to filter by customer or event attendees
* #param {EventsArgs} args Arguments to filter
* #returns {Promise<Prisma.EventsFindManyArgs['where']>} Returns an object with filters
*/
async createEventFilters(
args: EventsArgs
): Promise<Prisma.EventsFindManyArgs['where']> {
const filters: Prism.EventsFindManyArgs['where'] = {
userId: args.user
}
// Filter by customer
if (typeof args.client === 'string') {
const clients = await this.prisma.people.findMany({
where: {
isClient: true,
fullname: {
mode: 'insensitive',
contains: args.client
}
},
select: {
id: true,
fullname: true
}
})
filters.OR = []
for (const client of clients) {
filters.OR.push({
client: { equals: client }
})
}
}
// Filter by attendees
if (Array.isArray(args.assistants)) {
const assistants = await this.prisma.people.findMany({
where: {
isEmployee: true,
id: {
in: args.assistants
}
},
select: {
id: true,
fullname: true
}
})
if (!Array.isArray(filters.OR)) {
filters.OR = []
}
filters.OR.push({
assistants: { hasSome: assistants }
})
}
return filters
}
}

MissingSchemaError: Schema hasn't been registered for model in nextjs13

error - MissingSchemaError: Schema hasn't been registered for model "post".
Use mongoose.model(name, schema)
at Mongoose.model (/Users/mac/Practice/portfolio_projects/ai-image-generation/node_modules/mongoose/lib/index.js:549:13)
at eval (webpack-internal:///(api)/./src/lib/mongodb/models/post.ts:34:52)
at (api)/./src/lib/mongodb/models/post.ts (/Users/mac/Practice/portfolio_projects/ai-image-generation/.next/server/pages/api/post.js:62:1)
at webpack_require (/Users/mac/Practice/portfolio_projects/ai-image-generation/.next/server/webpack-api-runtime.js:33:42)
at eval (webpack-internal:///(api)/./src/pages/api/post.ts:9:82)
at (api)/./src/pages/api/post.ts (/Users/mac/Practice/portfolio_projects/ai-image-generation/.next/server/pages/api/post.js:82:1)
at webpack_require (/Users/mac/Practice/portfolio_projects/ai-image-generation/.next/server/webpack-api-runtime.js:33:42)
at webpack_exec (/Users/mac/Practice/portfolio_projects/ai-image-generation/.next/server/pages/api/post.js:92:39)
at /Users/mac/Practice/portfolio_projects/ai-image-generation/.next/server/pages/api/post.js:93:28
at Object. (/Users/mac/Practice/portfolio_projects/ai-image-generation/.next/server/pages/api/post.js:96:3)
for db connection
import { MongoClient } from "mongodb";
if (!process.env.MONGODB_URI) {
throw new Error('Invalid/Missing environment variable: "MONGODB_URI"');
}
const uri = process.env.MONGODB_URI;
const options = {};
let client;
let clientPromise: Promise<MongoClient>;
if (process.env.NODE_ENV === "development") {
// In development mode, use a global variable so that the value
// is preserved across module reloads caused by HMR (Hot Module Replacement).
if (!global._mongoClientPromise) {
client = new MongoClient(uri, options);
global._mongoClientPromise = client.connect();
}
clientPromise = global._mongoClientPromise;
} else {
// In production mode, it's best to not use a global variable.
client = new MongoClient(uri, options);
clientPromise = client.connect();
}
// Export a module-scoped MongoClient promise. By doing this in a
// separate module, the client can be shared across functions.
export default clientPromise;
**Post.tsx **
import * as mongoose from "mongoose";
import Joi from "joi";
type post = {
name: string;
prompt: string;
photo: string;
};
const PostSchema = new mongoose.Schema({
name: { type: String, required: true },
prompt: { type: String, required: true },
photo: { type: String, required: true },
});
function validatePost(data: post) {
const schema = Joi.object({
name: Joi.string().min(1).max(100).required(),
prompt: Joi.string().min(2).required(),
photo: Joi.string().min(0).required(),
});
return schema.validate(data);
}
const Post = mongoose.model("post") || mongoose.model("post", PostSchema);
export { validatePost };
export default Post;
**
Where i called post modal**
import type { NextApiRequest, NextApiResponse } from "next";
import clientPromise from "#/lib/mongodb/mongodb";
import { v2 as cloudinary } from "cloudinary";
import Post from "#/lib/mongodb/models/post";
import { validatePost } from "#/lib/mongodb/models/post";
cloudinary.config({
cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
api_key: process.env.CLOUDINARY_API_KEY,
api_secret: process.env.CLOUDINARY_API_SECRET,
});
export const config = {
api: {
bodyParser: {
sizeLimit: "50mb",
},
responseLimit: false,
},
};
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
await clientPromise;
if (req.method === "GET") {
try {
const posts = await Post.find({});
res.status(200).json({ success: true, data: posts });
} catch (err) {
res.status(500).json({
success: false,
message: "Fetching posts failed, please try again",
});
}
} else if (req.method === "POST") {
try {
const { error } = validatePost(req.body);
if (error) return res.status(400).send(error.details[0].message);
const { name, prompt, photo } = req.body;
const photoUrl = await cloudinary.uploader.upload(photo);
const post = new Post({
name,
prompt,
photo: photoUrl.url,
});
const newPost = await post.save();
res.status(200).json({ success: true, data: newPost });
} catch (err) {
res.status(500).json({
success: false,
message: "Unable to create a post, please try again",
});
}
}
}

Mongoose !: statics this undefined

import mongoose, { Schema, Document, Model } from "mongoose";
import { IUser } from "./interface/User.interface";
const userSchema = new Schema<IUserDoc>({
...
});
// Problem
userSchema.statics.findUser = async function (
email: Pick<IUser, "email">
): Promise<IUser | null> {
console.log("this", this); // undefined
try {
// Cannot read properties of undefined (reading 'findOne')
const user = await this.findOne({ email }).exec();
return user;
} catch (err: any) {
return err;
}
};
interface IUserDoc extends IUser, Document {
_id: string;
}
interface IUserModel extends Model<IUserDoc> {
findUser: (email: string) => Promise<IUser>;
}
const User = mongoose.model<IUserDoc, IUserModel>("User", userSchema);
export { User };
// Working
const findUserTest = async (email: string): Promise<IUser | null> => {
try {
const user = await User.findOne({ email }).exec();
return user;
} catch (err: any) {
return err;
}
};
mongoose -v 6.5.1
findUser does not work, but findUserTest works well. I think the problem is with userSchema, but I don't know what the problem is. I'd appreciate it if you could give me a hint.

Mongo Memory Server: Property 'getUri' does not exist on type '(opts?: MongoMemoryServerOpts) => Promise<MongoMemoryServer>'

I am trying to run e2e tests on a nestjs app.
I have trouble running MongoMemoryServer, in order to run set the MMS I used this article based on the original nestJs documentation.
I keep getting this error:
test/user-preferences.e2e-spec.ts:27:32 - error TS2339: Property 'getUri' does not exist on type '(opts?: MongoMemoryServerOpts) => Promise<MongoMemoryServer>'.
27 const uri = mongod.getUri();
~~~~~~
Test Suites: 1 failed, 1 total
This is the test I try to run:
import { Test, TestingModule } from '#nestjs/testing';
import { getModelToken, MongooseModule } from '#nestjs/mongoose';
import { MongoMemoryServer } from 'mongodb-memory-server';
import {
UserPreferences,
UserPreferencesDocument,
UserPreferencesSchema,
} from './../src/user-preferences/schemas/user-preferences.schema';
import { UserPreferencesModule } from './../src/user-preferences/user-preferences.module';
import * as request from 'supertest';
import { factory } from 'fakingoose';
import { Model } from 'mongoose';
describe('userPreferences controller', () => {
let userPreferencesModel;
let app;
const UserPreferencesFactory = factory<UserPreferencesDocument>(
UserPreferencesSchema,
).setGlobalObjectIdOptions({ tostring: false });
beforeAll(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [
await MongooseModule.forRootAsync({
useFactory: async () => {
const mongod = await MongoMemoryServer.create;
const uri = mongod.getUri();
return {
uri: uri,
};
},
}),
UserPreferencesModule,
],
}).compile();
app = moduleFixture.createNestApplication();
console.log('app: ', app);
userPreferencesModel = moduleFixture.get<Model<UserPreferencesDocument>>(
getModelToken(UserPreferences.name),
);
await app.init();
});
beforeEach(() => {
// populate the DB with 1 UserPreference using fakingoose
const mockUserPreferences = UserPreferencesFactory.generate();
return userPreferencesModel.create(mockUserPreferences);
});
afterEach(() => userPreferencesModel.remove({}));
it('GET /api/v1/user-preferences', () => {
return request(app.getHttpServer())
.get('/api/v1/user-preferences')
.expect(200)
.expect((res) => {
console.log('res: ', res);
expect(res.body.length > 0).toBe(true);
});
});
afterAll(() => {
app.close();
});
});
This is my schema file:
import { Schema, Prop, SchemaFactory } from '#nestjs/mongoose';
import { Document } from 'mongoose';
export enum exitToOptions {
THIS_POST = 'this_post',
ALL_POSTS = 'all_posts',
DASHBOARD = 'dashboard',
}
export type UserPreferencesDocument = UserPreferences & Document;
#Schema()
export class UserPreferences {
#Prop({ unique: true })
eUserId: string;
#Prop()
uiTheme: string;
#Prop()
panelWidth: number;
#Prop()
editingHandles: boolean;
#Prop()
enableLightboxInEditor: boolean;
#Prop()
hiddenElements: boolean;
#Prop()
defaultDeviceView: string;
// #Prop()
// exitTo: exitToOptions
#Prop()
exitTo: string;
}
export const UserPreferencesSchema =
SchemaFactory.createForClass(UserPreferences);

Mongoose getters are either not working the way I want or I'm misunderstanding what they are

I created some sample code to demonstrate my issue on a smaller scale. From my understanding, a getter function will not affect anything on my database, but when I want to make a get request to view items on my database, it will change the value to whatever is returned only when the data is displayed. However, when I make my get request to view items on my database, the item I am shown is exactly how it was saved. I'm not sure if I'm misunderstanding what a getter function is, or if my syntax is just incorrect somewhere.
Here is my main server:
const express = require('express')
const mongoose = require('mongoose')
// Linking my model
const User = require('./User')
// Initializing express
const app = express()
const PORT = 9999
app.use(express.json())
// Connecting to mongodb
const connectDB = async () => {
try {
await mongoose.connect('mongodb://localhost/testdatabase', {
useUnifiedTopology: true,
useNewUrlParser: true
})
console.log('Connected')
} catch (error) {
console.log('Failed to connect')
}
}
connectDB()
// Creates a new user
app.post('/user/create', async (req, res) => {
await User.create({
name: 'John Cena',
password: 'somepassword'
})
return res.json('User created')
})
// Allows me to view all my users
app.get('/user/view', async (req, res) => {
const findUser = await User.find()
return res.json(findUser)
})
// Running my server
app.listen(PORT, () => {
console.log(`Listening on localhost:${PORT}...`)
})
Here is my model:
const mongoose = require('mongoose')
// My setter - initialPassword is 'somepassword'
// This seems to work properly, in my database the password is changed to 'everyone has the same password here'
const autoChangePassword = (initialPassword) => {
console.log(initialPassword)
return 'everyone has the same password here'
}
// My getter - changedPassword should be 'everyone has the same password here' I think
// The console.log doesn't even run
const passwordReveal = (changedPassword) => {
console.log(changedPassword)
return 'fakehash1234'
}
// Creating my model
const UserSchema = mongoose.Schema({
name: {
type: String
},
password: {
type: String,
set: autoChangePassword,
get: passwordReveal
}
})
// Exporting my model
const model = mongoose.model('user', UserSchema)
module.exports = model
Not sure if it would help anyone since I found my answer on another StackOverflow post, but the issue was I had to set getters to true when converting back to JSON:
// Creating my model
const UserSchema = mongoose.Schema({
name: {
type: String
},
password: {
type: String,
set: autoChangePassword,
get: passwordReveal
}
}, {
toJSON: { getters: true }
})
Any similar problems can be solved by adding some combination of the following:
{
toJSON: {
getters: true,
setters: true
},
toObject: {
getters: true,
setters: true
}
}