I have a general question about DB transactions and specifically mongo DB.
Is the following code safe in terms of read and write operations?
import { MongoClient, ObjectId } from "mongodb";
const CONNECTION = "mongodb+srv://...";
const client = new MongoClient(CONNECTION);
const con = await client.connect();
const session = con.startSession();
try {
await session.withTransaction(
async () => {
const _id = new ObjectId("...");
const col = con.db("purchases").collection("collection");
// READ
const purchase = await col.findOne({ _id }) as any;
if (purchase.test) {
// WRITE
await col.updateOne({ _id }, { $set: { test: false } });
}
},
{ readConcern: "majority", writeConcern: { w: "majority" } }
);
} finally {
await session.endSession();
await client.close();
}
I know that I can also achieve this functionality using findOneAndUpdate but I'm interested in the transactions specifically.
Related
I'm new to Redis and can't find any good docs on querying database for specific value and then sort it.
I'm trying to get data from a database, I'm using NextJs & Redis-om
I tried doing this in my /lib/redis.js
import { Client, Entity, Repository, Schema } from "redis-om";
const client = new Client();
const connect = async () => {
if (!client.isOpen()) {
await client.open(process.env.NEXT_PUBLIC_REDIS_URL);
}
};
class Score extends Entity {}
const schema = new Schema(
Score,
{
address: { type: "string" },
score: { type: "string", textSearch: true },
},
{ dataStructure: "JSON" }
);
export const createScore = async (data) => {
await connect();
const repository = client?.fetchRepository(schema, client);
const score = repository.createEntity();
score.score = data.score;
score.address = data.address;
const id = await repository.save(score);
return id;
};
export const createIndex = async () => {
await connect();
const repository = client.fetchRepository(schema, client);
await repository.createIndex();
};
export const searchScore = async (query) => {
await connect();
const repository = client.fetchRepository(schema, client);
const scores = await repository.search().where("address").matches(query);
return scores;
};
And fetching data as
const router = useRouter();
const data = router.query;
const query = Object.keys(data)[0];
const fetchScores = async () => {
const res = await fetch("/api/search?" + query);
const results = await res.json();
// console.log(results);
// setScore(results[])
};
fetchScores();
To simplify this, I'm passing an address that was meant to check database and if any address is found matching to that query address, return it.
The issue is that you've modeled your address incorrectly.
When you set: address: { type: "string" } you set address up as a TAG field type in RediSearch, which does not allow full-text searches, instead you want to set address to a text type address: { type: "text" } which will support the full-text search capabilities of RediSearch.
I have a Data Table i want to delete every document inside collection before invoke loadCheckOut.
How can i dow that with latest JS Syntax.
I am using React JS, and it initilize DB from getDb() method so methods like db.collection() not work on it i want a complete moduler solution
const loadCheckout = async (priceId) => {
//before adding we must delete existing collection
const docRef_x = collection(db, `customers/${user.uid}/checkout_sessions`);
const snapshot = await getDocs(docRef_x);
const x = await deleteDoc(snapshot);
const docRef = await addDoc(
collection(db, `customers/${user.uid}/checkout_sessions`),
{
price: priceId,
success_url: window.location.origin,
cancel_url: window.location.origin,
}
);
const ref = collection(db, `customers/${user.uid}/checkout_sessions`);
const snap = onSnapshot(
ref,
{ includeMetadataChanges: true },
async (doc) => {
var error = null,
sessionId = null;
var first = true;
doc.forEach((ele) => {
if (first) {
error = ele.data().error;
sessionId = ele.data().sessionId;
first = false;
}
});
console.log(sessionId);
if (error) {
alert(error);
}
if (sessionId) {
const stripe = await loadStripe(stripe_public_key);
stripe.redirectToCheckout({ sessionId });
}
}
);
};
This won't work:
const snapshot = await getDocs(docRef_x);
const x = await deleteDoc(snapshot);
The deleteDoc function requires a single DocumentReference, while your snapshot is a QuerySnapshot. This has very little to do with the change in syntax, as snapshot.delete() also wouldn't have worked in v8 of the SDK and earlier.
To delete the documents in the query snapshot, loop over the results and delete them one by one:
snapshot.forEach((doc) => {
deleteDoc(doc.ref);
});
I am synchronising clocks in two JavaScript clients by writing to Firestore every second and then subscribing the second remote client / "slave" to the document.
This works fine and I can read the document changes in real time based on this method of creating the document reference:
const useCloudTimer = (isMaster, opponentCCID, userCCID) => {
const dispatch = useDispatch();
const { localRemainingTimeMS, clockTimeOut } = useSelector((state) => state.clock);
const timerRef = useRef(null);
useEffect(() => {
const db = firebase.firestore();
timerRef.current = db.collection('timers').doc(`${isMaster
? userCCID + opponentCCID
: opponentCCID + userCCID
}`);
dispatch(clock(CLOCK, { cloudTimerDbRef: timerRef.current }));
}, []);
const pushTimer = async (duration) => {
try {
await timerRef.current.set({ duration });
} catch (error) {
logger.error(error, 'cloud timer');
}
};
useEffect(() => { if (isMaster) pushTimer(localRemainingTimeMS); }, [localRemainingTimeMS]);
const getTimer = async () => {
try {
const unsubscribeRemoteTimer = await timerRef.current.onSnapshot((doc) => {
if (!clockTimeOut && doc.exists) {
const duration = Number(doc.data().duration);
dispatch(clock(CLOCK, { remoteRemainingTimeMS: duration }));
}
});
if (clockTimeOut) unsubscribeRemoteTimer().then((arg) => console.log('unsubscribeRemoteTimer', arg));
} catch (error) {
logger.error(error, 'getTimer');
}
};
useEffect(() => { if (!isMaster) getTimer(); }, []);
};
export default useCloudTimer;
The problem is when I want to delete the document. If the client that did not create the document tries to delete it, what happens is that a new document is created for a split second with the same name, and then that one is deleted. Here is the exact moment this happens where you can see two documents with the same name:
A document is green when it's being written to and red when it's being deleted.
My document ref is being stored in redux and then used via the store when required:
export const deleteCloudTimer = async (timerRef) => {
if (timerRef) {
try {
await timerRef.delete();
} catch (error) {
logger.error(error, 'Error removing document: ');
}
}
};
How can my Firebase client app delete a document if it didn't create it?
Tasks requiring precision timing especially with high amounts of reads/writes are not suggested for firestore. You might consider an alternative GCP product instead?
currently I am working on a app but struggling to find since last two weeks the following:
I have react native iOS app with RN-iap for subscription.. and would like to implement receipt verification via cloud function at firebase.
I found a similar solution but its with SWIFT: https://www.loopwerk.io/articles/2020/storekit-webhooks-firestore/
can anybody please help me convert the code (swift below) into React Native ? really appreciate
or if any suitable example or lines please.
(I am using React native firebase).
I can able to fetch receipt and save in Firestore collection. Thanks in advance.
below are the codes:
FRONT END CALLING Cloud function
import Firebase
import FirebaseFunctions
import Foundation
final class CloudFunction {
private lazy var functions = Functions.functions()
func validateReceipt(receipt: String, completionHandler: #escaping () -> Void) {
let parameters = ["receipt": receipt]
functions.httpsCallable("validateReceipt").call(parameters) { _, error in
if let error = error {
print(error)
}
completionHandler()
}
}
}
Cloud Function for above:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const fetch = require('node-fetch');
const db = admin.firestore();
const runtimeOpts = {
memory: '1GB',
};
function validateAndStoreReceipt(url, options, userSnapshot) {
return fetch(url, options).then(result => {
return result.json();
}).then(data => {
if (data.status === 21007) {
// Retry with sandbox URL
return validateAndStoreReceipt('https://sandbox.itunes.apple.com/verifyReceipt', options, userSnapshot);
}
// Process the result
if (data.status !== 0) {
return false;
}
const latestReceiptInfo = data.latest_receipt_info[0];
const expireDate = +latestReceiptInfo.expires_date_ms;
const isSubscribed = expireDate > Date.now();
const status = {
isSubscribed: isSubscribed,
expireDate: expireDate,
};
const appleSubscription = {
receipt: data.latest_receipt,
productId: latestReceiptInfo.product_id,
originalTransactionId: latestReceiptInfo.original_transaction_id
};
// Update the user document!
return userSnapshot.ref.update({status: status, appleSubscription: appleSubscription});
});
}
exports.validateReceipt = functions.runWith(runtimeOpts).https.onCall(async (data, context) => {
if (!context.auth) {
throw new functions.https.HttpsError('permission-denied', 'The function must be called while authenticated.');
}
if (!data.receipt) {
throw new functions.https.HttpsError('permission-denied', 'receipt is required');
}
// First we fetch the user
const userSnapshot = await db.collection('users').doc(context.auth.uid).get();
if (!userSnapshot.exists) {
throw new functions.https.HttpsError('not-found', 'No user document found.');
}
// Now we fetch the receipt from Apple
let body = {
'receipt-data': data.receipt,
'password': 'MY_SECRET_PASSWORD',
'exclude-old-transactions': true
};
const options = {
method: 'post',
body: JSON.stringify(body),
headers: {'Content-Type': 'application/json'},
};
return validateAndStoreReceipt('https://buy.itunes.apple.com/verifyReceipt', options, userSnapshot);
});
continuation another cloud function:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const db = admin.firestore();
const runtimeOpts = {
memory: '1GB',
};
exports.appleWebhook = functions.runWith(runtimeOpts).https.onRequest(async (req, res) => {
// Only allow POST requests
if (req.method !== 'POST') {
return res.status(403).send('Forbidden');
}
// Check for correct password
if (req.body.password !== 'MY_SECRET_PASSWORD') {
return res.status(403).send('Forbidden');
}
const receipt = req.body.unified_receipt.latest_receipt_info[0];
// Find the user with this stored transaction id
const userQuerySnapshot = await db.collection('users')
.where('appleSubscription.originalTransactionId', '==', receipt.original_transaction_id)
.limit(1)
.get();
if (userQuerySnapshot.empty) {
throw new functions.https.HttpsError('not-found', 'No user found');
}
const expireDate = +receipt.expires_date_ms;
const isSubscribed = expireDate > Date.now();
const status = {
isSubscribed: isSubscribed,
expireDate: expireDate,
};
const appleSubscription = {
receipt: req.body.unified_receipt.latest_receipt,
productId: receipt.product_id,
originalTransactionId: receipt.original_transaction_id,
};
// Update the user
return userQuerySnapshot.docs[0].ref.update({ status: status, appleSubscription: appleSubscription }).then(function() {
return res.sendStatus(200);
});
});
I have a graphql query that shows me a list of users, when I update the list with a mutation, the state change well, but when I change my route and I come back to the old one, it return the old state.I have to do a hard refresh on my browser to have the new list.
This is my query :
export const group = (id) => {
const data = Client.query({
query: gql`
query group($id: Int) {
group(_id: $id) {
_id
name
lat
lng
is_private
creation_date
}
}
})
This is my component :
async componentWillMount() {
try {
var data = await group(632);
var result = data.data.group[0];
this.setState({
group: result
});
} catch (e) {
console.log(e);
}
}
updateGroup = async() => {
try {
var data = await groupUpdate(501, 632, {
name: this.state.group.name,
is_private: this.state.group.is_private,
address: this.state.group.address,
creation_date: this.state.group.creation_date,
nbrEmployees: this.state.group.nbrEmployees
});
notifyUser(NOTIFY.success, "Done");
this.toggleInfo();
} catch (e) {
notifyUser(NOTIFY.error, "Error Serveur");
}
}
Any help please ?