Im new to firebase and I am having trouble setting up the user profile route on my app. Im trying to make it so that the userId stored in Firestore can be retrieved and all the posts made by that specific user will be displayed on the user profile page. Please any help would be appreciated!
This is my Firestore database :
and this is what I have so far but it is not displaying all the posts associated with the user id
const express = require("express");
const router = express.Router();
const firestore = require("firebase/firestore");
const db = firestore.getFirestore();
router.get("/", (req, res) => {
res.send("No user id provided");
});
router.get("/:uid", (req, res) => {
const uid = req.params.uid;
const userPost = firestore.getDoc(firestore.doc(db, "reviews", uid));
userPost(uid, req.query && req.query.dev === "true")
.then((user) => {
return res.send(user);
})
.catch((e) => {
return res.send(e);
});
});
module.exports = router;
This line gets the single (probably non-existant) post at /reviews/:uid:
const userPost = firestore.getDoc(firestore.doc(db, "reviews", uid));
What you are looking to do is build a query for documents in the collection /reviews where docData.userId = uid.
const reviewsColRef = firestore.collection(db, "reviews");
const userPostsQuery = firestore.query(reviewsColRef, firestore.where("userId", "==", uid));
const userPostsPromise = firestore.getDocs(userPostsQuery);
userPostsPromise
.then((querySnapshot) => {
const postsArray = querySnapshot.docs.map(docSnap => {
const docData = { id: docSnap.id, ...docSnap.data() };
delete docData.userId; // it's the same for every post, may as well omit it
delete docData.email; // same as above
return docData;
});
res.json({ posts: postsArray, count: postsArray.length });
})
.catch((err) => {
// don't send the full error object to the client,
// instead you should log the error and send the
// client as little information as possible
console.error(`Failed to get user posts for firebase:${uid}`, err);
res.status(500).json({ error: err.code || err.message }); // Don't forget to use an appropriate status code!
});
Notes:
I recommend using destructuring to import the Firestore lib. Importing it as firestore negates the benefits of the modular SDK of only importing what you need.
import { getFirstore, query, collection, ... } from "firebase/firestore";
If this code is running on a server you control, consider switching to the Firebase Admin SDK instead as this allows you to bypass security rules and has relaxed rate limits.
import { getFirestore, query, collection, ... } from "firebase-admin/firestore";
As a side note, if an import from the SDK conflicts with a name you want to use elsewhere, you can rename it. You might see this when using the RTDB and storage together as they both export ref:
import { getDatabase, ref: rtdbRef } from "firebase/database";
import { getStorage, ref: storageRef } from "firebase/storage";
const userDataRef = rtdbRef(getDatabase(), "users", uid, "data");
const imgStorageRef = storageRef(getStorage(), "path/to/image.png");
Treat emails as sensitive data akin to a phone number. Nobody likes spam and limiting ways to rip emails from your database is good practice. Unless you are working on an internal tool for a corporate network, you should hide the user's email as much as possible.
Email addresses should not be stored with reviews. Instead keep a collection with documents for each user (e.g. document at /userProfile/:uid) and limit access to privileged users.
Consider adding a 404 Not Found error when a user doesn't exist.
Consider adding authentication to restrict access to your API.
Related
I am creating a messaging application and I am looking for a way to check to see if there already exists a conversation between people before a new one is created. Here is the implementation I currently have:
export const createConversation = async (req, res) => {
const conversation = req.body.conversation;
const message = req.body.message;
try {
//Check if conversation between participants exists
const newConversation = await Conversation.find({participants: conversation.participants});
if(newConversation != null) {
newConversation = new Conversation(conversation);
}
message.conversationId = newConversation._id;
const newMessage = new Messages(message);
await newConversation.save();
await newMessage.save();
res.status(201).json({conversation: newConversation, message: newMessage});
} catch (error) {
res.status(409).json({ message: error.message });
}
};
I want to check if there exists a conversation in MongoDB atlas where the participant list contains the same Ids as the conversation that is trying to be created. What is the most efficient way to implement this?
I need to create a REST API in my node app, that GET data from an external API - https://newsapi.org/v2/top-headlines?category=%7Bcategoryname%7D&apiKey=APIKEY
The condition is that this rest API should contain id of the user in DB.
Only when we trigger an API with valid userID, it should return response as the data coming from external API.
otherwise show error
Can you help me build a function that would do so?
I am using mongoDB
I am writing few snippets of code i wrote to accomplish this, but i am pretty bad at it. So any help on this will be highly appreciated:
app.js
router.get('/api/Users/news/:id', controller.newsget);
router.get('/api/Users/news/:id', (req, res) => {
const id = req.params.id;
for (let i = 0; i <User.length; i++) {
let user = User[i]
if (user.id === id) {
axios
.get('http://newsapi.org/v2/top-headlines?country=in&category=general&apiKey=36f3e29b704f41339af8439dc1228334')
.then(response => {
let userData = response.data;
res.send(userData);})
}
}
});
controller.js
exports.newsget = (req, res)=>{
if(!req.body){
return res
.status(400)
.send({ message : "Data to update can not be empty"})
}
const id = req.params.id;
User.findByIdAndUpdate(id, req.body, { useFindAndModify: false})
.then(data => {
if(!data){
res.status(404).send({ message : `Cannot Update user with ${id}. Maybe user not found!`})
}else{
res.send(data)
}
})
.catch(err =>{
res.status(500).send({ message : "Error Update user information"})
})
}
I have very little clue on the approach, but i badly need assistance. Please help
I have tried mimicking some online functions to search the user and then try to fetch data from external API if the user's ID was present in my DB. But it failed
First of all, you are writing two GET methods for same route, from what I am being concluding that, the first route should be POST according to what I am suspecting to be your functionality is depicted as follows
router.post('/api/Users/news/:id', controller.newsget);
router.get('/api/Users/news/:id', (req, res) => {
const id = req.params.id;
for (let i = 0; i <User.length; i++) {
let user = User[i]
if (user.id === id) {
axios
.get('http://newsapi.org/v2/top-headlines?country=in&category=general&apiKey=36f3e29b704f41339af8439dc1228334')
.then(response => {
let userData = response.data;
res.send(userData);})
}
}
});
Also if you are picking the logged in user, use req.user.id
I'm working on an e-commerce project. I'm trying to create a shopping cart within the app so that people don't accidentally access another user's data in the Mongo database. To do this, I tried setting up a variable as res.locals.cart. This didn't work because I found out from the docs that res.locals expires in each new page.
My next idea was to create an anonymous shopping cart each time app.js started and store it in the global app.locals object. This does work, and in the following code, you can see it returns the model of the shopping cart. But after that, it's undefined as soon as I refresh or go to a new page as seen by console.log. Why is it doing that? How can I make it so that my data stays across the whole app? And I need it to be a variable, so that it changes for each new user. If there are also any NPM packages that solve this problem, that would be helpful to know.
app.locals.cart = Cart.create({}, function (err, newCart) {
if (!err) {
console.log(newCart);
return newCart
}
});
app.get('/cart', function (req, res) {
console.log(app.locals.cart);
res.render('cart')
});
💡 This is not the best practive, but, if you still want to do it, than this is an example code you can see:
const express = require('express');
const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.locals.cart = [];
const item = {
id: 1,
name: 'Testing'
}
const addToCart = function(req, res, next) {
const { username } = req.body;
// req.body.username just for identifier
// you can change it with user id from token or token or whatever you want
if(typeof app.locals.cart[username] === 'undefined') {
app.locals.cart[username] = [];
}
// add item to cart by username / identifier
app.locals.cart[username].push(item);
next();
}
// or if you want to use this add to global, than you can use this middleware
// and now, you can see
// app.use(addToCart);
app.post('/', addToCart, (req, res, next) => {
// console.log
const { username } = req.body;
console.log(app.locals.cart[username])
res.send(app.locals.cart[username]);
})
app.get('/:username', (req, res, next) => {
const { username } = req.params;
console.log(app.locals.cart[username]);
res.send(app.locals.cart[username]);
})
app.listen(3000, () => {
console.log('Server is up');
})
I hope it's can help you 🙏.
I think the way you are trying is not a best practice.
Instead of using the locals try a different approach.
Creating a cart for each user in the database will be better.
You can link the card with the user. And whenever a user makes a request you fetch the cart from DB and do whatever you want.
To do that, you can add a user property to the Cart Schema. Whenever a user signs up, create a cart for it in the DB. When the user checkouts the cart, save the products in the card as another Document, let say an Order in the Orders Collection and clear the cart for future usage.
QUICK DIGEST:
Store any data from Mongoose onto a variable on your middleware and then have that variable read by app.locals or res.locals. The reason for this is because app.locals is changing and your middleware variable isn't, which lets it be read the same way every time. Example:
res.locals.data = middleware.var;
//or
app.locals.data = middleware.var;
Model.findById("model-id", function (err, noErr) {
if (!err) {
middleware.var = noErr //data retrieved from callback being passed into middleware.var
}
});
Here's what I discovered. My code didn't work because Express refuses to store data directly from a Mongoose function. I discovered this by console.logging the data inside the function and again outside it. I'm not sure why Express refuses to take data this way, but that's what was happening.
Cart.create({}, function (err, newCart) {
if (!err) {
app.locals.cart = newCart;
console.log(app.locals.cart);
//Returns Mongoose model
}
});
console.log(app.locals.cart);
//Now returns undefined
I solved this by storing the value to a variable in my middleware object called mids.
Here's the code on my app.js:
mids.anonymousCart();
app.use(function (req, res, next) {
res.locals.userTrue = req.user;
res.locals.cart = mids.cart;
next();
});
And here's the code on my middleware file:
var mids = {
anonymous_id: undefined,
cart: []
};
mids.anonymousCart = function () {
if (typeof mids.anonymous_id === 'undefined') {
Cart.create({}, function (err, newCart) {
if (!err) {
mids.anonymous_id = newCart._id.toString();
Cart.findById(mids.anonymous_id).populate('items').exec(function (err, cartReady) {
if (!err) {
mids.cart = cartReady;
}
});
}
});
}
}
What's happening here is that when app.js first starts, my middleware mids.anonymousCart is run. In my middleware, it checks that the id of Cart model stored as anonymous_id exists or not. It doesn't, so it creates the cart and stores the id. Since this is stored in the middleware and not Express, it can be accessed by any file linked to it.
I then turned the id I got back into a string, so that it could be read in the next function's findById. And if there's any items in the cart, Mongoose will then fill them in using the populate() method. But THE THING TO PAY ATTENTION TO is the way the data received in cartReady is stored in the middleware variable mids.cart, which is then read by res.locals.cart on app.js.
I use Node.js and back4app.com
I try to update the user object. Therefore I have read a lot and found this promissing documentation:
let progressId = "xyz";
let userId = "12354"; //aka objectId
const User = new Parse.User();
const query = new Parse.Query(User);
// Finds the user by its ID
query.get(userId).then((user) => {
// Updates the data we want
user.set('progressId', progressId);
// Saves the user with the updated data
user.save()
.then((response) => {
console.log('Updated user', response);
})
.catch((error) => {
console.error('Error while updating user', error);
});
});
But there also is a warning. It states:
The Parse.User class is secured by default, you are not able to invoke save method unless the Parse.User was obtained using an authenticated method, like logIn, signUp or current
How would this look like in code?
My solution
Well, I got it to work. While I figured it out, I have found some small show stoppers. I list it for anyone it may concern.
Thanks #RamosCharles I added the Master Key in Parse._initialize. Only with that .save(null, {useMasterKey: true}) works. Take notice, without null it also won't work.
That's my working code:
let progressId = "xyz";
const User = Parse.Object.extend('User'); //instead of const User = new Parse.User();
const query = new Parse.Query(User);
query.equalTo("objectId", '123xyz');
query.get(userId).then((userObj) => {
// Updates the data we want
userObj.set('progressId', progressId);
// Saves the user with the updated data
userObj.save(null, {useMasterKey: true}).then((response) => {
console.log('Updated user', response);
}).catch((error) => {
console.error('Error while updating user', error);
});
});
Now I'm wondering
why my working code is different from documentation?
how secure is my code? And what is to do to get it more secure?
Yes, their API Reference is very helpful! On this section, there's a "try on JSFiddle" button, have you already seen that?
To update a user object, you must use the Master Key. On the frontend, it's not recommended, and it's better to create a cloud code function and call it on your frontend. However, for test purposes, you can keep using the API Reference, but on JSFiddle, you need to do some changes, here is their sample code, but with the adjustments:
Parse.serverURL = 'https://parseapi.back4app.com';
Parse._initialize('<your-appID-here>', '<your-JSKey-here>', '<Your-MasterKey-here>');
const MyCustomClass = Parse.Object.extend('User');
const query = new Parse.Query(MyCustomClass);
query.equalTo("objectId", "<object-ID-here>");
query.find({useMasterKey: true}).then((results) => {
if (typeof document !== 'undefined') document.write(`ParseObjects found: ${JSON.stringify(results)}`);
console.log('ParseObjects found:', results);
}, (error) => {
if (typeof document !== 'undefined') document.write(`Error while fetching ParseObjects: ${JSON.stringify(error)}`);
console.error('Error while fetching ParseObjects', error);
});
You'll need to insert the "_" before the "initialize" in your "Parse._initialize" and insert the Master Key in your query as I did on the query.find.
I'm new to Node.js and Express.js and I'm wondering how to return different results with the same query based on different logics. Here is my code:
const { Student } = require('../mongoose-models/student');
const express = require('express');
const router = express.Router();
router.get('/api/students/', async (req, res) => {
const query = req.query;
const studentsList = await Student.find(query);
if (!studentsList) return res.status(404).send('No student found.');
res.send(studentsList);
});
module.exports = router;
Now, if I go to http://localhost:3000/api/students/age=20 in my browser, then it will return a list of all students that are exactly 20 in a json format (the student data is stored in MongoDB). Now, I want to implement another function that will return a list of students younger than 20 when specifying age=20. Is this possible to add this logic within the same block and how can I do it?
In express for same GET request with dynamic parameter we have
router.get('/api/students/:dynamicVariable', async (req, res) => {
// you can pass multiple parameter in url
const query = req.params.dynamicVariable
// for access then use req.params.paramsNameInUrl with ":" in from of variable
})
This is wrong way to query from database But it help for big logic
router.get('/api/students/query=:Query', async (req, res) => {
const query = req.params.Query;
const studentsList = await Student.find(query);
if (!studentsList)
return res.status(404).send('No student found.');
res.send(studentsList);
});
Your query in Postman or url
www.your-domin.com/api/students/query={"age": 20 }
// or query based on your requirement
query={"status":true}
query={"age": { $gt: 20 } }
query={"age": {$nin: [20, 30, 13]}}
use GET Request if using Post