Why does my GraphQL/Apollo mutation fail? - node.js

This is how the Apollo query is defined:
const createUser = gql`
mutation(
$username: String!,
$email: String!,
$password: String!,
$time_created: String!,
$time_played: Int!,
$verified: Boolean!,
$type_user: Boolean!,
$userLevel: UserLevelInput!,
$ranks: RanksInput!,
$pvp: PvpInput!
){
createUser(
username: $username,
email: $email,
password: $password,
time_created: $time_created,
time_played: $time_played,
verified: $verified,
type_user: $type_user,
userLevel: $userLevel,
ranks: $ranks,
pvp: $pvp
){
username
email
password
}
}
`;
My schema:
const userSchema = new Schema({
username: String,
email: String,
password: String,
time_created: Date,
time_played: Number,
verified: Boolean,
type_user: Boolean,
userLevel: {
lidUnlocked: Number,
gidUnlocked: Number,
},
ranks: {
level: [
{
level: Number,
avgTime: Number,
rank: Number,
group: [
{
group: Number,
time: Number,
rank: Number,
},
],
},
],
},
pvp: {
points: Number,
rank: Number,
},
});
How I'm making the request:
const handleSubmit = (e) => {
e.preventDefault();
addUser({
variables: {
username: input.username,
email: input.email,
password: input.password,
time_created: Date.now(),
time_played: 0,
verified: false,
type_user: false,
userLevel: {
lidUnlocked: 1,
gidUnlocked: 1
},
ranks: {
level: [{
level: 1,
avgTime: 0,
rank: 0,
group: [{
group: 1,
time: 0,
rank: 0
}]
}]
},
pvp: {
points: 0,
rank: 0,
}
}
})
}
UserLevelInput, RanksInput and PvpInput:
const UserLevelInputType = new GraphQLInputObjectType({
name: "UserLevelInput",
fields: () => ({
lidUnlocked: { type: GraphQLInt },
gidUnlocked: { type: GraphQLInt },
}),
});
const RanksInputType = new GraphQLInputObjectType({
name: "RanksInput",
fields: () => ({
level: { type: new GraphQLList(LevelInputType) },
}),
});
const LevelInputType = new GraphQLInputObjectType({
name: "LevelInput",
fields: () => ({
level: { type: GraphQLInt },
avgTime: { type: GraphQLInt },
rank: { type: GraphQLInt },
group: { type: new GraphQLList(GroupInputType) },
}),
});
const GroupInputType = new GraphQLInputObjectType({
name: "GroupInput",
fields: () => ({
group: { type: GraphQLInt },
time: { type: GraphQLInt },
rank: { type: GraphQLInt },
}),
});
const PvpInputType = new GraphQLInputObjectType({
name: "PvpInput",
fields: () => ({
points: { type: GraphQLInt },
rank: { type: GraphQLInt },
}),
});
If i make this mutation on localhost:5005/graphql it works as intended:
mutation{
createUser(
username:"babadany2999",
email:"babadany2999#gmail.com",
password:"Immboold1",
time_created:"1645738406658",
time_played: 0,
verified: false,
type_user: false,
userLevel:{
lidUnlocked: 1,
gidUnlocked: 1
},
ranks: {
level: [{
level: 1,
avgTime: 0,
rank: 0,
group:[{
group: 1,
time: 0,
rank: 0
}]
}]
},
pvp: {
points: 0,
rank: 0
}
), {
username
email
password
}
}
Also if I make the request(with the code not in /graphql) and then check out Apollo Dev tools for that particular mutation, I get that the Int, UserLevelInput, RanksInput and PpvInput types are not known.
Apollo Dev Tools type unknown

For anyone encountering the same problem, I managed to "fix" it by creating constants of the complex objects and simply setting the default to those constants in the mongoose table and not giving that as input to apollo.
username: String,
email: String,
password: String,
time_created: {
type: Date,
default: new Date()
},
time_played: {
type: Number,
default: 0
},
type_user: {
type: Boolean,
default: false
},
verified: {
type: Boolean,
default: false
},
userLevel: {
lidUnlocked: Number,
gidUnlocked: Number
},
ranks: {
type: Object,
default: ranks
},
pvp: {
points: {
type: Number,
default: 0
},
rank: Number
}
})
And part of the constant(it's very long but it has the same structure until the end):
const ranks= {
level: [
{
level: 1,
group: [
{ group: 1, unlocked: true, time: 0, rank: 0 },
{ group: 2, unlocked: false, time: 0, rank: 0 },
{ group: 3, unlocked: false, time: 0, rank: 0 },
{ group: 4, unlocked: false, time: 0, rank: 0 },
{ group: 5, unlocked: false, time: 0, rank: 0 },
{ group: 6, unlocked: false, time: 0, rank: 0 },
{ group: 7, unlocked: false, time: 0, rank: 0 },
{ group: 8, unlocked: false, time: 0, rank: 0 },
{ group: 9, unlocked: false, time: 0, rank: 0 },
{ group: 10, unlocked: false, time: 0, rank: 0 },
{ group: 11, unlocked: false, time: 0, rank: 0 },
{ group: 12, unlocked: false, time: 0, rank: 0 },
{ group: 13, unlocked: false, time: 0, rank: 0 },
],
unlocked: true,
avgTime: 0,
rank: 0,
},

Related

Mongoose: Array with 2 different nested schemas as key-value

I'm trying to insert data from Google Reply to Reviews API into MongoDb using mongoose but some of the objects inserted have empty fields and some of them are nested key-value objects inside an Array which I don't know how to catch.
This is the JSON response from the API:
{
"reviews": [
{
"reviewId": "19940672-2a0f-470b-8760-05ce91add518",
"authorName": "el piculin 357",
"comments": [
{
"userComment": {
"text": "\tEs bueno y los logos son faciles de adivinar",
"lastModified": {
"seconds": "1675041753",
"nanos": 386000000
},
"starRating": 5,
"reviewerLanguage": "es",
"device": "grandpplte",
"androidOsVersion": 23,
"appVersionCode": 99,
"appVersionName": "2.5.0",
"deviceMetadata": {
"productName": "grandpplte (Galaxy J2 Prime)",
"manufacturer": "Samsung",
"deviceClass": "FORM_FACTOR_PHONE",
"screenWidthPx": 540,
"screenHeightPx": 960,
"nativePlatform": "ABI_ARM_V7,ABI_ARM",
"screenDensityDpi": 240,
"glEsVersion": 196609,
"ramMb": 1407
}
}
}
]
}
]
}
This is my Schema (review.model.js):
const mongoose = require("mongoose");
const ReviewSchema = new mongoose.Schema({
reviewId: { type: String, default: '', unique: true },
authorName: { type: String, default: '' },
userComment: {
text: { type: String, default: '' },
lastModified: {
seconds: { type: String, default: '' },
nanos: { type: Number, default: 0 },
},
starRating: { type: Number, default: 0 },
reviewerLanguage: { type: String, default: '' },
device: { type: String, default: '' },
androidOsVersion: { type: Number, default: 0 },
appVersionCode: { type: Number, default: 0 },
appVersionNumber: { type: String, default: '' },
thumbsUpCount: { type: Number, default: 0 },
thumbsDownCount: { type: Number, default: 0 },
},
developerComment: {
text: { type: String, default: '' },
lastModified: {
seconds: { type: String, default: '' },
nanos: { type: Number, default: 0 },
}
},
appInformation: {
packageIdentifier: { type: String, default: '' }
}
});
const Review = mongoose.model("Review", ReviewSchema);
module.exports = Review;
Another thing that I tried:
const mongoose = require("mongoose");
const AppInformationSchema = new mongoose.Schema({
packageIdentifier: { type: String, default: '' }
});
const DeveloperCommentSchema = new mongoose.Schema({
text: { type: String, default: '' },
lastModified: { LastModifiedSchema }
});
const LastModifiedSchema = new mongoose.Schema({
seconds: { type: String, default: '' },
nanos: { type: Number, default: 0 },
});
const UserCommentScehma = new mongoose.Schema({
text: { type: String, default: '' },
lastModified: { type: LastModifiedSchema },
starRating: { type: Number, default: 0 },
reviewerLanguage: { type: String, default: '' },
device: { type: String, default: '' },
androidOsVersion: { type: Number, default: 0 },
appVersionCode: { type: Number, default: 0 },
appVersionNumber: { type: String, default: '' },
thumbsUpCount: { type: Number, default: 0 },
thumbsDownCount: { type: Number, default: 0 }
});
const ReviewSchema = new mongoose.Schema({
reviewId: { type: String, default: '', unique: true },
authorName: { type: String, default: '' },
comments: [UserCommentScehma, DeveloperCommentSchema],
appInformation: { AppInformationSchema }
});
const Review = mongoose.model("Review", ReviewSchema);
module.exports = Review;
But I get an error when trying to implement it like that:
ReferenceError: Cannot access 'LastModifiedSchema' before initialization
What is the right way of implementing it?
As UserCommentScehma is under userComment itself, so you need to consider this structure in the schema as well.
const ReviewSchema = new mongoose.Schema({
reviewId: { type: String, default: '', unique: true },
authorName: { type: String, default: '' },
comments: [{ userComment: UserCommentScehma }, DeveloperCommentSchema],
appInformation: { AppInformationSchema }
});

One to many with MongoDB (Mongoose) with .populate()

What I am doing
I am trying to return a player by playerId with all ref items from each object in it's schema. In this example, I am specifically talking about the players inventory.
How can I return all reference items and their properties?
In my service file, I am getting my player with:
/**
* Get a player by playerId
* #param playerId
* #returns {Promise<*>}
*/
module.exports.getPlayer = async (playerId) => {
return await Player.findOne({ playerId: playerId }).populate('inventory');
};
And this is my returned JSON
{
"success": true,
"data": {
"_id": "63bb1f3ec17d33f4d2a87194",
"playerId": 4183489039,
"name": "AlanGreyjoy",
"currency": {
"copper": 500,
"silver": 10,
"gold": 0
},
"online": false,
"inventory": [
{
"currentDurability": 0,
"item": "63ba0c54407456969ba38615",
"amount": 1,
"_id": "63bc4fa070eaa247288e3573",
"createdAt": "2023-01-09T17:32:16.643Z",
"updatedAt": "2023-01-09T17:32:16.643Z"
}
],
"bank": [],
"questTracker": [],
"friends": [],
"knownRecipes": [],
"jobs": [],
"createdAt": "2023-01-08T19:53:34.903Z",
"updatedAt": "2023-01-09T17:32:16.643Z",
"__v": 1
}
}
As you can see, the item in the inventory array is not populating with the ref item from the items collection.
I have googled and googled and tried so many different things, but nothing is working.\
The item does exist in the db
My Models
My Player.model.js
const mongoose = require('mongoose');
const toJSON = require('../plugins/mongoToJson');
const Schema = mongoose.Schema;
const questTracker = new Schema(
{
quest: { type: mongoose.Schema.Types.ObjectId, ref: 'Quest' },
complete: { type: Boolean, default: false },
},
{ timestamps: true }
);
const friends = new Schema(
{
player: { type: mongoose.Schema.Types.ObjectId, ref: 'Player' },
isFavorite: { type: Boolean, default: false },
},
{ timestamps: true }
);
const knownRecipes = new Schema(
{
recipe: { type: mongoose.Schema.Types.ObjectId, ref: 'Recipe' },
},
{ timestamps: true }
);
const jobs = new Schema(
{
job: { type: mongoose.Schema.Types.ObjectId, ref: 'Job' },
},
{ timestamps: true }
);
const inventoryItems = new Schema(
{
item: { type: mongoose.Schema.Types.ObjectId, ref: 'Item' },
amount: { type: Number, default: 0 },
currentDurability: { type: Number, default: 0 },
},
{ timestamps: true }
);
const bank = new Schema(
{
item: { type: mongoose.Schema.Types.ObjectId, ref: 'Item' },
amount: { type: Number, default: 0 },
},
{ timestamps: true }
);
const playerSchema = new Schema(
{
playerId: {
type: Number,
unique: true,
required: true,
},
name: {
type: String,
required: true,
unique: true,
},
currency: {
type: Object,
default: {
copper: 500,
silver: 10,
gold: 0,
},
},
inventory: [inventoryItems],
bank: [bank],
questTracker: [questTracker],
friends: [friends],
knownRecipes: [knownRecipes],
jobs: [jobs],
online: {
type: Boolean,
default: false,
},
},
{ timestamps: true }
);
const PlayerModel = mongoose.model('player', playerSchema);
module.exports = PlayerModel;
My Item.model.js
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const toJSON = require('../plugins/mongoToJson');
const vendorPriceSchema = mongoose.Schema({
copper: {
type: Number,
default: 0,
},
silver: {
type: Number,
default: 0,
},
gold: {
type: Number,
default: 0,
},
});
const itemSchema = new Schema(
{
title: {
type: String,
required: true,
unique: true,
},
assetId: {
type: Number,
required: true,
},
description: {
type: String,
},
equipDescription: {
type: String,
},
additionalDescription: {
type: String,
},
consumableUseDescription: {
type: String,
},
itemLevel: {
type: Number,
},
requiredLevel: {
type: Number,
},
type: {
type: String,
required: true,
},
subtype: {
type: String,
required: true,
},
stamina: {
type: Number,
},
intellect: {
type: Number,
},
criticalStrike: {
type: Number,
},
agility: {
type: Number,
},
mastery: {
type: Number,
},
maxDurability: {
type: Number,
},
vendorPrice: { vendorPriceSchema },
minDamage: {
type: Number,
default: 0,
},
maxDamage: {
type: Number,
default: 0,
},
speed: {
type: Number,
},
maxStack: {
type: Number,
},
},
{ timestamps: true }
);
const ItemModel = mongoose.model('Item', itemSchema);
module.exports = ItemModel;
You can use something like this:
module.exports.getPlayer = async (playerId) => {
return await Player.findOne({ playerId: playerId }).populate({
path: "inventory",
populate: {
path: "item",
},
});
};

Problems using $in in MongoDB with NodeJS

I am facing a strange problem in trying to use $in operator with mongoose and node. Following is the snippet from the code.
queryObj = {imei: {$in: queryItems.imei}}
console.log(JSON.stringify(queryObj))
//{"imei":{"$in":[866551038558099,866551038558099, 866551038549072, 866551038536939, 866551038557844]}}
The query returns an empty array, whereas if I copy-paste the log in MongoDB Compass, I can see the result set.
const mongoose = require('mongoose');
const GeofenceReportSchema = new mongoose.Schema({
createdOn: {
type: Date,
default: new Date(),
required: true
},
targetDate: {
type: Date,
required: true
},
vehicleNo: {
type: String,
default: '',
required: true
},
imei: {
type: Number,
required: true
},
numberPlate: {
type: String,
default: '',
required: false
},
driverName: {
type: String,
default: '',
required: false
},
driverContact: {
type: Number,
default: 0,
required: false
},
startTimeIgnOn: {
type: String,
default: '',
required: false
},
endTimeIgnOff: {
type: String,
default: '',
required: false
},
idlingTime: {
type: Number,
default: 0,
required: false
},
distance: {
type: Number,
default: 0,
required: true
},
averageSpeed: {
type: Number,
default: 0,
required: true
},
engineHours: {
type: Number,
default: 0,
required: true
},
startOdometer: {
type: Number,
default: 0,
required: true
},
endOdometer: {
type: Number,
default: 0,
required: true
},
totalFuelConsumption: {
type: Number,
default: 0,
required: true
},
totalStops: {
type: Number,
default: 0,
required: true
},
geofenceInTime: {
type: Number,
default: 0,
required: false
},
geofenceOutTime: {
type: Number,
default: 0,
required: false
},
distanceInsideGeofence: {
type: Number,
default: 0,
required: false
},
distanceOutsideGeofence: {
type: Number,
default: 0,
required: false
},
performance: {
type: Number,
default: 0,
required: false
}
});
let model;
try {
model = mongoose.model('GeofenceReport');
} catch (error) {
model = mongoose.model('GeofenceReport', GeofenceReportSchema);
}
module.exports = model;
function getGeofenceReport(queryItems){
let queryObj = {$and: [ {targetDate: { $gte: new Date(queryItems.fromTime), $lte: new Date(queryItems.toTime)}},{imei: {$in: queryItems.imei}}]}
console.log(JSON.stringify(queryObj))
return new Promise((resolve, reject) => {
connectToDatabase().then(() => {
GeofenceReportSchema.find(queryObj)
.sort({"targetDate" : 1})
.then((resp) => {
resolve(resp);
})
.catch((err) => {
reject(err);
})
}).catch((err) => {
reject(err);
});
});
}
Sample data
{
"_id": {
"$oid": "606d82c585ac11242012cb37"
},
"createdOn": {
"$date": "2021-04-07T10:00:37.269Z"
},
"targetDate": {
"$date": "2021-04-06T14:30:00.000Z"
},
"vehicleNo": "ABCDE",
"imei": {
"$numberLong": "866551038558000"
},
"numberPlate": "",
"driverName": "John Doe",
"driverContact": 99999999,
"startTimeIgnOn": "2021-04-06T01:59:44.000Z",
"endTimeIgnOff": "2021-04-06T11:15:31.000Z",
"idlingTime": 8344000,
"distance": 27277.310000000056,
"averageSpeed": 3.59232320529802,
"engineHours": 23325000,
"startOdometer": 658548.82,
"endOdometer": 685826.13,
"totalFuelConsumption": 27.277310000000057,
"totalStops": 8,
"geofenceInTime": 18043000,
"geofenceOutTime": 5282000,
"distanceInsideGeofence": 4421.061251513145,
"distanceOutsideGeofence": 22856.248748486913,
"performance": 59.52
}
Invocation
{
fromTime : '2021-03-03T18:30:00.000Z',
toTime: '2021-03-31T18:29:59.000Z',
imei: 866551038558000
}
Note that I have already checked DB connections, have tried with the empty find method and I am able to get data. I have also checked data types. Isn't an issue there. What can be the issue?

Mongoose upsert doesn't set defaults for Schema.types.mixed properties, setDefaultsOnInsert is true

I'm using an upsert function to serialize users with my backend. I'm using Mongoose's "findOneAndUpdate" with "upsert: true" and "setDefaultsOnInsert: true" set, so that all default values defined in my Schema ought to be used for all parameters that I don't pass to the upsert.
This is my upsert:
let serialize = (params, cb) => {
User.findOneAndUpdate({"fbId" : params.fbId}, params, { upsert: true, setDefaultsOnInsert: true, new: true }, (err, doc) => {
if (err) {
//winston.log
return cb (ErrorTypes.serverError(), null);
}
else if (doc) {
return cb(null, doc);
}
else {
return cb(ErrorTypes.serverError(), null);
}
});
}
For this schema:
const userSchema = new Mongoose.Schema({
fbId: {
type: String,
index: true,
required: "Users must have a facebook ID.",
maxlength: 40
},
firstName: {
type: String,
required: "Users must have a name.",
maxlength: 40
},
lastName: {
type: String,
required: "Users must have a name.",
maxlength: 40
},
gender: {
type: String,
required: "Users must have a gender",
default: "Unknown"
},
ageRange: {
type: String,
default: "Unknown",
maxlength: 25
},
pictureUrl: {
type: String,
required: "Users must have a profile picture."
},
dateJoined: {
type: Number,
required: "Users must have a joined date.",
default: date.getTime()
},
pushId: String,
bookmarks: [{
item: {
type: Mongoose.Schema.Types.ObjectId,
ref: 'Item',
required: "An item id is required to create a bookmark."
},
lastOfferMade: Number,
timeOfferMade: Number,
}], default: [],
offersInCategory: {
type: Object,
validate: object => {
let allowedKeys = ['Furniture', 'Household', 'Electronics', 'Other'];
let correctKeys = Object.keys(object).every(key => allowedKeys.includes(key));
let min = 0;
let correctValues = Object.values(object).every(value => value >= min);
return correctKeys && correctValues;
}
}, default: {
'Furniture': 0,
'Household': 0,
'Electronics': 0,
'Other': 0
},
rejectionsInCategory: {
type: Object,
validate: object => {
let allowedKeys = ['Furniture', 'Household', 'Electronics', 'Other'];
let correctKeys = Object.keys(object).every(key => allowedKeys.includes(key));
let min = 0;
let correctValues = Object.values(object).every(value => value >= min );
return correctKeys && correctValues;
}
}, default: {
'Furniture': 0,
'Household': 0,
'Electronics': 0,
'Other': 0
},
blockedUsers: [{
userId: String,
blockedOn: Number
}], default: [],
blockedBy: [{
userId: String,
blockedOn: Number
}], default: [],
hasSeenWalkThrough: { type: Boolean, default: false },
hasBookmarkedItem: { type: Boolean, default: false },
hasSeenSearchPrompt: { type: Boolean, default: false }
});
Passing in these parameters:
var upsertNewParams = {
fbId: "newUpsertFbId",
firstName: "New upsert - User",
lastName: "Test Last Name - User",
gender: "Male",
ageRange: "18-22",
pictureUrl: "url",
pushId: "12345"
}
My upsert function returns this document:
{ _id: 5931b0efbeadf32d858a7578,
fbId: 'newUpsertFbId',
__v: 0,
firstName: 'New upsert - User',
lastName: 'Test Last Name - User',
pictureUrl: 'url',
pushId: '12345',
hasSeenSearchPrompt: false,
hasBookmarkedItem: false,
hasSeenWalkThrough: false,
blockedBy: [],
blockedUsers: [],
default: [],
bookmarks: [],
dateJoined: 1496429065565,
ageRange: '18-22',
gender: 'Male' }
I can clearly see all other defaults such as "hasSeenWalkthrough" and "blockedBy/blockedUsers/bookmarks" get set, but my "offersInCategory" and "rejectionsInCategory" for whatever reason don't, though I provide default values for those properties like any others. How do I set these default values using an upsert?

Removing an objectID of an object in an array with Mongoose

I have the following in my clan scheme
var ClanScheme = new mongoose.Schema
({
name: {type: String, unique: true},
members:
[
{
user: {type: mongoose.Schema.Types.ObjectId, ref:'User', unique: true},
wins: {type: Number, default: 0},
losses: {type: Number, default: 0}
}
],
How do I remove a user from the clan? I've tried a few methods this looking like the least code:
clan.members.remove({'user':userObj._id});
clan.save(function(err)
It seems to run, but the user sticks in the document..
Clan
{ _id: 55e5e8d017e055495dcc3643,
name: 'DBz',
__v: 9,
rating: 1000,
losses: 0,
wins: 0,
rank: 0,
members:
[ { user: [Object],
_id: 55e5e8d017e055495dcc3644,
losses: 0,
wins: 0 },
{ user: [Object],
_id: 55e5eb0f17e055495dcc3645, //<< 55e4ac0340f964d52f8e7fb7
losses: 0,
wins: 0 } ] }
User
{ _id: 55e4ac0340f964d52f8e7fb7,
facebookid: '999',
name: 'Joe Blogs',
__v: 0,
lastDevice: { device: 'Desktop', id: 'adsbr2fjui33emk9p6gtnfrulv' },
multiplayer:
{ clanid: 55e5e8d017e055495dcc3643,
powers: [],
world_commander: 0,
losses: 0,
wins: 0,
clanname: 'DBz',
rating: 1000,
rank: 0,
username: 'Joe' },
saveDataSeed: 40wq211,
saveData: 'yuV2hVJA00zYGm'}
Use a filter function and save.
clan.members = clan.members.filter(function(member){
return String(member.user._id) !== String(userObj._id);
});
clan.markModified('members');
clan.save(function(err)

Resources