diff --git a/backend-services/feed-service/app/utils/MongoConnection.scala b/backend-services/feed-service/app/utils/MongoConnection.scala new file mode 100644 index 0000000000000000000000000000000000000000..39caedb80c63b68cb573ce00e052067552f8800f --- /dev/null +++ b/backend-services/feed-service/app/utils/MongoConnection.scala @@ -0,0 +1,114 @@ +package utils + +import org.mongodb.scala.{MongoClient, MongoDatabase, MongoCollection} +import org.mongodb.scala.model.{Filters, Projections, Sorts} +import com.mongodb.client.result.{InsertOneResult, UpdateResult} + +import org.bson.conversions.Bson +import org.bson.types.ObjectId + +import scala.concurrent.ExecutionContext.Implicits.global +import scala.concurrent.Future +import scala.reflect.ClassTag + +import utils.MongoCodecs + + +/** + * Mongo helper functions for connecting to and interacting with MongoDB clients, databases, and collections. + * + * All functions return a Future. + */ +object MongoConnection { + /** + * Connects to and gets a reference to a MongoDB client. + * + * @param mongoUri The connection string. + * @return A Future containing the MongoClient instance. + */ + def getClient(mongoUri: String): Future[MongoClient] = Future { + MongoClient(mongoUri) + } + + /** + * Gets a reference to a MongoDB database. + * + * @param database The name of the database to retrieve. + * @return A Future containing a MongoDatabase instance. + */ + def getDatabase(client: MongoClient, database: String): Future[MongoDatabase] = Future { + client.getDatabase(database).withCodecRegistry(MongoCodecs.codecRegistry) + } + + /** + * Gets a reference to a MongoDB collection within a database. + * + * @param database The MongoDatabase instance containing the desired collection. + * @param collection The name of the collection to retrieve. + * @return A Future containing a MongoCollection instance. + */ + def getCollection[T: ClassTag](database: MongoDatabase, collection: String): Future[MongoCollection[T]] = Future { + database.getCollection[T](collection) + } + + /** + * Finds documents in a MongoDB collection. + * + * @param collection The MongoCollection instance to search. + * @param filter A Bson filter to apply to the search. + * @param projection A Bson projection to apply to the search. + * @param sort A Bson sort to apply to the search. + * @return A Future containing a sequence of matching documents as Documents. + */ + def find[T: ClassTag]( + collection: MongoCollection[T], + filter: Bson = Filters.empty(), + projection: Bson = Projections.excludeId(), + sort: Bson = Sorts.ascending("_id") + ): Future[Seq[T]] = { + collection.find[T](filter) + .projection(projection) + .sort(sort) + .toFuture() + } + + /** + * Inserts a document into a MongoDB collection. + * + * @param collection The MongoCollection instance to insert into. + * @param document The document to insert. + * @return A Future containing the ID of the inserted document. + * @throws RuntimeException if the insertion was not acknowledged by the database. + */ + def insertOne[T](collection: MongoCollection[T], document: T): Future[String] = { + val futureResult: Future[InsertOneResult] = collection.insertOne(document).toFuture() + + futureResult.map[String]((result: InsertOneResult) => { + if (result.wasAcknowledged()) { + // Grab the generated ID of the inserted document + result.getInsertedId().asObjectId.getValue().toString() + } else { + throw new RuntimeException("Insertion was not acknowledged") + } + }) + } + + /** + * Updates a document in a MongoDB collection. + * + * @param collection The MongoCollection instance the document is in. + * @param documentId The ID of the document to update. + * @param updates A sequence of Bson documents defining the updates. + * @throws RuntimeException if the update was not acknowledged by the database. + */ + def updateOne[T](collection: MongoCollection[T], documentId: ObjectId, updates: Seq[Bson]): Future[Unit] = { + val filter: Bson = Filters.equal[ObjectId]("_id", documentId) + val futureResult: Future[UpdateResult] = collection.updateOne(filter, updates).toFuture() + + futureResult.map[Unit]((result: UpdateResult) => { + if (!result.wasAcknowledged()) { + throw new RuntimeException("Update was not acknowledged") + } + }) + } +}