How should i connect Schema models in Mongoose? - express

i want to make a User, Post and comment. connect them together and when i create a Post, it should be connected to one of my users. I don't know why i get an unusual error. Error:
ID cannot represent value: <Buffer 5e 9b f1 3e e9 49 61 38 fc 1a 6f 59>
these are all of my code so if you know whats my problem please help me fix it. Thanks
typeDefs:
import { gql } from 'apollo-server-express';
export const typeDefs = gql`
type User {
id: ID!
name: String!
email: String!
age: Int
posts: [Post!]!
comments: [Comment!]!
}
type Post {
id: ID!
title: String!
body: String!
published: Boolean!
author: User!
comments: [Comment!]!
}
type Comment {
id: ID!
text: String!
author: User!
post: Post!
}
`
UserSchema:
import mongoose, { mongo } from 'mongoose';
const userSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
email: {
type: String,
required: true
},
age: {
type: Number,
required: false
},
posts: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Post'
}
],
comments: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Comment'
}
]
});
module.exports = mongoose.model('User',userSchema);
PostSchema:
import mongoose from 'mongoose';
const postSchema = new mongoose.Schema({
title: {
type: String,
required: true
},
body: {
type: String,
required: true
},
published: {
type: Boolean,
required: true
},
author: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
comments: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Comment'
}
]
});
module.exports = mongoose.model('Post',postSchema);
CommentSchema:
import mongoose from 'mongoose';
const commentSchema = new mongoose.Schema({
text: {
type: String,
required: true
},
author: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
post: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Post'
}
});
module.exports = mongoose.model('Comment',commentSchema);
Resolver:
import Users from './models/User';
import Posts from './models/Post';
import Comments from './models/Comment';
export const resolvers = {
Mutation: {
createUser: async (parent, args, context, info) => {
const user = new Users(args);
await user.save();
return user;
},
createPost: async (parent, { title, body, published, author }, context, info) => {
const user = await Users.findById(author);
if (!user) {
console.log("User not found")
}
console.log(user)
const post = new Posts({ title, body, published, author: user.id });
await post.save();
user.posts.push(post);
await user.save();
return post;
}
}
}

I have found the solution and you should use type: mongoose.Schema.Types.ObjectId and ref: 'Comment' and after that inside resolvers you should use population .

Related

ExpressJs - Mongoose: Delete documents with Many To Many relationship

I've two Models, Post and Tag with Many To Many relationships.
Post Schema:
const postSchema = new Schema(
{
user: {
type: Schema.Types.ObjectId,
ref: 'User',
required: [true, 'A post must belong to a user.'],
},
title: {
type: String,
unique: [true, 'A Post already exists with this title.'],
required: [true, 'A Post must have a title.'],
},
slug: { type: String, unique: true },
body: { type: String, required: [true, 'A Post must have a body.'] },
coverImage: String,
images: Array,
isDraft: { type: Boolean, default: false },
isPublished: { type: Boolean, default: false },
tags: [{ type: Schema.Types.ObjectId, ref: 'Tag' }],
},
{
timestamps: { currentTime: () => Math.floor(Date.now() / 1000) },
toJSON: { virtuals: true },
toObject: { virtuals: true },
}
)
Tag Schema:
const tagSchema = new Schema(
{
title: { type: String, required: true },
slug: { type: String },
posts: [{ type: Schema.Types.ObjectId, ref: 'Post' }],
},
{
timestamps: { currentTime: () => Math.floor(Date.now() / 1000) },
toJSON: { virtuals: true },
toObject: { virtuals: true },
}
)
Now I want to remove all the references of the Posts from the Tag documents when a Post is deleted.
I'm trying to the following remove middleware in the Post model but it is not working. The post get deleted but the reference still there on the Tag documents.
postSchema.pre('remove', function (next) {
var post = this
post
.model('Tag')
.update(
{ posts: { $in: post.tags } },
{ $pull: { posts: post._id } },
{ multi: true },
next
)
})
After trying many times I finally fired out what wrong I was doing. Following the fix I made to make it work:
In Post Controller I was previously doing this:
const post = await Post.findByIdAndDelete(req.params.id)
Which I changed to:
const post = await Post.findById(req.params.id)
await post.remove()
And in Post Model:
postSchema.pre('remove', async function (next) {
await this.model('Tag').updateMany(
{ posts: this._id },
{ $pull: { posts: this._id } },
{ multi: true },
next
)
})

Can't be blank - Password - Loopback 4 - JWT authentication

When I want to signup on the loopback API Explorer with a JWT authentication and this json format:
{
"id": "string",
"nom": "string",
"prenom": "string",
"email": "string",
"sexe": true,
"dateNaissance": "string",
"password": "strifsvng"
}
I had that error message :
{
"error": {
"statusCode": 422,
"name": "ValidationError",
"message": "L'instance `User` n'est pas valide. Détails : `password` can't be blank (value: undefined).",
"details": {
"context": "User",
"codes": {
"password": [
"presence"
]
},
"messages": {
"password": [
"can't be blank"
]
}
}
}
}
Here the link of the documentation's loopback I've used.
You can see here the user modal :
import {Entity, model, property} from '#loopback/repository';
#model()
export class User extends Entity {
#property({
type: 'number',
id: true,
generated: true,
})
id?: number;
#property({
type: 'string',
required: true,
})
nom: string;
#property({
type: 'string',
required: true,
})
prenom: string;
#property({
type: 'string',
required: true,
})
dateNaissance: string;
#property({
type: 'string',
required: true,
})
sexe: string;
#property({
type: 'string',
required: true,
})
email: string;
#property({
type: 'string',
required: true,
})
password: string;
constructor(data?: Partial<User>) {
super(data);
}
}
export interface UserRelations {
// describe navigational properties here
}
export type UserWithRelations = User & UserRelations;
and the user controller :
// import {inject} from '#loopback/core';
import {inject} from '#loopback/core';
import {
Credentials,
MyUserService,
TokenServiceBindings,
User,
UserRepository,
UserServiceBindings,
} from '#loopback/authentication-jwt';
import {authenticate, TokenService} from '#loopback/authentication';
import {model, property, repository} from '#loopback/repository';
import {get, getModelSchemaRef, post, requestBody} from '#loopback/rest';
import {SecurityBindings, securityId, UserProfile} from '#loopback/security';
import {genSalt, hash} from 'bcryptjs';
import _ from 'lodash';
#model()
export class NewUserRequest extends User {
#property({
type: 'string',
required: true,
})
password: string;
}
const CredentialsSchema = {
type: 'object',
required: ['email', 'password'],
properties: {
email: {
type: 'string',
format: 'email',
},
password: {
type: 'string',
minLength: 8,
},
},
};
export const CredentialsRequestBody = {
description: 'The input of login function',
required: true,
content: {
'application/json': {schema: CredentialsSchema},
},
};
export class UserController {
constructor(
#inject(TokenServiceBindings.TOKEN_SERVICE)
public jwtService: TokenService,
#inject(UserServiceBindings.USER_SERVICE)
public userService: MyUserService,
#inject(SecurityBindings.USER, {optional: true})
public user: UserProfile,
#repository(UserRepository) protected userRepository: UserRepository,
) {}
#post('/users/login', {
responses: {
'200': {
description: 'Token',
content: {
'application/json': {
schema: {
type: 'object',
properties: {
token: {
type: 'string',
},
},
},
},
},
},
},
})
async login(
#requestBody(CredentialsRequestBody) credentials: Credentials,
): Promise<{token: string}> {
// ensure the user exists, and the password is correct
const user = await this.userService.verifyCredentials(credentials);
// convert a User object into a UserProfile object (reduced set of properties)
const userProfile = this.userService.convertToUserProfile(user);
// create a JSON Web Token based on the user profile
const token = await this.jwtService.generateToken(userProfile);
return {token};
}
#authenticate('jwt')
#get('/whoAmI', {
responses: {
'200': {
description: '',
schema: {
type: 'string',
},
},
},
})
async whoAmI(
#inject(SecurityBindings.USER)
currentUserProfile: UserProfile,
): Promise<string> {
return currentUserProfile[securityId];
}
#post('/signup', {
responses: {
'200': {
description: 'User',
content: {
'application/json': {
schema: {
'x-ts-type': User,
},
},
},
},
},
})
async signUp(
#requestBody({
content: {
'application/json': {
schema: getModelSchemaRef(NewUserRequest, {
title: 'NewUser',
}),
},
},
})
newUserRequest: NewUserRequest,
): Promise<User> {
const password = await hash(newUserRequest.password, await genSalt());
const savedUser = await this.userRepository.create(
_.omit(newUserRequest, 'password'),
);
await this.userRepository.userCredentials(savedUser.id).create({password});
return savedUser;
}
}
I don't know why there are that error as I write something in the password.
Thank you in advance :)

Password to be hashed during PATCH request

During patch request, along with other changes (e.g. "name" and "email" if required) password changed and then to be hashed.
I have the following code so far patch route:
router.patch("/edit/:_id", (req, res, next) => {
User.findOneAndUpdate({_id : req.params._id},
{$set:
{
email: req.body.email,
name: req.body.name,
password: req.body.password
},
},
{
new : true,
upsert: true,
omitUndefined: true
}
)
.then(user => res.json(user))
.catch(err => console.log(err));
});
Model/Schema
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
var ObjectId = mongoose.Schema.Types.ObjectId;
// Create Schema
const UserSchema = new Schema({
name: {
type: String,
required: true
},
email: {
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true
},
date: {
type: Date,
default: Date.now
}
});
module.exports = User = mongoose.model("users", UserSchema);
I want to use "bcryptjs" to hash user's password. Please en-light me. Thanks in advance.
All I did in order to solve, add findOneandUpdate pre hooks on schema.
Here is code:
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
var ObjectId = mongoose.Schema.Types.ObjectId;
const bcrypt = require("bcryptjs");
// Create Schema
const UserSchema = new Schema({
name: {
type: String,
required: true
},
email: {
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true
},
date: {
type: Date,
default: Date.now
}
});
UserSchema.pre('findOneAndUpdate', async function (next) {
await this.updateOne({},{ $set: { password: bcrypt.hashSync(this.getUpdate().$set.password, 10) }})
});
module.exports = User = mongoose.model("users", UserSchema);

Transpiled GraphQL with Babel is throwing error "Cannot call class as function"

I am trying to get running GraphQL server. I have simple schema in GraphQL
import {
GraphQLObjectType,
GraphQLInt,
GraphQLString,
GraphQLList,
GraphQLSchema
} from 'graphql'
import db from './models'
const user = new GraphQLObjectType({
name: "user",
description: 'This represents a user',
fields: () => {
return {
id: {
type: GraphQLInt,
resolve(user) {
return user.id
}
},
firstName: {
type: GraphQLString,
resole(user) {
return user.firstName
}
},
lastName: {
type: GraphQLString,
resole(user) {
return user.lastName
}
},
email: {
type: GraphQLString,
resole(user) {
return user.email
}
},
createdAt: {
type: GraphQLString,
resole(user) {
return user.createdAt
}
},
updatedAt: {
type: GraphQLString,
resole(user) => {
return user.updatedAt
}
}
}
}
})
const Query = new GraphQLObjectType({
name: 'Query',
description: 'This is root Query',
fields: () => {
return {
users: {
type: GraphQLList(user),
args: {
id: {
type: GraphQLInt
},
email: {
type: GraphQLString
}
},
resolve(root, args) {
return db.user.findAll({where: args})
}
}
}
}
})
const Schema = new GraphQLSchema({
query: Query
})
export default Schema
I am transpile it with babel into ES5, but every time when I try run it with express
import GraphHTTP from 'express-graphql'
import Schema from './schema'
app.use('/grapql', GraphHTTP({
schema: Schema,
pretty: true,
graphiql: true
}))
I am getting this error
\node_modules\graphql\type\definition.js:41
function _classCallCheck(instance, Constructor) { if (!instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }                                                             
TypeError: Cannot call a class as a function
I check it again and again if i have some typing error but i didnt find enything.
instead of type: GraphQLList(user) use type: new GraphQLList(user)
GraphQLList is a class and you have to create it's instance and use, but you have called it as a function.
const Query = new GraphQLObjectType({
name: 'Query',
description: 'This is root Query',
fields: () => {
return {
users: {
type: new GraphQLList(user),
args: {
id: {
type: GraphQLInt
},
email: {
type: GraphQLString
}
},
resolve(root, args) {
return db.user.findAll({where: args})
}
}
}
}
})

Mocha Testing a Nested Model

Trying to write a test for a nested model but can't get it working:
Model:
const EmployeeSchema = new mongoose.Schema({
firstName: {type: String, required: true},
lastName: { type: String, required: true}
});
const CompanySchema = new mongoose.Schema({
name: { type: String, required: true },
streetAddress: { type: String, required: true },
country: { type: String, required: true },
employees:[EmployeeSchema]
}, { timestamps: true});
Controller:
function create(req, res, next) {
const company = new Company({
name: req.body.name,
streetAddress: req.body.streetAddress,
country: req.body.country
});
company.employees.push(req.employees);
company.save()
.then(savedCompany => res.json(savedCompany))
.catch(e => next(e));
}
Test:
describe('## Company APIs', () => {
let company = {
name: "Test Company",
streetAddress: "123 Fake Street",
country: "A Country"
};
company.employees.push({firstName: "Jane", lastName: "Doe"});
describe('# POST /api/company', () => {
it('should create a new company', (done) => {
request(app)
.post('/api/company')
.send(company)
.expect(httpStatus.OK)
.then((res) => {
expect(res.body.name).to.equal(company.name);
expect(res.body.streetAddress).to.equal(company.streetAddress);
expect(res.body.country).to.equal(company.country);
company = res.body;
done();
})
.catch(done);
});
});
The above gives: TypeError: Cannot read property 'push' of undefined
I've tried a few other things but this is the most promising result, for some reason I just can't seem to populate the embedded model as part of setting up the unit test.
I ended up resolving this, hopefully this helps someone in the future.
Test:
it('should associate an employee with the company', (done) => {
var employee = new Employee();
company.employees.push(employee);
request(app)
.put(`/api/company/${company._id}`)
.send(company)
.expect(httpStatus.OK)
.then((res) => {
expect(res.body.employees).to.be.an('array')
expect(res.body.employees).to.contain(employee.id)
done();
})
.catch(done);
});
Controller:
Adding this to handle multiple additions:
if (req.body.employees != null) {
req.body.employees.forEach(function(employee) {
company.employees.push(employee);
}, this);
}