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.
Related
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",
});
}
}
}
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?
Iam using the row level security in supabase with nest.js, So how can I set runtime variables safely to the DB so that I can be sure that the variables sync with each app user (due to the http request triggered the execution)?
I saw that it is possible to set local variables in a transaction but I wouldn't like to wrap all the queries with transactions.
Thanks & Regards
I tried to execute this with subscribers in nestjs it working fine . but it wont have a function like beforeSelect or beforeLoad , so i drop it
import { Inject, Injectable, Scope } from '#nestjs/common';
import { InjectDataSource } from '#nestjs/typeorm';
import { ContextService } from 'src/context/context.service';
import { DataSource, EntityManager, LoadEvent, RecoverEvent, TransactionRollbackEvent, TransactionStartEvent } from 'typeorm';
import {
EventSubscriber,
EntitySubscriberInterface,
InsertEvent,
UpdateEvent,
RemoveEvent,
} from 'typeorm';
#Injectable()
#EventSubscriber()
export class CurrentUserSubscriber implements EntitySubscriberInterface {
constructor(
#InjectDataSource() dataSource: DataSource,
private context: ContextService,
) {
dataSource.subscribers.push(this);
}
async setUserId(mng: EntityManager, userId: string) {
await mng.query(
`SELECT set_config('request.jwt.claim.sub', '${userId}', true);`,
);
}
async beforeInsert(event: InsertEvent<any>) {
try {
const userId = this.context.getRequest();
await this.setUserId(event.manager, userId);
} catch (err) {
console.log(err);
}
}
async beforeTransactionRollback(event: TransactionRollbackEvent) {
console.log('hello')
try {
const userId = this.context.getRequest();
await this.setUserId(event.manager, userId);
} catch (err) {
console.log(err);
}
}
async beforeUpdate(event: UpdateEvent<any>) {
try {
const userId = this.context.getRequest();
await this.setUserId(event.manager, userId);
} catch (err) {
console.log(err);
}
}
async beforeRemove(event: RemoveEvent<any>) {
try {
const userId = this.context.getRequest();
await this.setUserId(event.manager, userId);
} catch (err) {
console.log(err);
}
}
}
After i get to know that we can use query runner instead of subscriber . but its not working ,
also i need a common method to use all the queries
import { Injectable } from '#nestjs/common';
import { InjectRepository } from '#nestjs/typeorm';
import { Users } from 'src/common/entities';
import { DataSource, EntityManager, Repository } from 'typeorm';
#Injectable()
export class UsersService {
constructor(
#InjectRepository(Users) private userRepository: Repository<Users>,
private dataSource: DataSource,
private em: EntityManager,
) {}
getAllUsers(userId: string) {
const queryRunner = this.dataSource.createQueryRunner();
return new Promise(async (resolve, reject) => {
let res: any;
try {
await queryRunner.connect();
await queryRunner.manager.query(
// like this we can set the variable
`SELECT set_config('request.jwt.claim.sub', '${userId}', true);`,
);
// after setting config variable the query should return only one user by userId
res = await queryRunner.query('SELECT * FROM users');
// but it reurns every user
} catch (err) {
reject(err);
} finally {
await queryRunner.manager.query(`RESET request.jwt.claim.sub`);
await queryRunner.release();
resolve(res);
}
});
}
}
Thanks in advance....
Sorry to say, bro. But in currently state of development TypeORM does not have a feature that let us set conection variables. The roundabout for your problem is to do something like this.
/**
* Note: Set current_tenant session var and executes a query on repository.
* Usage:
* const itens = = await tenantTransactionWrapper( manager => {
* return manager.getRepository(Entity).find();
* });
*
* #param {function} callback - a function thar receives an Entity Manager and returns a method to be executed by tenantTransactionWrapper
* #param {string} providedTenantId - optional tenantId, otherwise tenant will be taken from localStorage
*/
async function tenantWrapper<R>(
callback: (manager: EntityManager) => Promise<R>,
providedTenantId?: string,
) {
const tenantId = providedTenantId || tenantStorage.get();
let response: R;
await AppDataSource.transaction(async manager => {
await manager.query(`SET LOCAL smsystem.current_tenant='${tenantId}';`);
response = await callback(manager);
});
return response;
}
Then create a custom repository to make use of the wraper a little bit simple.
const customRepository = <T>(entity: EntityTarget<T>) => ({
find: (options?: FindManyOptions<T>) =>
tenantTransactionWrapper(mng => mng.getRepository(entity).find(options))(),
findAndCount: (options?: FindManyOptions<T>) =>
tenantTransactionWrapper(mng =>
mng.getRepository(entity).findAndCount(options),
)(),
save: (entities: DeepPartial<T>[], options?: SaveOptions) =>
tenantTransactionWrapper(mng =>
mng.getRepository(entity).save(entities, options),
)(),
findOne: (options: FindOneOptions<T>) =>
tenantTransactionWrapper(async mng =>
mng.getRepository(entity).findOne(options),
)(),
remove: (entities: T[], options?: RemoveOptions) =>
tenantTransactionWrapper(mng =>
mng.getRepository(entity).remove(entities, options),
)(),
createQueryBuilder: () => {
throw new Error(
'Cannot create queryBuilder for that repository type, instead use: tenantWrapper',
);
},
tenantTransactionWrapper,
});
And finally use our customRepository :
class PersonsRepository implements IPersonsRepository {
private ormRepository: Repository<Person>;
constructor() {
this.ormRepository = AppDataSource.getRepository<Person>(Person).extend(
customRepository(Person),
);
}
public async create(data: ICreatePersonDTO): Promise<Person> {
const newPerson = this.ormRepository.create(data);
await this.ormRepository.save(newPerson);
return newPerson;
}
public async getAll(relations: string[] = []): Promise<Person[]> {
return this.ormRepository.find({ relations });
}
I hope this may help someone and will be very glad if someone provides a better solution.
First you have to create a custom class for wrapping your userId or any stuff
custome_service.ts ==>
#Injectable()
export class UserIdWrapper {
constructor(private dataSource: DataSource) {}
userIdWrapper = (callback: (mng: QueryRunner) => Promise<any>, userId: string) => {
const queryRunner = this.dataSource.createQueryRunner();
return new Promise(async (resolve, reject) => {
let res: any;
try {
await queryRunner.connect();
await queryRunner.manager.query(
`SELECT set_config('your_variable_name', '${userId}', false)`,
);
//here is your funciton your calling in the service
res = await callback(queryRunner);
} catch (err) {
reject(err);
} finally {
await queryRunner.manager.query(`RESET your_variable_name`);
await queryRunner.release();
resolve(res);
}
});
};
}
Now here you have to call the function inside user service
user.service.ts ==>
import { Injectable } from '#nestjs/common';
import { InjectRepository } from '#nestjs/typeorm';
import { Users } from 'src/common/entities';
import { UserIdWrapper } from 'src/common/local-settup/userId_wrapper';
import { DataSource, EntityManager, QueryRunner, Repository } from 'typeorm';
#Injectable()
export class UsersService {
constructor(
#InjectRepository(Users) private userRepository: Repository<Users>,
private dataSource: DataSource,
private userIdWrapper: UserIdWrapper
) {}
async getAllUsers(userId: string) {
//This is your call back funciton that have to pass
const findOne = async (queryRunner: QueryRunner) => {
const res = await queryRunner.query('SELECT * FROM public.users');
return res;
};
try {
//hear we are passing the function in to the class funciton
return this.userIdWrapper.userIdWrapper(findOne, userId);
} catch (err) {
console.log(err);
}
}
}
Dont forgot to provide the custom class service inside the provider of user service.
i want to make command that can give me information about someone that i mention like !info #Someone i try code below, but didnt work.
This is the schema
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const profileSchema = new Schema({
_id: mongoose.Schema.Types.ObjectId,
userID: String,
nickname: String,
ar: Number,
server: String,
uid: Number,
});
module.exports = mongoose.model("User", profileSchema);
and this is what i try, but show nothing, didnt show any error sign.
client.on("message", async msg => {
let member = msg.mentions.users.first().username
if (msg.content === `!info #${member}`){
userData = await User.findOne({userID : msg.mentions.users.first().id});
if (userData) {
const exampleEmbed = new MessageEmbed()
.setColor('#808080')
.setTitle('Data Member')
.setDescription(`**Nickname :** ${userData.nickname}\n**Adventure Rank :** ${userData.ar}\nServer: ${userData.server}\n**User ID :** ${userData.uid}`)
.setThumbnail(msg.author.avatarURL())
msg.reply({ embeds: [exampleEmbed] });
} else{
msg.reply("Please registration first")
}
}
}
);
By seeing your code, it might shuffle all of your .first() lets modify your code.
client.on("message", async msg => {
let member = msg.mentions.members.first() || msg.guild.members.fetch(args[0]); //You can also use their ID by using these
if (msg.content === `!info ${member.username || member.user.username}`) { //then adding the user.username
const userData = await User.findOne({
userID: member.id || member.user.id //same as here
}); //userData shows as "any" so you need to change it to const userData
if (userData) {
const exampleEmbed = new MessageEmbed()
.setColor('#808080')
.setTitle('Data Member')
.setDescription(`**Nickname :** ${userData.nickname}\n**Adventure Rank :** ${userData.ar}\nServer: ${userData.server}\n**User ID :** ${userData.uid}`)
.setThumbnail(msg.author.avatarURL())
msg.reply({
embeds: [exampleEmbed]
});
} else {
msg.reply("Please registration first")
}
}
});
Change the if condition. How Discord Mentions Work
Discord uses a special syntax to embed mentions in a message. For user mentions, it is the user's ID with <# at the start and > at the end, like this: <#86890631690977280>.
if (msg.content === `!info ${message.mentions.users.first()}`)
For example:
const member = msg.mentions.users.first();
if (msg.content === `!info ${member}`){
User.findOne({ userID: member.id }, (err, user) => {
if (err) return console.error(err);
if (!user) return msg.reply("User not found");
console.log(user);
});
}
Going through your code, I found these errors.
first of all you need members not users in message.mentions.members.first().
Second of all, you need to define UserData first like const UserData = ...
client.on("message", async msg => {
let member = msg.mentions.members.first()
if (msg.content === `!info #${member}`){
User.findOne({userID : member.id}, async (err, userData) => {
if (userData) {
const exampleEmbed = new MessageEmbed()
.setColor('#808080')
.setTitle('Data Member')
.setDescription(`**Nickname :** ${userData.nickname}\n**Adventure Rank :** ${userData.ar}\nServer: ${userData.server}\n**User ID :** ${userData.uid}`)
.setThumbnail(msg.author.avatarURL())
msg.reply({ embeds: [exampleEmbed] });
} else{
msg.reply("Please registration first")
}
}
});
});
Let me know if it works after fixing these errors.
Also message event is depricated so try using MessageCreate instead from now on
I'm new to this JS Word, I read the Adonis and Knex doc, but I really don't understand what I'm doing wrong here.
Adonis: https://preview.adonisjs.com/guides/database/transactions and https://adonisjs.com/docs/4.1/lucid#_transactions
knex: http://knexjs.org/#Transactions
In the case when nothing goes wrong all the data are stored with no problem, but when I do a error on propose just to test the transaction I can notice that the "empresas" table had a record and the other tables are empty, I supose that when occurs an error all the tables need to be empty, by the transaction's rollback() function. Can someone enlighten me here?
Using: Adonis with postgres
this are my migrations:
table empresas:
'use strict'
/** #type {import('#adonisjs/lucid/src/Schema')} */
const Schema = use('Schema')
class EmpresaSchema extends Schema {
up () {
this.create('empresas', (table) => {
table.increments()
table.text('codigo').notNullable()
table.text('tipo').notNullable()
table.text('origem').notNullable()
table.bigInteger('grupo_id').notNullable()
table.text('nome_fantasia')
table.text('razao_social')
table.timestamps()
})
}
down () {
this.drop('empresas')
}
}
module.exports = EmpresaSchema
table contatos:
'use strict'
/** #type {import('#adonisjs/lucid/src/Schema')} */
const Schema = use('Schema')
class ContatosSchema extends Schema {
up () {
this.create('contatos', (table) => {
table.increments()
table
.integer('tipo_id')
.unsigned()
.notNullable()
.references('id')
.inTable('contato_tipos')
table.text('nome').notNullable()
table.text('dado').notNullable()
table.timestamps()
})
}
down () {
this.drop('contatos')
}
}
module.exports = ContatosSchema
table empresa_contatos:
'use strict'
/** #type {import('#adonisjs/lucid/src/Schema')} */
const Schema = use('Schema')
class EmpresaContatosSchema extends Schema {
up () {
this.create('empresa_contatos', (table) => {
table.increments()
table
.integer('empresa_id')
.unsigned()
.references('id')
.inTable('empresas')
.onUpdate('CASCADE')
.onDelete('CASCADE')
.index()
table
.integer('contato_id')
.unsigned()
.references('id')
.inTable('contatos')
.onUpdate('CASCADE')
.onDelete('CASCADE')
.index()
})
}
down () {
this.drop('empresa_contatos')
}
}
module.exports = EmpresaContatosSchema
My models:
Empresa model:
'use strict'
/** #type {typeof import('#adonisjs/lucid/src/Lucid/Model')} */
const Model = use('Model')
class Empresa extends Model {
contatos () {
return this.belongsToMany('App/Models/Contato').pivotTable('empresa_contatos')
}
}
module.exports = Empresa
Contato model:
'use strict'
/** #type {typeof import('#adonisjs/lucid/src/Lucid/Model')} */
const Model = use('Model')
class Contato extends Model {
static get table () {
return 'contatos'
}
empresa () {
return this.belongsToMany('App/Models/Empresa').pivotTable('empresa_contatos')
}
}
module.exports = Contato
And this is a part of my EmpresaController:
'use strict'
const Database = use('Database')
const Empresa = use('App/Models/Empresa')
const Contato = use('App/Models/Contato')
const Sequence = use('App/Controllers/Http/SequenceController')
class EmpresaController {
async customCreate ({ request, response, auth }) {
const trx = await Database.beginTransaction()
try {
const { enderecos, contatos, ...data } = request.all()
if (data.endereco.logradouro !== '') {
enderecos.push(data.endereco)
}
if (data.contato.nome !== '') {
contatos.push(data.contato)
}
const codigo = await Sequence.gerarNovoCodigoCliente(auth.user.grupo_id, trx)
let empresa = null
await Empresa.create({
codigo: codigo,
grupo_id: 1,
tipo: data.tipo,
origem: data.origem,
nome_fantasia: data.nome_fantasia,
razao_social: data.razao_social
}, trx)
.then(response => {
empresa = response
})
.catch(error => {
console.log('error empresa:')
console.log(error)
})
contatos.forEach(async contato => {
let novosContato = null
await Contato.create({
nome: contato.nome,
dado: contato.dado,
tipo_id: contato.tipo_contato.id
}, trx)
.then(response => {
novosContato = response
console.log('contato ok')
})
.catch(error => {
console.log('error contato:')
console.log(error)
})
await empresa.contatos().attach(novosContato.id, null, trx)
.then(response => {
console.log('attach ok')
})
.catch(error => {
console.log('error attach:')
console.log(error)
})
})
await trx.commit()
return response.ok(empresa)
} catch (error) {
await trx.rollback()
return response.badRequest(error.message)
}
}
}
module.exports = EmpresaController
I already tried put each async function on a const and resolve all with promise.all approach, and get the same problem, I really don't know but I guess that "const empresa = await Empresa.create(.......)" is commiting the transaction and running the everything else after it.
(edit)log:
error contato:
Error: Transaction query already complete, run with DEBUG=knex:tx for more info
at completedError (/app/node_modules/knex/lib/transaction.js:261:9)
at /app/node_modules/knex/lib/transaction.js:231:22
From previous event:
at Client_PG.trxClient.query (/app/node_modules/knex/lib/transaction.js:229:33)
at Runner.<anonymous> (/app/node_modules/knex/lib/runner.js:138:36)
From previous event:
at /app/node_modules/knex/lib/runner.js:47:21
at processImmediate (internal/timers.js:461:21)
From previous event:
at Runner.run (/app/node_modules/knex/lib/runner.js:33:30)
at Builder.Target.then (/app/node_modules/knex/lib/interface.js:23:43)
warning:
WARNING: Adonis has detected an unhandled promise rejection, which may
cause undesired behavior in production.
To stop this warning, use catch() on promises or wrap await
calls inside try/catch.
TypeError: Cannot read property 'id' of null
at /app/app/Controllers/Http/EmpresaController.js:165:54
You need to pass transaction reference to create also...
Just add trx here:
const empresa = await Empresa.create({
codigo: codigo,
grupo_id: 1,
tipo: data.tipo,
origem: data.origem,
nome_fantasia: data.nome_fantasia,
razao_social: data.razao_social
}, trx) <-- adding trx to create!
More info here: https://adonisjs.com/docs/4.1/lucid#_transactions