I'm trying to implement GraphQL+MongoDb into a project and I'm stuck at nesting.
I have to do a meal history for an User. I've managed to create the User type+resolver+query but i'm stuck at getting a meal for that User.
I've got this typeDefs:
import { gql } from 'apollo-server';
export const typeDefs = gql`
type User {
id: Int!
CNP: String
firstName: String!
lastName: String!
email: String!
birthday: String
sex: String
Studii: String
Adresa: String
}
type Aliment {
id: String!
foodName: String!
}
type Foods {
aliment: Aliment!
quantity: Int!
}
type Meal {
id: String!
user: User!
currentmeal: [Foods!]!
date: String!
}
input AlimentInput {
id: String
foodName: String
}
input FoodsInput {
aliment: AlimentInput
quantity: String
}
input UserInput {
id: Int
CNP: String
firstName: String
lastName: String
email: String
birthday: String
sex: String
Studii: String
Adresa: String
}
input MealInput {
id: String
user: UserInput
currentmeal: [FoodsInput]
date: String
}
type Query {
getUsers: [User]
getUserInfo(id: Int): User
getMeals: [Meal]
}
type Mutation {
createMeal(
id: String!
user: UserInput
currentmeal: [FoodsInput]
date: String
): Meal
}
`;
and this resolver:
import { Users, Meals } from '../models/users.js';
export const resolvers = {
Query: {
getUsers: async () => {
console.log('1');
return Users.find({}).then((users) => {
console.log('2');
return users.map((user) => ({
id: user.id,
firstName: user.firstName,
lastName: user.lastName,
email: user.email,
}));
});
},
getUserInfo: async (parent, args) => {
console.log('b');
return Users.findOne({ id: args.id }).then(async (user) => {
return {
id: user.id,
firstName: user.firstName,
lastName: user.lastName,
email: user.email,
};
});
},
getMeals: async () => {
return Meals.find({}).then((meals) => {
return meals.map(async (meal) => {
console.log(meal);
return {
id: meal.id,
user: meal.user,
currentmeal: meal.currentmeal,
date: meal.date,
};
});
});
},
},
Mutation: {
createMeal: async (id, user, currentmeal, date) => {
return await Meals.create({
id: id,
user: user,
currentmeal: currentmeal,
date: date,
});
},
},
};
and these schemas:
import mongoose from 'mongoose';
const { Schema } = mongoose;
const { model } = mongoose;
const userSchema = new Schema({
id: { type: String, required: true, index: { unique: true } },
firstName: { type: String, required: true },
lastName: { type: String, required: true },
userName: { type: String, required: true },
email: { type: String, required: true },
jobTitle: { type: String, required: false },
});
const alimentSchema = new Schema({
id: { type: String, required: true, index: { unique: true } },
foodName: { type: String, required: true },
});
const foodsSchema = new Schema({
aliment: { type: Schema.Types.ObjectId, ref: 'users' },
quantity: { type: String },
});
const mealSchema = new Schema({
id: { type: String, required: true, index: { unique: true } },
user: { type: Schema.Types.ObjectId, ref: 'users' },
currentmeal: [foodsSchema],
date: { type: String },
});
export const Users = model('users', userSchema);
export const Aliments = model('aliments', alimentSchema);
export const Meals = model('meals', mealSchema);
When I use the getMeals query i want to get something like:
Meal{
id: 1
user:{
id: Int!
CNP: String
firstName: String!
lastName: String!
email: String!
birthday: String
sex: String
Studii: String
Adresa: String
}
currentmeal: [
{
aliment: {
id:1
foodName:Chicken breasts
}
quantity: 100
},
{
aliment: {
id:2
foodName:French Fries
}
quantity: 200
}
]
date: notimportant
}
I've been searching for several hours on nesting for GraphQL but found nothing useful.
I've managed to populate the database somehow and this is what i currently have:
import { MongoClient } from 'mongodb';
const populate = async () => {
const connstring ="";
const client = await MongoClient.connect(connstring, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
const db = client.db('test');
let id = 0;
let meals = [];
for (id = 1; id < 90; id++) {
const user = await db.collection('users').findOne({ id: id });
const currentmeal = [];
let aliment1 = await db.collection('aliments').findOne({ id: id });
let aliment2 = await db.collection('aliments').findOne({ id: id });
currentmeal.push({
aliment: aliment1,
quantity: 200 - id,
});
currentmeal.push({
aliment: aliment2,
quantity: 200 - id,
});
const date = JSON.stringify(Date.now());
meals.push({
id,
user,
currentmeal,
date,
});
}
await db.collection('meals').insertMany(meals);
client.close();
};
populate();
this is how Users look:
this is how aliments look:
and this is how meals look:
I think that in the meals collection i should have user's _id instead of the user object, same for aliment in the currentmeal Array
Related
I want to update rooms subdocument which is in Property document, but I get this error:
MongoServerError: Updating the path 'rooms.$.updatedAt' would create a
conflict at 'rooms.$'
Error which i got in postman:
{
"ok": 0,
"code": 40,
"codeName": "ConflictingUpdateOperators",
"$clusterTime": {
"clusterTime": {
"$timestamp": "7137698220989218823"
},
"signature": {
"hash": "34spx6E0zZFa5bVYFSL2JyjFszQ=",
"keyId": {
"low": 2,
"high": 1646669662,
"unsigned": false
}
}
},
"operationTime": {
"$timestamp": "7137698220989218823"
}
}
My property model:
import { IReview, ReviewSchema } from './reviews.model'
import { IRoom, RoomSchema } from './room.model'
type TCheapestRoom = {
roomType: string
bedType: string
lastPrice: number
actualPrice: number
}
export interface IProperty {
propertyType: string
name: string
description: string
city: string
photos: [string]
cheapestRoom: TCheapestRoom
adress: string
distance: number
cancellationPolicy: string
meals: string
funThingsToDo: [string]
accessibility: [string]
rooms: IRoom[]
reviews: IReview[]
}
interface IPropertyDocument extends IProperty, Document {
createdAt: Date
updatedAt: Date
// rooms: Types.Array<IRoom>
}
const PropertySchema = new Schema<IPropertyDocument>(
{
propertyType: { type: String, required: true },
name: { type: String, required: true },
description: { type: String, required: true },
city: { type: String, required: true },
photos: { type: [String] },
cheapestRoom: {
roomType: { type: String },
bedType: { type: String },
lastPrice: { type: Number },
actualPrice: { type: Number },
},
adress: { type: String, required: true },
distance: { type: Number, required: true },
cancellationPolicy: { type: String, required: true },
meals: { type: String, required: true },
funThingsToDo: { type: [String] },
accessibility: { type: [String] },
rooms: [RoomSchema],
reviews: [ReviewSchema],
},
{
timestamps: true,
}
)
const PropertyModel = model<IPropertyDocument>('Property', PropertySchema)
export default PropertyModel
Subdocument room model:
export interface IRoom {
roomType: string
bedTypes: [string]
roomFacilities: [string]
sleeps: number
lastPrice: number
actualPrice: number
cancellation: string
payment: string
breakfast: string
unavailableDates: [Date]
}
export interface IRoomDocument extends IRoom, Document {
createdAt: Date
updatedAt: Date
}
export const RoomSchema = new Schema<IRoomDocument>(
{
roomType: { type: String, required: true },
bedTypes: { type: [String], required: true },
roomFacilities: { type: [String], required: true },
sleeps: { type: Number, required: true, default: 2 },
lastPrice: { type: Number, required: true },
actualPrice: { type: Number, required: true },
cancellation: { type: String, required: true },
payment: { type: String, required: true },
breakfast: { type: String, required: true },
unavailableDates: { type: [Date] },
},
{
timestamps: true,
}
)
const RoomModel = model<IRoomDocument>('Room', RoomSchema)
export default RoomModel
room controller:
import PropertyModel from '../models/property.model'
import RoomModel from '../models/room.model'
const updateRoom = async (req: Request, res: Response) => {
try {
const updatedProperty = await PropertyModel.findOneAndUpdate(
{ _id: req.params.propertyId, 'rooms._id': req.params.roomId },
{
$set: { 'rooms.$': req.body },
},
{
new: true,
}
)
res.status(201).json(updatedProperty)
} catch (error) {
console.log(error)
res.status(400).json(error)
}
}
export { addNewRoom, deleteRoom, updateRoom }
I want to be able updating my subdocuments, in this case rooms using property id and room id.
How can i update this subdocument ?
I want to achieve like this:
On every user there's subject, and on every subject there's students.
user: [{ subjects: {subjectCode, description},[{students: {name, year, phone}}]}]
I already got the users -> subject side.
my problem now is how can i achieve to create students inside of subjects?
This is for my authentication.
User.js
const mongoose = require('mongoose');
const UserSchema = mongoose.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 = mongoose.model('user', UserSchema);
Every user can create subjects.
Subject.js
const mongoose = require('mongoose');
const SubjectSchema = mongoose.Schema({
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'users',
},
student: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'student',
},
],
title: {
type: String,
required: true,
},
description: {
type: String,
},
date: {
type: Date,
default: Date.now,
},
});
module.exports = mongoose.model('subject', SubjectSchema);
In every subjects, there's should array of students.
Student.js
const mongoose = require('mongoose');
const StudentSchema = mongoose.Schema({
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'users',
},
name: {
type: String,
required: true,
},
year: {
type: String,
},
phone: {
type: String,
},
email: {
type: String,
required: true,
},
date: {
type: Date,
default: Date.now,
},
});
module.exports = mongoose.model('student', StudentSchema);
Fragment in graphql query is not working I am trying to pass fragment like below in constant query this query is successfully running on graphql server I don't know where I am mistaking. Is it correct way of passing fragment or not. please check below code and provide me your suggestion to fix this issue and sucessfully run this test case
import React from 'react';
import gql from 'graphql-tag';
import { MockedProvider } from '#apollo/client/testing';
import { render, wait } from '../../../test-utils';
describe('customer list', () => {
it('render with data', async () => {
const query = gql`
fragment Customer on Customer {
id
userId
firstName
lastName
status
dateOfBirth
}
query getCustomers {
customers {
...Customer
}
}
`;
const mocks = [
{
request: {
query
},
result: {
data: {
customers: [
{
id: 'tid1',
userId: 'test1',
firstName: 'test f',
lastName: 'test l',
status: 'testactive',
contactInfo: {
email: 'test#gmail.com',
phone: '797789956',
address: {
unit: '123',
streetAddress: 'test',
city: 'mp',
jurisdictionCode: 'tst',
country: 'xyz',
postalCode: '412356'
}
},
dateOfBirth: '12/12/2014'
},
{
id: 'tid2',
userId: 'test2',
firstName: 'test2 f',
lastName: 'test2 l',
status: 'active',
contactInfo: {
email: 'test#gmail.com',
phone: '797789956',
address: {
unit: '123',
streetAddress: 'test',
city: 'mp',
jurisdictionCode: 'sds',
country: 'yxz',
postalCode: '412356'
}
},
dateOfBirth: '12/12/2014'
},
{
id: 'tid3',
userId: 'test3',
firstName: 'test3 f',
lastName: 'test3 l',
status: 'active',
contactInfo: {
email: 'test1#gmail.com',
phone: '797789956',
address: {
unit: '123',
streetAddress: 'test',
city: 'mp',
jurisdictionCode: 'sds',
country: 'zxy',
postalCode: '412356'
}
},
dateOfBirth: '12/12/1995'
}
]
}
}
}
];
const { asFragment, findByText } = render(
<MockedProvider mocks={mocks} addTypename={false}>
<Customers />
</MockedProvider>
);
const testid1 = await findByText('tid1');
expect(testid1).toBeInTheDocument();
const testfname = await findByText('test f');
expect(testfname).toBeInTheDocument();
const testlname = await findByText('test l');
expect(testlname).toBeInTheDocument();
const status = await findByText('testactive');
expect(status).toBeInTheDocument();
const email = await findByText('test1#gmail.com');
expect(email).toBeInTheDocument();
const DOB = await findByText('12/12/1995');
expect(DOB).toBeInTheDocument();
await wait(() => expect(asFragment()).toMatchSnapshot());
});
});
If anyone can help must be appreciated.
I have no idea how to deal with MongoDB, and I tried and failed.
The problem as following:
I have data gathered from URL. This data is: text, location, skills, type, hasJob, hasItem.
This data used to search for a user in database has matched values with these parameters.
for example, this is an URL: http://localhost:3000/profile/search/?type=exact&skills=Test,Programming&text=salam&location=uk
text=salam
type=exact
skills=[Test, Programming]
location=uk
The required query should do the following: if there is matched name and location and skills, the query should return the user that has these matched data. But, if there is a matched name and location and text without any skills matches, the query also should return the user.
So, the skills may be matched or not, in all cases, the query should return user if there is a matched name and location as text.
This is the search end-point that should does the work:
router.get('/search', (req, res) => {
if (!req.query || !req.query.type || (!req.query.text && !req.query.skills && !req.query.location)) {
return res.status(400).json({status: 'bad', error: 'Missing required fields.'});
}
if (req.query.type !== 'fuzzy' && req.query.type !== 'exact') {
return res.status(400).json({status: 'bad', error: 'Invalid type.'});
}
const user = req.user;
const exact = req.query.type === 'exact';
let result = [];
let searchBuilder = {};
let searchQuery = [];
let locationResult =[];
let textResult = [];
let skillsResuts = [];
let locationData=[];
let textData=[];
let skillsData= [];
let skills =[];
let item = {};
//1- Location
if (req.query.location) {
locationData = [
{'profile.address.city': { $regex: req.query.location }},
{'profile.address.country': { $regex: req.query.location }},
{'profile.address.state': { $regex: req.query.location }},
{'profile.address.zip': { $regex: req.query.location }}
]
searchBuilder['$or'] = locationData;
searchQuery.push({
'$or': locationData
})
}
//2- Text
if (req.query.text) {
textData = [
{ 'phone': req.query.text },
{'profile.fullName': { $regex: req.query.text }},
{'profile.email': { $regex: req.query.text }},
// {'profile.username': { $regex: req.query.text }}
]
searchBuilder['$or'] = textData;
searchQuery.push({
'$or': textData
})
}
//3- Skills
if (req.query.skills) {
let allSkills = req.query.skills.split(',')
skillsData = {
'profile.skills.custom.name': { '$in': allSkills }
}
skills = allSkills.map(function(v, i){return new RegExp(v, 'i')});
searchBuilder['profile.skills.custom.name'] = { '$in': skills }
item = {'profile.skills.custom.name': { '$in': skills }};
searchQuery.push({
'$or': [ skillsData ]
})
}
//4. hasJobs
if (req.query.hasJobs) {
searchBuilder['jobs.0'] = { $exists: true };
searchQuery.push({
'jobs.0': { '$exists': true }
})
}
//5.hasItems
if (req.query.hasItems) {
searchBuilder['items.0'] = { $exists: true };
searchQuery.push({
'items.0': { '$exists': true }
})
}
// Add the query here
db.User.find({}})
.populate('connections.user')
.populate('jobs')
.populate('items')
.populate('profile.skills.custom')
.select('-contacts')
.sort('connections.user.profile.fullName')
.then(users => {
async.each(users, (user, next) => {
if (user.id === req.user.id) {
return next();
}
const check = result.find(usr => {
return usr.id === user.id;
});
if (!check) {
result.push(user);
next();
}
else {
return next();
}
})
})
.then(() => {
res.json(result);
})
.catch(err => {
return res.status(400).json({status: 'bad', error: err});
});
});
and this is the schema
const UserSchema = new Schema({
phone: String,
shortPhone: String,
password: String,
profile: {
email: String,
fullName: String,
username: String,
address: {
city: {type: String, default: 'Unknown'},
state: String,
country: String,
zip: String,
},
photos: String,
summary: String,
resume: String,
resumeFile: String,
experiences: [{
title: String,
company: String,
location: String,
startDate: Date,
endDate: Date,
description: String
}],
educations: [{
school: String,
degree: String,
field: String,
startDate: Date,
endDate: Date,
description: String
}],
skills: {
standard: [{type: Schema.Types.ObjectId, ref: 'Skill'}],
custom: [{name: String}]
},
points: {type: Number, default: 0},
totalPoints: {type: Number, default: 0},
hidden: {type: Boolean, default: false},
public: {type: Boolean, default: true},
countryCode: String,
},
location: {type: [Number], index: '2d'},
items: [{type: Schema.Types.ObjectId, ref: 'Item'}],
blogs: [{type: Schema.Types.ObjectId, ref: 'Blog'}],
news: [{type: Schema.Types.ObjectId, ref: 'News'}],
conversations: [{type: Schema.Types.ObjectId, ref: 'Conversation'}],
viewBy: [{type: Schema.Types.ObjectId, ref: 'User'}],
level: {type: Number, default: 1},
verified: {type: Boolean, default: false},
notificationTokens: [{type: String}],
connections: [{
user: {type: Schema.Types.ObjectId, ref: 'User'},
relationship: {type: String, enum: ['requested', 'received', 'connected']},
isForced: {type: Boolean, default: false}
}],
declinedRequests: [String],
jobs: [{type: Schema.Types.ObjectId, ref: 'Job'}],
favoritedJobs: [{type: Schema.Types.ObjectId, ref: 'Job'}],
sharedJobs: [{type: Schema.Types.ObjectId, ref: 'Job'}],
viewedJobs: [{type: Schema.Types.ObjectId, ref: 'Job'}],
favoritedItems: [{type: Schema.Types.ObjectId, ref: 'Item'}],
sharedItems: [{type: Schema.Types.ObjectId, ref: 'Item'}],
viewedItems: [{type: Schema.Types.ObjectId, ref: 'Item'}],
actions: [{type: Schema.Types.ObjectId, ref: 'Action'}],
incentives: [{type: Schema.Types.ObjectId, ref: 'Incentive'}],
contacts: [String],
synced: {type: Boolean, default: false}
});
//UserSchema.plugin(autoPopulateAllFields);
exports.User = mongoose.model('User', UserSchema);
I have this code:
router.post('/setsuggestions', function(req, res, next){
if(!req.body.username || !req.body.challengessuggestions){
return res.status(400).json({message: challengessuggestions});
}
var query = { username: req.body.username };
/*User.findOneAndUpdate(query, { challengessuggestions: req.body.challengessuggestions }, callback = function(response){
res.json(response);
});*/
/*
User.findOneAndUpdate(
query,
{$push: {"challengessuggestions": {$oid: req.body.challengessuggestions}}},
callback = function(response) {
res.json(response);
}
);*/
User.findOneAndUpdate(
query,
{$push: {challengessuggestions: req.body.challengessuggestions}},
{safe: true, upsert: true},
function(err, model) {
res.json(err);
}
);
});
When I postman like this:
I get the following error:
{ "name": "MongoError", "message": "exception: The field
'challengessuggestions' must be an array but is of type OID in
document {_id: ObjectId('56263b910d1a2f1f0077ffae')}", "errmsg":
"exception: The field 'challengessuggestions' must be an array but is
of type OID in document {_id: ObjectId('56263b910d1a2f1f0077ffae')}",
"code": 16837, "ok": 0 }
This is the schema definition of AppUser:
var UserSchema = new mongoose.Schema({
username: { type: String, lowercase: true, unique: true },
firstname: { type: String},
lastname: { type: String},
difficulty: { type: String},
isstudent: { type: Boolean },
haschildren: { type: Boolean},
gender: { type: String },
email: { type: String, unique: true},
birthdate: String,
isdoingchallenges: { type: Boolean },
challengescompleted: [{ type: ObjectId, ref: 'Challenge' }],
currentchallenge: { type: ObjectId, ref: 'Challenge' },
challengessuggestions: [{ type: ObjectId, ref: 'Challenge' }],
hash: String,
salt: String
});
This is the schema definiton of challenge:
var Challengeschema = new mongoose.Schema({
name: { type: String, initial: true, required: true, index: true },
image: { type: Array },
difficulty: { type: String },
studentfriendly: { type: Boolean },
childfriendly: { type: Boolean },
description: { type: String }
});
I'm sending this in the function that calls the api:
Object {_id: "5631423f8c5ba50300f2b4f6", difficulty: "medium", name:
"Probeer 1 van onze recepten.", __v: 0, childfriendly: true…}
This gives me following error:
D:\Stijn\Documenten\EVA-project-Groep-6\Api\node_modules\mongoose\lib\schema\obj
ectid.js:134
throw new CastError('ObjectId', value, this.path);
^ Error
at MongooseError.CastError (D:\Stijn\Documenten\EVA-project-Groep-6\Api\node
_modules\mongoose\lib\error\cast.js:18:16)
at ObjectId.cast (D:\Stijn\Documenten\EVA-project-Groep-6\Api\node_modules\m
ongoose\lib\schema\objectid.js:134:13)
at Array.MongooseArray.mixin._cast (D:\Stijn\Documenten\EVA-project-Groep-6\
Api\node_modules\mongoose\lib\types\array.js:124:32)
at Array.MongooseArray.mixin._mapCast (D:\Stijn\Documenten\EVA-project-Groep
-6\Api\node_modules\mongoose\lib\types\array.js:295:17)
at Object.map (native)
at Array.MongooseArray.mixin.push (D:\Stijn\Documenten\EVA-project-Groep-6\A
pi\node_modules\mongoose\lib\types\array.js:308:25)
at Query. (D:\Stijn\Documenten\EVA-project-Groep-6\Api\routes\ind ex.js:144:44)
at D:\Stijn\Documenten\EVA-project-Groep-6\Api\node_modules\mongoose\node_mo
dules\kareem\index.js:177:19
at D:\Stijn\Documenten\EVA-project-Groep-6\Api\node_modules\mongoose\node_mo
dules\kareem\index.js:109:16
at doNTCallback0 (node.js:408:9)
at process._tickCallback (node.js:337:13) 29 Oct 22:05:38 - [nodemon] app crashed - waiting for file changes before starti ng...
How do I solve this?
Query the User user using findOne() first and use the first found document that's passed to the callback to save the embedded documents with:
router.post('/setsuggestions', function(req, res, next){
if(!req.body.username || !req.body.challengessuggestions){
return res.status(400).json({message: challengessuggestions});
}
var query = { username: req.body.username };
User.findOne(query, function (err, user){
if (err) //throw ...
if (user) {
if (user.challengessuggestions && user.challengessuggestions.length) {
user.challengessuggestions.push(req.body.challengessuggestions);
}
else {
user.challengessuggestions = [req.body.challengessuggestions];
}
// save changes
user.save(function (err) {
if (!err) {
// done ...
}
});
}
});
);