mongoose.populate() with graphql is not able to perform 'infinite' queries - mongodb

I have a stack of ApolloServer for GraphQL with MongoDB as a database with mongoose as a library.
I have now set up my code that I am able to perform some queries that goes 2-3 levels in to deep. But when I try to query further and go to my original object. It will return me with a null.
I have tried the solution of Academind already with some youtube films + searching the world of internet, however. No solutions found to this yet.
My resolver functions
module.exports = {
Query: {
athlete: async (root, args, context) => {
return await Athlete.findOne({ _id: args._id }).populate('results');
},
race: async (root, args) => {
return await Race.findOne({ _id: args._id }).populate('athletes');
},
result: async (root, args) => {
return await Result.findOne({ _id: args._id })
.populate('race')
.populate('athlete')
.exec();
},
athletes: async () => {
return await Athlete.find().populate('results');
},
races: async () => {
return await Race.find().populate('athletes');
},
results: async () => {
return await Result.find()
.populate('race')
.populate('athlete')
.exec();
}
}
};
Easier is to go to github though: https://github.com/axelvuylsteke/fullstack-graphql-vue/tree/graphQL_stackOverflow_question
npm run dev to start it in dev mode.
You can run the queries, first one you will get a null for the race.name, second one when you take results as a source root, you get a value for the name:
query searchAthlete {
athlete(_id: "5d690a25a1917025486c27ac") {
_id
country
results{
_id
race{
_id
name
}
}
}
}
query searchResults {
results {
_id
race{
_id
name
}
}
}
I expect to have infinite possibilities to go through the queries from object to object...
You can normally connect to the mongoDB as the dev mode is set to all open IP adresses.
Little background:
An athlete will store a race result.
A race will store all participated athletes.
A result will store the result itself with an athlete and the race where the result is performed.

Related

How to remove all the items from MongoDB except last N using Mongoose?

I am using NestJS to keep logic and history of Calculator. So the point is I want to keep only last 10 cases in DB. But don't know how to do it. Let's take a look. Here's my history.service.ts
#Injectable()
export class HistoryService {
constructor(
#InjectModel(HistoryItem.name)
private historyModel: Model<HistoryItemDocument>,
) {}
async create(dto: CreateHistoryItemDto): Promise<HistoryItem> {
const historyItem = await this.historyModel.create({ ...dto });
return historyItem;
}
async getAll(): Promise<HistoryItem[]> {
const allHistoryItems = await this.historyModel
.find()
.sort({ _id: -1 }) //Here I sort the items to get the latest ones
.limit(maxNumberOfDBItemsToDisplay);
//Here I limit number of items to send it to Client
return allHistoryItems;
}
async getOne(id: ObjectId): Promise<HistoryItem> {
const historyItem = await this.historyModel.findById(id);
return historyItem;
}
async delete(id: ObjectId): Promise<ObjectId> {
const historyItem = await this.historyModel.findByIdAndDelete(id);
return historyItem.id;
}
}
As you see I can get 10 last items from DB. But how to remove the rest to keep base updated only with 10 last cases?

MongoDB Mutation Upsert - Can't Get Id of New Record on First Submit

I'm executing an upsert mutation on MongoDB to create a new document or update an existing document. If the document exists, the mutation returns the id as expected. If a new document is created, the mutation returns null (in both Apollo sandbox and via console.log) in the initial return then in subsequent returns it will return the id. I need it to return the id of the newly created document immediately (on the first return) so I can use that id in subsequent actions.
Starting from the beginning here's the setup:
TYPEDEF
updateHourByEmployeeIdByJobDate(
jobDate: String
startTime: String
endTime: String
hoursWorked: String
employee: String
): Hour
RESOLVER
updateHourByEmployeeIdByJobDate: async (
parent,
{ jobDate, startTime, endTime, hoursWorked, employee },
context
) => {
// if (context.user) {
console.log("resolver hours update = ");
return Hour.findOneAndUpdate(
{ employee, jobDate },
{
jobDate,
startTime,
endTime,
hoursWorked,
employee,
},
{
upsert: true,
}
);
// }
// throw new AuthenticationError("You need to be logged in!");
},
MUTATION
//UPDATE HOUR RECORD - CREATES DOCUMENT IF IT DOESN'T EXIST OR UPDATES IF IT DOES EXIST VIA THE UPSERT OPTION ON THE RESOLVER
export const UPDATE_HOURS_BYEMPLOYEEID_BYJOBDATE = gql`
mutation UpdateHourByEmployeeIdByJobDate(
$jobDate: String
$startTime: String
$endTime: String
$hoursWorked: String
$employee: String
) {
updateHourByEmployeeIdByJobDate(
jobDate: $jobDate
startTime: $startTime
endTime: $endTime
hoursWorked: $hoursWorked
employee: $employee
) {
_id
}
}
`;
FRONT-END EXECUTION
const [ mostRecentHourUpdateId, setMostRecentHoursUpdateId ] = useState();
const [updateHours] = useMutation(UPDATE_HOURS_BYEMPLOYEEID_BYJOBDATE, {
onCompleted: (data) => {
console.log('mutation result #1 = ', data)
setMostRecentHoursUpdateId(data?.updateHourByEmployeeIdByJobDate?._id);
console.log('mutation result #2 = ', mostRecentHourUpdateId)
},
});
//section update database - this mutation is an upsert...it either updates or creates a record
const handleUpdateDatabase = async (data) => {
console.log(data);
try {
// eslint-disable-next-line
const { data2 } = await updateHours({
variables: {
jobDate: moment(data.date).format("MMMM DD YYYY"), //"January 20 2023"
startTime: `${data.startTime}:00 (MST)`, //"12:00:00 (MST)"
endTime: `${data.endTime}:00 (MST)`, //"13:00:00 (MST)"
hoursWorked: data.hours.toString(), //"3.0"
employee: userId, //"6398fb54494aa98f85992da3"
},
});
console.log('handle update database function = data = ', data2); //fix
} catch (err) {
console.error(err);
}
singleHoursRefetch();
};
I've tried using onComplete as part of the mutation request as well as useEffect not to mention running the mutation in Apollo Sandbox. Same result in all scenarios. The alternative is to re-run the useQuery to get the most recent / last record created but this seems like a challenging solution (if at some point the records are sorted differently) and/or seems like I should be able to get access to the newly created record immediately as part of the mutation results.
You'll want to use { returnNewDocument: true }
Like this:
const getData = async () => {
const returnedRecord = await Hour.findOneAndUpdate( { employee, jobDate }, { jobDate, startTime, endTime, hoursWorked, employee, }, { upsert: true, returnNewDocument: true } );
// do something with returnedRecord
}
getData();
For more information:
https://www.mongodb.com/docs/manual/reference/method/db.collection.findOneAndUpdate/

Create new document if there is not document with same name mongoose

var userSchema = mongoose.Schema({
username: { type: String},
email: String,
password: String,
tasks: [String]
});
I want to create a new user document only if there is no another user with the same name.
What the best way to do that?
this is my implementation currently :
router.post('/', async function(req, res, next) {
use let user= await User.findOne({ name: req.body.name})
if (user) {
if (req.body.name == user.name) {
// return new Error("User exists!"));
}
User.create(req.body, function (err, newuser) {
if (err) {
console.log(err);
return next(err);
}
res.json(newuser);
});
}
Another question about mongoDB collections:
Should I open new collection ( and schema) for 'task' property?
It has only 1 field - the task itself in type string.
Your current implementation is the way to go, you find the user, if none is found, you create a new one.
I refactored your code a little bit, here's how it would look like:
router.post('/', async function (req, res, next) {
try {
const user = await User.findOne({ name: req.body.name });
if (user) {
return res.json(user);
}
const newUser = await User.create(req.body);
return res.json(newUser);
} catch (err) {
console.log(err);
return next(err);
}
});
As to your second question, if you're sure that the task will always be a simple string and you won't need more data in there, it's better to keep it embedded within the document.
It's likely that you'll need to extend functionalities for tasks, and add more data such as a status, deadline, maybe subtasks, in which case it's better to separate it in a different collection.
This series of articles is probably a good place to quickly learn a rule of thumb on how to design your schemas.

Issue Connecting to MongoDB collections

I am using axios and express.js API to connect to my mongo DB. I have a .get() request that works for one collection and doesn't work for any other collection. This currently will connect to the database and can access one of the collections called users. I have another collection setup under the same database called tasks, I have both users and tasks setup the same way and being used the same way in the code. The users can connect to the DB (get, post) and the tasks fails to connect to the collection when calling the get or the post functions. When viewing the .get() API request in the browser it just hangs and never returns anything or finishes the request.
any help would be greatly appreciated!
The project is on GitHub under SCRUM-150.
API connection
MONGO_URI=mongodb://localhost:27017/mydb
Working
methods: {
//load all users from DB, we call this often to make sure the data is up to date
load() {
http
.get("users")
.then(response => {
this.users = response.data.users;
})
.catch(e => {
this.errors.push(e);
});
},
//opens delete dialog
setupDelete(user) {
this.userToDelete = user;
this.deleteDialog = true;
},
//opens edit dialog
setupEdit(user) {
Object.keys(user).forEach(key => {
this.userToEdit[key] = user[key];
});
this.editName = user.name;
this.editDialog = true;
},
//build the alert info for us
//Will emit an alert, followed by a boolean for success, the type of call made, and the name of the
//resource we are working on
alert(success, callName, resource) {
console.log('Page Alerting')
this.$emit('alert', success, callName, resource)
this.load()
}
},
//get those users
mounted() {
this.load();
}
};
Broken
methods: {
//load all tasks from DB, we call this often to make sure the data is up to date
load() {
http
.get("tasks")
.then(response => {
this.tasks = response.data.tasks
})
.catch(e => {
this.errors.push(e);
});
},
//opens delete dialog
setupDelete(tasks) {
this.taskToDelete = tasks;
this.deleteDialog = true;
},
//opens edit dialog
setupEdit(tasks) {
Object.keys(tasks).forEach(key => {
this.taskToEdit[key] = tasks[key];
});
this.editName = tasks.name;
this.editDialog = true;
},
//build the alert info for us
//Will emit an alert, followed by a boolean for success, the type of call made, and the name of the
//resource we are working on
alert(success, callName, resource) {
console.log('Page Alerting')
this.$emit('alert', success, callName, resource)
this.load()
}
},
//get those tasks
mounted() {
this.load();
}
};
Are you setting any access controls in the code?
Also refer to mongoDB's documentation here:
https://docs.mongodb.com/manual/core/collection-level-access-control/
Here is my solution:
In your app.js, have this:
let mongoose = require('mongoose');
mongoose.connect('Your/Database/Url', {
keepAlive : true,
reconnectTries: 2,
useMongoClient: true
});
In your route have this:
let mongoose = require('mongoose');
let db = mongoose.connection;
fetchAndSendDatabase('yourCollectionName', db);
function fetchAndSendDatabase(dbName, db) {
db.collection(dbName).find({}).toArray(function(err, result) {
if( err ) {
console.log("couldn't get database items. " + err);
}
else {
console.log('Database received successfully');
}
});
}

Using $inc to increment a document property with Mongoose

I would like to increment the views count by 1 each time my document is accessed. So far, my code is:
Document
.find({})
.sort('date', -1)
.limit(limit)
.exec();
Where does $inc fit in here?
Never used mongoose but quickly looking over the docs here it seems like this will work for you:
# create query conditions and update variables
var conditions = { },
update = { $inc: { views: 1 }};
# update documents matching condition
Model.update(conditions, update).limit(limit).sort('date', -1).exec();
Cheers and good luck!
I ran into another problem, which is kind of related to $inc.. So I'll post it here as it might help somebody else. I have the following code:
var Schema = require('models/schema.js');
var exports = module.exports = {};
exports.increase = function(id, key, amount, callback){
Schema.findByIdAndUpdate(id, { $inc: { key: amount }}, function(err, data){
//error handling
}
}
from a different module I would call something like
var saver = require('./saver.js');
saver.increase('555f49f1f9e81ecaf14f4748', 'counter', 1, function(err,data){
//error handling
}
However, this would not increase the desired counter. Apparently it is not allowed to directly pass the key into the update object. This has something to do with the syntax for string literals in object field names. The solution was to define the update object like this:
exports.increase = function(id, key, amount, callback){
var update = {};
update['$inc'] = {};
update['$inc'][key] = amount;
Schema.findByIdAndUpdate(id, update, function(err, data){
//error handling
}
}
Works for me (mongoose 5.7)
blogRouter.put("/:id", async (request, response) => {
try {
const updatedBlog = await Blog.findByIdAndUpdate(
request.params.id,
{
$inc: { likes: 1 }
},
{ new: true } //to return the new document
);
response.json(updatedBlog);
} catch (error) {
response.status(400).end();
}
});