How to access #Args() in #ResolveField in nestjs - javascript

I have a query for getting projects from the DB which has args as shown below for filtering projects. How would I use the same args in the #resolveField for filtering form values of the project
#Query(() => [Project])
async getProjects(#Args() projectArgs: ProjectArgs) {
return await this.projectsService.find(projectArgs);
}
#ResolveField("formValues", () => [FormValues])
async getFormValues(#Parent() project: Project) {
const { id } = project;
return await this.formsService.findValues({ projectId: id});
}

I have faced this problem before and through thorough searching, I have come to the conclusion that the best and scalable way of doing this is to define the args for the field resolver itself. Here is how you do it
#ResolveField("formValues", () => [FormValues])
async getFormValues(#Args() projectArgs: ProjectArgs) {
..../////
}
This way you will have to pass the args just as you pass it in the parent query. Or you could leave them out if you don't want to filter the form values
There are other approaches that you can use. One of them is by setting the info as
#Query(() => [Project])
async getProjects(#Args() projectArgs: ProjectArgs, #Info() info) {
info.variableValues.some_key = value
return await this.projectsService.find(projectArgs);
}
But this won't scale and make it very tightly coupled

Related

Value (number) is different from the MongoDB to the react client async call

Problem:
An entire field of my MongoDB's collections' is not transmitted correctly from the db to the react client. For an exemple, the first element of this field is tweet_id: 1537466989966413825 in my DB. It becomes tweet_id: 1537466989966413800 when I fetch from my client.
My steps
I pull my data from the MongoDB using their App services' function like that:
exports = function(){
var collection = context.services.get("mongodb-atlas").db("meme_news").collection("news");
return collection.find({});
};
When I press the Run button of their built-in terminal, the correct data is displayed.
On my react's application, I perform the fetch like that:
useEffect(() => {
getData();
}, []);
const getData = async () => {
let getAllData = await user.functions.getAllData();
// all these data are wrong
let tweetId = getAllData
.map((ele) => {
return ele.tweet_id;
})
let tweetIdFirstEle = tweetId[0];
// this return 1537466989966413800
// It should return 1537466989966413825
};
Why is my async/await altering my Mongodb data? I have no idea what is going on here.

Delete a batch of docs using admin SDK and callable cloud function in most similar way to client SDK?

I have an array of docs ids that I want to delete in using a cloud function, my code looks like the following :
//If the user decieds on deleting his account forever we need to make sure we wont have any thing left inside of db after this !!
// incoming payload array of 3 docs
data = {array : ['a302-5e9c8ae97b3b','92c8d309-090d','a302-5e932c8ae97b3b']}
export const deleteClients = functions.https.onCall(async (data, context) => {
try {
// declare batch
const batch = db.batch();
// set
data.arr.forEach((doc: string) => {
batch.delete(db.collection('Data'), doc);
});
// commit
await batch.commit();
} catch (e) {
console.log(e);
}
return null;
});
I am getting a syntax error on batch.delete how to pass the right arguments in to the batch delete to reference that doc I want to submit for deletion before commit ?
Delete takes a single param, the doc ref of the doc to be deleted.
data.arr.forEach((docId: string) => {
batch.delete(doc(db, "Data", docId));
});
There are several errors in your code:
data.arr.forEach() cannot work wince your data object contains one element with the key array and not the key arr.
You are mixing up the syntax of the JS SDK v9 and the Admin SDK. See the write batch Admin SDK syntax here.
You need to send back some data to the client when all the asynchronous work is complete, to correctly terminate your CF.
You do return null; AFTER the try/catch block: this means that, in most of the cases, your Cloud Function will be terminated before asynchronous work is complete (see the link above)
So the following should do the trick (untested):
const db = admin.firestore();
const data = {array : ['a302-5e9c8ae97b3b','92c8d309-090d','a302-5e932c8ae97b3b']};
export const deleteClients = functions.https.onCall(async (data, context) => {
try {
const batch = db.batch();
const parentCollection = db.collection('Data')
data.array.forEach((docId) => {
batch.delete(parentCollection.doc(docId));
});
// commit
await batch.commit();
return {result: 'success'} // IMPORTANT, see explanations above
} catch (e) {
console.log(e);
// IMPORTANT See https://firebase.google.com/docs/functions/callable#handle_errors
}
});

What functions should I mock during unit testing

I've been reading some articles, and posts here on Stack Overflow about when I should mock a function and when I shouldn't, but I have a case where I'm not sure about what to do.
I have a UserService class which uses dependency injection concept to receive dependencies through its constructor.
class UserService {
constructor(userRepository) {
this.userRepository = userRepository;
}
async getUserByEmail(userEmail) {
// would perform some validations to check if the value is an e-mail
const user = await this.userRepository.findByEmail(email);
return user;
}
async createUser(userData) {
const isEmailInUse = await this.getUserByEmail(userData.email);
if(isEmailInUse) {
return "error";
}
const user = await this.userRepository.create(userData);
return user;
}
}
I want to test if the createUser method works properly, and for my tests, I created a fake userRepository which is basically a object with mocked methods that I will use while instantiating UserService Class
const UserService = require('./UserService.js');
describe("User Service tests", () => {
let userService;
let userRepository;
beforeEach(() => {
userRepository = {
findOne: jest.fn(),
create: jest.fn(),
}
userService = new UserService(userRepository);
});
afterEach(() => {
resetAllMocks();
});
describe("createUser", () => {
it("should be able to create a new user", async () => {
const newUserData = { name: 'User', email: 'user#test.com.br' }
const user = { id: 1, name: 'User', email: 'user#test.com.br' }
userRepository.create.mockResolvedValue(user);
const result = await userService.createUser();
expect(result).toStrictEqual(user);
})
})
})
Note that in the createUser method, there is a call to the getUserByEmail method which is also a method of UserService class, and that is where I got confused.
Should I mock the getUserByEmail method even it is a method of the class I'm testing? If it is not the correct approach, what should I do?
You should almost always prefer not to mock parts of the thing you're supposed to be testing, in this case UserService. To illustrate why, consider these two tests:
Provides a test double implementation for findByEmail on the repo object:
it("throws an error if the user already exists", async () => {
const email = "foo#bar.baz";
const user = { email, name: "Foo Barrington" };
const service = new UserService({
findByEmail: (_email) => Promise.resolve(_email === email ? user : null),
});
await expect(service.createUser(user)).rejects.toThrow("User already exists");
});
Stubs out the service's own getUserByEmail method:
it("throws an error if the user already exists", async () => {
const email = "foo#bar.baz";
const user = { email, name: "Foo Barrington" };
const service = new UserService({});
service.getUserByEmail = (_email) => Promise.resolve(_email === email ? user : null);
await expect(service.createUser(user)).rejects.toThrow("User already exists");
});
For your current implementation, both pass just fine. But let's think about how things might change.
Imagine we need to enrich the user model getUserByEmail provides at some point:
async getUserByEmail(userEmail) {
const user = await this.userRepository.findByEmail(userEmail);
user.moreStuff = await.this.userRepository.getSomething(user.id);
return user;
}
Obviously we don't need this extra data just to know whether or not the user exists, so we factor out the basic user object retrieval:
async getUserByEmail(userEmail) {
const user = await this._getUser(userEmail);
user.moreStuff = await.this.userRepository.getSomething(user.id);
return user;
}
async createUser(userData) {
if (await this._getUser(userData.email)) {
throw new Error("User already exists");
}
return this.userRepository.create(userData);
}
async _getUser(userEmail) {
return this.userRepository.findByEmail(userEmail);
}
If we were using test 1, we wouldn't have to change it at all - we're still consuming findByEmail on the repo, the fact that the internal implementation has changed is opaque to our test. But with test 2, that's now failing even though the code still does the same things. This is a false negative; the functionality works but the test fails.
In fact you could have applied that refactor, extracting _getUser, prior to a new feature making the need so clear; the fact that createUser uses getUserByEmail directly reflects accidental duplication of this.userRepository.findByEmail(email) - they have different reasons to change.
Or imagine we make some change that breaks getUserByEmail. Let's simulate a problem with the enrichment, for example:
async getUserByEmail(userEmail) {
const user = await this.userRepository.findByEmail(userEmail);
throw new Error("lol whoops!");
return user;
}
If we're using test 1, our test for createUser fails too, but that's the correct outcome! The implementation is broken, a user cannot be created. With test 2 we have a false positive; the test passes but the functionality doesn't work.
In this case, you could say that it's better to see that only getUserByEmail is failing because that's where the problem is, but I'd contend that would be pretty confusing when you looked at the code: "createUser calls that method too, but the tests say it's fine...".
You shouldn't mock any of these functions since its creating users and reading data from the database. If you mock them then what's the point of the test. In other words, you wouldn't know if your app is working correctly with the database or not. Anyway, I would mock functions such as the functions that send emails and so on. Don't mock the functions that are the heart of the application. You should have a database for testing and another one for production.

How to return value of both of the consecutive async functions in JS?

I've recently started with web development, and am a novice in JavaScript.
I'm currently working on a Blog Project and using Firebase as the backend, and Firestore as the Database.
PROJECT-
using the following project structure
Firestore DB ->
-Posts
-post1
-post2
-post3
...
-authors
-author1
-author2
..
-subscribers
...
I'm using this function to retrieve my posts from Firestore.
app.get('/:id', (req, res) => {
const id = req.params.id;
async function getDocument(id) {
const doc = await db.collection('Posts').doc(id).get();
if (!doc.exists) {
console.log('No such document!');
} else {
return doc.data();
}
}
getDocument(id).then(function (data) {
res.render('post',{ articleInfo:data} );
// that send back an object containing post details
})
Now, from the JSON I get from the above function, I want to use the value of "Author" to get the author's details from the another collection(run another async function),
and then send this data along with the data from previous function(used to get post details) together in res.render()
For example
I make a get request for a post and get back it's details. Inside which get the author's name "{..,"author : mike-ross",..} " .
Now, after .then, I use this value to run another async function get this author's JSON from the Authors collection. and pass the result from both the functions together in res.render() to get diplayed on the page.
You could have one async function that does both calls and returns an object with both results:
async function getDocWithDetails (id) {
const doc = await getDocument(id);
const authorInfo = await getAuthorInfo(doc.author);
return {
doc: doc.data(),
authorInfo: doc.data()
}
}
getDocWithDetails(id).then((data) => {
res.render('post', data) // has data.doc and data.authorInfo
});
Just go for it, same way you did the first half :-)
In general (at least at my work projects), it's good practice to setup multiple getters for each collection or one main getter where the collection is a secondary argument.
Here's the second variant with dynamic variant (but the first one is just as valid). Especially if you're not using typescript or flowtype to typecheck what you're passing, then it's more prone to unexpected errors by passing incorrect param, I'm just lazy to write the similar function in the answer twice.
const getDocumentById = async (id, collection) => {
const doc = await db.collection(collection).doc(id).get()
if (!doc.exists) {
throw Error(`Doc with id "${id}" doesn't exist on collection "${collection}"`)
}
return doc.data()
}
Now that we have our generic getter setup, you just need to create a function that fetches both.
As a precursor, I have taken some creative liberties with the answer, since the question is missing some cruicial info about documents.
At the very least (and if you don't you should, it's good firebase practice) I presume you have a link to the authorId inside Posts document.
Posts
-> post
-> id
-> authorId
-> some_other_fields
Authors
-> author
-> id
-> some_other_fields
With the following structure in mind, your answer will look something like this:
const getAuthorByPostId = async (id) => {
try {
const post = await getDocumentById(id, 'Posts')
const { authorId } = post
const author = await getDocumentById(authorId, 'Authors')
// I'm not sure how your res is structured, but could look something like this
res.render('page', { articleInfo: post, authorInfo: author })
} catch (error) {
console.error(error.message)
}
// in case it was unable to fetch data
return null
}

Firebase firestore how to get reference data without additional request?

When I have fetched places that we can see in screenshot below, I need get their data, but it's reference to data, I can get the data without additional request like place.ref.data()? If I have 30 places, should I make 30 requests, really? I use react native and the ref object has type DocumentReference https://rnfirebase.io/docs/v3.1.x/firestore/reference/DocumentReference
You need to create your own populate method.
Here's one example I made where my collection eventSchedule has references to events, then I have the array of events and I want to get the events from each eventSchedule... That's how I did:
...
const docRef = db.collection("eventSchedule").doc(dayName);
try {
const doc = await docRef.get();
if (doc && doc.exists) {
const eventSchedule = doc.data();
if (eventSchedule.activeEvents) {
const activeEventsRefs = eventSchedule.activeEvents;
eventSchedule.activeEvents = [];
await activeEventsRefs.reduce(
async (promise: any, event: any) => {
await promise;
const childDoc = await event.get();
if (childDoc) {
eventSchedule.activeEvents.push(childDoc.data());
}
},
Promise.resolve()
);
}
} else {
logMessage += `No docs found for eventSchedule for today - ${dayName}`;
}
} catch (error) {
logMessage += `Error getting document ${JSON.stringify(error)}`;
}
So I used reducer to handle the promises. It works like a charm and it was the simplest "manual" way of doing this.
Also if you use React + Redux + Firebase, you can check the library react-redux-firebase that already implements a lot of stuff including the populate that is, similar to the MongoDB populate and the SQL Join:
http://react-redux-firebase.com/docs/populate.html

Categories