Mongoose Aggrigate - mongodb

I have seen so many similar questions and answers, but and I'm a bit confused.
How can I achieve this with aggregate in mongoose?
const userId = "23523891729417419";
const students = await Students.find({ active: true });
const courses = await Courses.find({ userId: userId });
const result = students.filter((s) => {
const found = courses.find((c) => c.someId === s.someOtherId);
if (!found) return s;
});
There is one field (with differnet names) in both models that on some documents the value of them might be equal. I want to exclude them from result.
students and courses are just dummy names. and might mess with the question.

Related

Query many-to-many relationship in Prisma

I want to query a many-to-many relation in prisma, like "select all posts where the category ID equals 'abc...' ".
This seems fairly simple but even after spending 2 hours reading the Prisma docs on relational queries, I can't figure it out.
model Category {
id String #id #default(cuid())
name String
post Post[]
}
model Post {
id String #id #default(cuid())
body String
category Category[]
}
const posts = await prisma.post.findMany({
select: {
category: {
where: {id: "abc123"}
}},
});
this returns an array of as many category objects as there are posts.
This will return all posts which have the category of id abc123. Note that posts may include categories other than id abc123.
const posts = await prisma.post.findMany({
where: {
category: {
some: {
id: 'abc123',
},
},
},
});
some: Returns all records where one or more ("some") related records match filtering criteria.
https://www.prisma.io/docs/reference/api-reference/prisma-client-reference#some

Firestore function saving entire document data to Algolia

I have created a Function which is saving the Entire Document to Algolia when onCreate function is triggered. I want to save only three fields and not the entire document.
Here is my current function code:
exports.addToIndex = functions.firestore.document('questions/{questionsId}')
.onCreate((snapshot: { data: () => any; id: any; desc:any; }) => {
const data = snapshot.data();
// const descdata = snapshot.data().desc;
const objectID = snapshot.id;
console.log(objectID);
// console.log(descdata);
console.log(data);
// return index.saveObject({ ...descdata, objectID });
return index.saveObject({ ...data, objectID });
});
I only want to save three objectid's:
ObjectID
slatex
alatex
At present, it is saving all 18 fields of the document. How can I do this?
The data may carry the entire firestore document.
For limited fields, I usually frame the index data separately; like below
exports.addToIndex = functions.firestore.document('questions/{questionsId}')
.onCreate((snapshot) => {
const data = snapshot.data();
const objectID = snapshot.id;
let _index_data = {
'objectID': objectID,
'slatex': data.slatex,
'alatex': data. alatex
}
return index.saveObject(_index_data);
});

How to trigger a mongoose updatedAt

I need to update my model when the data has changed. Sadly, this seems to not work.
const mongoose = require('mongoose');
const moment = require('moment');
const Schema = mongoose.Schema;
const SomeSchema = new Schema({
query: String,
data: Object,
userId: String,
// Date.now() does work. I'm working with existing code.
createdAt: { type: Date, default: moment().format() },
updatedAt: { type: Date, default: moment().format() }
});
// Not sure why I need this 😕
// Have also used 'save' instead of 'updateOne'
SomeSchema.pre('updateOne', function(next) {
this.updated = Date.now();
// this.updatedAt = Date.now() does not work either.
return next();
});
mongoose.model('someModel', SomeSchema);
Actual usage:
const mongoose = require('mongoose');
const Model = mongoose.model('someModel');
// Ideally, I wanted something like "Model.findOrCreate" but... cant see that
const obj = {..};
// Im happy nothing will error here with this.
// Would love to use "findOrCreate" instead.
const data = await Model.updateOne({ ...obj });
// I hate this so much... by hey.
if (data.n === 0) {
// Create
Model.create({...obj}).save
}
All Im saying is, if the data is there, update it and if not, create it. But my updatedAt key is not updating at all. It stays the same as the createdAt. Based on the docs, I dont see how I'd use $set here.
The main thing is to trigger updatedAt whenever the data was found.
Script example using MongoDB Atlas Triggers:
exports = function(changeEvent) {
const { updateDescription, fullDocument, ns } = changeEvent;
const updatedFields = Object.keys(updateDescription.updatedFields);
// For debug
//console.log('changeEvent', JSON.stringify(changeEvent));
const isUpdated = updatedFields.some(field =>
field.match(/updatedAt/)
);
const updatedAt = fullDocument.updatedAt;
// Prevent update again after the update
if (!isUpdated || !updatedAt) {
const { _id } = fullDocument;
console.log(`Triggered! ${ns.db}:${ns.coll}:${_id}, isUpdated:${isUpdated ? 'true' : 'false'}, updatedAt:${updatedAt}`);
const mongodb = context.services.get(ns.db /* Cluster Name, like the DB name */);
const collection = mongodb.db(ns.db).collection(ns.coll);
collection.updateOne({
_id: _id,
}, {
$set: {
updatedAt: new Date(),
}
});
}
};
Looks like there is a typo in the Pre middleware function. Based on our Schema the key name is updatedAt, but in the function, it's mentioned as updated.

Can't find subdocument by _id

I need to retrieve a subdocument by id. My abbreviated schema is:
OfferSchema.add({
counterOffers: {
type: [OfferSchema]
},
...
});
I've tried a few different methods:
const offer = await this.offerModel.findById(parentID).select('_id counterOffers');
const counterOffer = offer.counterOffers.find(obj => obj._id === _id);
// and also
const counterOffer = offer.counterOffers.find(obj => obj._id === Mongoose.Types.ObjectId(_id));
// returns undefined for counterOffer
also:
const counterOffer = await this.offerModel.findOne({ 'counterOffers._id': _id });
// returns the parent object, not the subdocument
but neither will return the subdocument. I have tripe-checked that my IDs are correct.
What am I doing wrong?

Firestore unique index or unique constraint?

Is it possible in Firestore to define an index with a unique constraint? If not, how is it possible to enforce uniqueness on a document field (without using document ID)?
Yes, this is possible using a combination of two collections, Firestore rules and batched writes.
https://cloud.google.com/firestore/docs/manage-data/transactions#batched-writes
The simple idea is, using a batched write, you write your document to your "data" collection and at the same write to a separate "index" collection where you index the value of the field that you want to be unique.
Using the Firestore rules, you can then ensure that the "data" collection can only have a document written to it if the document field's value also exists in the index collection and, vice versa, that the index collection can only be written to if value in the index matches what's in the data collection.
Example
Let's say that we have a User collection and we want to ensure that the username field is unique.
Our User collection will contain simply the username
/User/{id}
{
username: String
}
Our Index collection will contain the username in the path and a value property that contains the id of the User that is indexed.
/Index/User/username/{username}
{
value: User.id
}
To create our User we use a batch write to create both the User document and the Index document at the same time.
const firebaseApp = ...construct your firebase app
const createUser = async (username) => {
const database = firebaseApp.firestore()
const batch = database.batch()
const Collection = database.collection('User')
const ref = Collection.doc()
batch.set(ref, {
username
})
const Index = database.collection('Index')
const indexRef = Index.doc(`User/username/${username}`)
batch.set(indexRef, {
value: ref.id
})
await batch.commit()
}
To update our User's username we use a batch write to update the User document, delete the previous Index document and create a new Index document all at the same time.
const firebaseApp = ...construct your firebase app
const updateUser = async (id, username) => {
const database = firebaseApp.firestore()
const batch = database.batch()
const Collection = database.collection('User')
const ref = Collection.doc(id)
const refDoc = await ref.get()
const prevData = refDoc.data()
batch.update(ref, {
username
})
const Index = database.collection('Index')
const prevIndexRef = Index.doc(`User/username/${prevData.username}`)
const indexRef = Index.doc(`User/username/${username}`)
batch.delete(prevIndexRef)
batch.set(indexRef, {
value: ref.id
})
await batch.commit()
}
To delete a User we use a batch write to delete both the User document and the Index document at the same time.
const firebaseApp = ...construct your firebase app
const deleteUser = async (id) => {
const database = firebaseApp.firestore()
const batch = database.batch()
const Collection = database.collection('User')
const ref = Collection.doc(id)
const refDoc = await ref.get()
const prevData = refDoc.data()
batch.delete(ref)
const Index = database.collection('Index')
const indexRef = Index.doc(`User/username/${prevData.username}`)
batch.delete(indexRef)
await batch.commit()
}
We then setup our Firestore rules so that they only allow a User to be created if the username is not already indexed for a different User. A User's username can only be updated if an Index does not already exist for the username and a User can only be deleted if the Index is deleted as well. Create and update will fail with a "Missing or insufficient permissions" error if a User with the same username already exists.
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Index collection helper methods
function getIndexAfter(path) {
return getAfter(/databases/$(database)/documents/Index/$(path))
}
function getIndexBefore(path) {
return get(/databases/$(database)/documents/Index/$(path))
}
function indexExistsAfter(path) {
return existsAfter(/databases/$(database)/documents/Index/$(path))
}
function indexExistsBefore(path) {
return exists(/databases/$(database)/documents/Index/$(path))
}
// User collection helper methods
function getUserAfter(id) {
return getAfter(/databases/$(database)/documents/User/$(id))
}
function getUserBefore(id) {
return get(/databases/$(database)/documents/User/$(id))
}
function userExistsAfter(id) {
return existsAfter(/databases/$(database)/documents/User/$(id))
}
match /User/{id} {
allow read: true;
allow create: if
getIndexAfter(/User/username/$(getUserAfter(id).data.username)).data.value == id;
allow update: if
getIndexAfter(/User/username/$(getUserAfter(id).data.username)).data.value == id &&
!indexExistsBefore(/User/username/$(getUserAfter(id).data.username));
allow delete: if
!indexExistsAfter(/User/username/$(getUserBefore(id).data.username));
}
match /Index/User/username/{username} {
allow read: if true;
allow create: if
getUserAfter(getIndexAfter(/User/username/$(username)).data.value).data.username == username;
allow delete: if
!userExistsAfter(getIndexBefore(/User/username/$(username)).data.value) ||
getUserAfter(getIndexBefore(/User/username/$(username)).data.value).data.username != username;
}
}
}
[Its not a perfect solution but working]
I have done this unique key using key...
I want my table to be having unique date value. so i made it key of my document.
Any way i am able to get all documents
db.collection('sensors').doc(sensorId).collection("data").doc(date).set(dataObj).then(() => {
response.send(dataObj);
});
What about doing a Transaction to first check if there are documents with the same value in this unique field, and only create the document if the result is empty.
As an example, creating a User with username as unique field:
type User = {
id?: string
username: string
firstName: string
lastName: string
}
async function createUser(user: User) {
try {
const newDocRef = db.collection('Users').doc()
await db.runTransaction(async t => {
const checkRef = db.collection('Users')
.where('username', '==', user.username)
const doc = await t.get(checkRef)
if (!doc.empty) {
throw new FirebaseError('firestore/unique-restriction',
`There is already a user with the username: '${user.username}' in the database.`
)
}
await t.create(newDocRef, user)
})
console.log('User Created')
} catch (err) {
if (err instanceof FirebaseError) {
console.log('Some error in firebase')
//Do something
} else {
console.log('Another error')
//Do whatever
}
}
}
Is this code ok or am I missing something?.
This is possible using a transaction, where a reading must be made to find out if another document uses the unique value.
IMPORTANT: Transaction has to be done using a Firestore server library to ensure blocking on concurrent operations (https://firebase.google.com/docs/firestore/transaction-data-contention#transactions_and_data_contention)
I did several tests simultaneously using Cloud Functions simulating delays and it worked great. See an example:
const result = await admin.firestore().runTransaction(async (t) => {
const personsRef = admin.firestore().collection("persons").where('email', '==', data.email)
const query = await t.get(personsRef);
if (query.docs.length > 0) {
throw new functions.https.HttpsError('permission-denied', `email ${data.email} already exists`);
}
const newPersonRef = admin.firestore().collection("persons").doc();
t.set(newPersonRef, {name: data.name, email: data.email});
return "update success";
}
In this example it is guaranteed that two people cannot use the same email in the inclusion (the same should be done for email changes).
Based on the documentation from this section https://cloud.google.com/firestore/docs/manage-data/add-data#set_a_document
You can simply add a custom identifier when adding document object to a collection as shown below:
const data = {
name: 'Los Angeles',
state: 'CA',
country: 'USA'
};
// Add a new document in collection "cities" with ID 'LA'
const res = await db.collection('cities').doc('LA').set(data);
Using this https://cloud.google.com/firestore/docs/manage-data/add-data#node.js_4 as a reference when you use set as a method on your collection you can be able to specify an id for such document when you need to auto-generate an id you simply use the add method on your collection