Strapi: query rooms with and without user ID - javascript

I want to return some default data along with the authenticated data. So basically I have a table called rooms with two columns user and owner_id with relation to another table. I have a few data which does not have any owner_id associated with it. Those are my default data and I want to always send them and then from within my app I can create custom room which will have owner_id associated with it and for those I want to return only the authenticated users data along with the default one. But my problem is either I can send only the authenticated data or all of the data from default to all authenticated users data. Giving the code and screenshots please help.
async find(ctx) {
const owner_id = ctx.state.user.id;
const dname = await strapi.services.rooms.find();
const name = await strapi.services.rooms.find({owner_id})
return sanitizeEntity(name, { model: strapi.models.rooms });},

Assuming you are using PostGreSQL with strapi, you can easily fetch the rooms which have owner linked and the default by using strapi.query instead of strapi.services
async find(ctx) {
const resp = await strapi
.query('rooms')
.model.query(function (qb) {
if (ctx.state.user) qb.where('owner_id', ctx.state.user.id);
qb.orWhere('owner_id', null);
})
.fetchAll();
const data = resp.toJSON();
return data
}

Related

How do I query a collection of firebase documents by a properties value?

I am a building a Firebase React social media app with a search bar, I want my searchbar to show suggestions of users based on the value of input. If I type "F" I expect Foo and then underneath Bar, pretty much like any social media search bar that filters the users and returns the relevant ones. . I am having trouble understanding the Firebase queries and what would be appropriate for this.
The Layout of the DB is
users collections
documents that represent a user
a username property on the document
const searchUser = async (text) => {
const queryUsers = [];
if (text !== '') {
try {
const usersRef = collection(firestore, "users");
const q = query(usersRef, orderBy("username"),startAt(text.toLowerCase()),limit(5))
const querySnapshot = await getDocs(q)
querySnapshot.forEach((doc) => {
queryUsers.push(doc.data())
})
} catch (error) {
console.error(error);
}
}
console.log(queryUsers);
return queryUsers;
};
I tried all sorts of queries but none of them worked, I am expecting to get all the users ordered by the value of the string that was sent to the query
Found the issue, the username was saved with a Capital letter. Not sure if this query is also the best one.

How to create a nested collection when creating a user in Firebase / Firestore where users can save bookmarked items

I want to be able to have a nested collection in firebase/firestore where I can save an authenticated users favorites. I was trying to create the collection when the user is created so I can just read/write to it later but I can't figure out how to create the collection. I have something like this:
//This function creates a new user. If the user already exists, no new document will be created
export const createUserDocumentFromAuth = async (
userAuth,
additionalInfo = {}
) => {
if (!userAuth) return;
const userDocRef = doc(db, 'users', userAuth.uid); //database instance, collection, identifier
const bookmarkRef = doc(db, 'users', userAuth.id, 'bookmarks'); //This triggers error
const userSnapshot = await getDoc(userDocRef);
if (!userSnapshot.exists()) {
//If user snapshot doesn't exist - create userDocRef
const { displayName, email } = userAuth;
const createdAt = new Date();
try {
await setDoc(userDocRef, {
displayName,
email,
createdAt,
...additionalInfo,
});
setDoc(bookmarkRef, { //Try to create a bookmarks collection here
favorites: []
})
} catch (error) {
console.log('Error creating user', error.message);
}
}
//if user data exists
return userDocRef;
};
I can create the user just fine but not another collection at the same time. I also tried just creating the collection when a signed-in user clicks on the bookmark button like this but I get a type error in both cases Uncaught (in promise) TypeError: n is undefined every time.
export const addBookmarkForUser = async (userAuth, showId) => {
const bookmarkRef = doc(db, 'users', userAuth.id, 'bookmarks');
try {
await setDoc(bookmarkRef, {
favorites: showId
});
}catch(error){
console.log('error creating bookmark', error.message)
}
};
I'm pretty new to Firebase / Firestore and all I want is to be able to save an item id in an array for an individual user when they click a button. If saving in an array is not ideal or there is any better way to do this, I am open to any suggestions at this point.
I was trying to create the collection when the user is created so I
can just read/write to it later but I can't figure out how to create
the collection.
A (sub)collection is only created when you create the first document in it. There is no way to materialize an empty collection without a document.
And it is normal that you get an error when using the doc() method as follows
const bookmarkRef = doc(db, 'users', userAuth.id, 'bookmarks');
because this method is used to create a DocumentReference and therefore you need to pass a path with an even number of path segments. In you case you pass 3 segments.
You could very well define the CollectionReference for the bookmarks subcollection as follows, using the collection() method and passing the 3 segments
const bookmarkRef = collection(db, 'users', userAuth.id, 'bookmarks');
but, until you add a document in it, it will not exist in the database.
Conclusion: You will automatically create the user's bookmarks subcollection the first time you create a bookmark for the user.
For example:
const bookmarksCollectionRef = collection(db, 'users', userAuth.id, 'bookmarks');
await bookmarksCollectionRef.add({ ... })

Firebase Query and how to read the output

Above is my Js code
I have a database that has information regarding the location of an apartment, I am trying to search for a specific property to see if it exists in the database. The user will be able to key into the search box to perform the search.
"propertiesRef" is used to store the user input.
I tried storing the data into "q" that I received from querying the database. But I have no idea how to read the result.
This is the console log for "q", but I don't quite understand the information that is shown, I want to know which output in the console should I be looking at and how do I access them?
The query() function just creates an instance Query. You need to use getDocs() function to actually fetch data from Firestore.
const search = (property) => {
const propertiesRef = collection(db, "flats-table");
const q = query(propertiesRef, where("name", "==", property))
return getDocs(q).then((qSnap) => {
const data = qSnap.docs.map(d => ({ id: d.id, ...d.data() }))
console.log(data);
return data;
})
// or use async-await
// const qSnap = await getDocs(q);
}
Checkout the documentation for more examples.

Show user invoices for simultaneously logged in users using Expressjs

I have created a simple invoice application using the MERN stack. The application is great at handling data for the logged in user as long as one user is logged in, but if another user logs in then the invoices for the user that first logged in is shown.
I am currently using app.set() and app.get() to pass data between endpoints and send to my frontend client. Auth0 handles the authentication layer, but would express-session solve this issue? And if it is how would I go about implementing this? Or is there a better solution?
Below is the code that sends the invoices currently to the frontend:
var express = require('express');
var app = express();
var userInvoices = express.Router();
const axios = require('axios');
const InvoiceModel = require('../models/Invoice');
const UserModel = require('../models/User');
//Functions//
async function clientCall() {
const url = `${process.env.REACT_APP_SAVE_USER}`;
const axiosConfig = {
method: 'get',
url
};
await axios(axiosConfig).catch((e) => {console.log(e)});
};
async function fetchUsersFromDB() {
const usersFromDB = await UserModel.find().catch((e) => {console.log(e)});
return usersFromDB;
};
async function saveUser(User) {
const condition = {email: User.email};
const query = {
nickname: User.nickname,
name: User.name,
picture: User.picture,
email: User.email,
email_verified: User.email_verified,
sub: User.sub,
};
const options = { upsert: true };
const update = await UserModel.updateMany(condition, query, options).catch((e) => {console.log(e)});
// Log the amount of documents that where updated in the DB.
if(update.nModified > 0 ) {
console.log('Number of Users added or updated to DB:', update.nModified)
}
};
function findCommonUser(dbUser, User) {
if(dbUser.length <= 0) {
UserModel.create(User, () => {console.log('Users saved to database')});
console.log('An Error has occured with Database')
} else {
dbUser.forEach((DBElement) => {
if(User.email !== DBElement.email) {
saveUser(User);
}
})
}
console.log(' Users match')
};
function matchUserAndInvoice(dbInvoices, loggedInUser) {
let newArray = [];
dbInvoices.forEach(async (DBElement) => {
if(DBElement.customer_email === loggedInUser.email){
newArray.push(DBElement);
app.set('userInvoices', newArray);
}
})
}
// prevents user from having to refresh to get data.
clientCall();
userInvoices.post('/saveUser', async (req, res) => {
try {
const User = req.body;
const usersFromDB = await fetchUsersFromDB().catch((e) => {console.log(e)});
findCommonUser(usersFromDB, User);
app.set('Users', User)
} catch (error) {
console.log(error)
}
})
userInvoices.get('/fetchUserInvoices', async (req,res) => {
try {
const invoices = await InvoiceModel.find().catch((e) => {console.log(e)});
const user = await app.get('Users');
await matchUserAndInvoice(invoices,user);
const userInvoices = await app.get('userInvoices')
res.json(userInvoices);
} catch (error) {
console.log(error)
}
});
;
module.exports = userInvoices;
app.get() is essentially global to your server instance so putting data there to use between requests for individual users will (as you have discovered) get data confused between different users as all users are trying to store data in the same place.
The usual way to solve a problem like this is to use express-session. This cookies the end-user's connection the first time they connect to your server and then creates a server-side object that is automatically associated with that cookie. Then, inside of any request handler, you can read or set data in req.session and that data will uniquely belong to just that user.
If the user changes devices or clears their cookies, then the association with the session object for that user will be lost (creating a new session object for them upon their next connection). But, if you have a persistent data store and you use some sort of login that allows you to populate the session from the user's persistent store, you can even make the session persistent across devices for the same user (though often times this is not required).
In the specific application you describe in your question (tracking invoices), it seems like your invoice data should be tagged with a specific user when it is stored in your database and then any future requests to display invoices should query based on the particular user that is making the request. This requires a login and login cookie so that you can uniquely identify each user and thus show them only the invoices that pertain to them.
The above-described session object should only be used for temporal session state, not for persistent storage such as invoices. If your server implementation is truly RESTFUL, you may not even need any data stored in the session object other than user's logged in userID.

Create a firestore doc for each auth user in Nextjs (only using sign in with Google)

I am building a user auth system with Nextjs
I am trying to create a document within firestore for each user in my firebase authentication system. I was easily able to do this in previous projects when creating an account with email and password but with the 'sign in with google' feature I can't seem to figure out how.
I don't want to create a new document every time the user logs in..
My only idea is this:
When user signs in, loop through all firestore documents and see if the users e-mail matches any firestore doc email. If not, create document, else return.
I feel like there is another way though..
Simplest way would be to make a custom hook that can be used anywhere across the application.
First in the _app file inside useeffect hook simply try to get the data from doc if data exist well it means user document is already there and if data does not exists, we need to create a document for that, quite simple. Let's see the code now
Make sure you read comments written inside the code to better understand
In _app.js,
useEffect(async () => {
// now this checks if user is logged in or not
const unsubscribe = auth.onAuthStateChanged(async (userAuth) => {
if (userAuth) {
// if logged in it simply passes the userAuth object to handle user profile
// which is a custom hook to check if document for this user pre-exist or not!
// if there wont be any document it will go and create a document and return
// that document.
// If already there is a document created it will simply return that.
const userRef = await handleUserProfile(userAuth);
userRef.onSnapshot((snapshot) => {
// later you can save currentUsr value in any of the state to use it later
const currentUsr = {
id: snapshot.id,
...snapshot.data(),
};
}
});
}
}
});
return () => unsubscribe();
}, []);
Now the custom hook to check if document is already there or not, here comes the tricky part.
export const handleUserProfile = async (userAuth) => {
// as a second check it check if falsy values are returned
if (!userAuth) return;
const { uid } = userAuth;
// first it tries to get data from that uid
const userRef = firestore.doc(`users/${uid}`);
const snapshot = await userRef.get();
// checks if snapshot exist
if (!snapshot.exists) {
// if snapshot does not exist, it will simply create a document with document
// name as this 'uid'
const { displayName, email } = userAuth;
const timeStamp = new Date();
try {
// making use of same userRef that we created above to create
await userRef.set({
displayName,
email,
createdAt: timeStamp,
});
} catch (error) {}
}
// if snapshot exist it will simply return the userRef which contains the
// document only.
return userRef;
};
Voila! :)
There is no reason why you should not use the onAuthStateChanged event on auth. A write would cost you the same as a read to check if the data is already there. But with a read you would sometimes need also a write. In total only writes every time come less expensive in read/write actions.
Just listen to auth state changes and update your firestore data each time it changes:
firebase.auth().onAuthStateChanged(async (user) => {
if (user) {
await firebase.firestore()
.collection("users")
.doc(user.uid)
.set(data, {merge:true});
// User is signed in.
}
});
Make sure to use set with merge turned on. That will ensure that the data will be created if it doens't exist and update only the field you want to update.
Also make sure to store the data under the user uid. With that you ensure that each user has an unique idenfier. It is a bad practice to store users under the email. One of the reasons for that is that emails could have chars that are not supported as keys so would need to remove those when saving and add them again when reading the keys.
Firestore won't create duplicate docs if created when signing in with Google.. so this works:
const signInWithGoogle = () => {
fire
.auth()
.signInWithPopup(google_provider)
.then((result) => {
/** #type {firebase.auth.OAuthCredential} */
var credential = result.credential;
// This gives you a Google Access Token. You can use it to access the Google API.
var token = credential.accessToken;
// The signed-in user info.
var user = result.user;
// ...
})
.catch((error) => {
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
// The email of the user's account used.
var email = error.email;
// The firebase.auth.AuthCredential type that was used.
var credential = error.credential;
// ...
})
// CREATE USER DATA IN FIRESTORE
.then(async () => {
const data = {
//ADD DATA HERE
};
await fire
.firestore()
.collection("users")
.doc(fire.auth().currentUser.email)
.set(data);
});
};

Categories

Resources