diff --git a/backend-services/feed-service/app/models/Daily.scala b/backend-services/feed-service/app/models/Daily.scala index 3638ce7e836664efe2cae82c26521cf59126b259..e4fa685fcb3e8dff60395e3dfb0aafe7140e45fb 100644 --- a/backend-services/feed-service/app/models/Daily.scala +++ b/backend-services/feed-service/app/models/Daily.scala @@ -1,6 +1,6 @@ package models -import repositories.DailyRepository +import repositories.{DailyRepository} import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.{Future, Await} @@ -44,12 +44,12 @@ object Daily { } def getAllDailiesAsync(timeout: Int = 4): Seq[Daily] = { - val future: Future[Seq[Daily]] = dailyRepo.getAllDailies() + val future: Future[Seq[Daily]] = dailyRepo.getAll() Await.result[Seq[Daily]](future, timeout.seconds) } def getUserDailiesAsync(userId: ObjectId, timeout: Int = 4): Seq[Daily] = { - val future: Future[Seq[Daily]] = dailyRepo.getUserDailies(userId) + val future: Future[Seq[Daily]] = dailyRepo.getByValue("user_id", userId) Await.result[Seq[Daily]](future, timeout.seconds) } @@ -57,17 +57,22 @@ object Daily { // Sequentially waits for Future objects to complete before calling next method val result: Future[Seq[Daily]] = for { friends: Seq[ObjectId] <- User.getUserFriends(userId) - feed: Seq[Daily] <- dailyRepo.getUserDailies(friends) + feed: Seq[Daily] <- dailyRepo.getByValues[ObjectId]("user_id", friends) } yield feed Await.result[Seq[Daily]](result, timeout.seconds) } - def likeAsync(dailyId: ObjectId, userId: ObjectId, timeout: Int = 4): Unit = { + def likeAsync(dailyId: ObjectId, likerId: ObjectId, timeout: Int = 4): Unit = { val result: Future[Unit] = for { - daily: Daily <- dailyRepo.getDaily(dailyId) - unit: Unit <- dailyRepo.like(daily, userId) - } yield unit + daily: Daily <- dailyRepo.getById(dailyId) + like: Unit <- { + val updatedUsersLiked: Seq[ObjectId] = daily.usersLiked :+ likerId + val update: Bson = Updates.set("usersLiked", updatedUsersLiked) + + dailyRepo.updateOne(dailyId, Seq(update)) + } + } yield like Await.result[Unit](result, timeout.seconds) } diff --git a/backend-services/feed-service/app/models/MongoDBClient.scala b/backend-services/feed-service/app/models/MongoDBClient.scala deleted file mode 100644 index 2edd41218a5c7f1646790ad230e63fdc6e175b90..0000000000000000000000000000000000000000 --- a/backend-services/feed-service/app/models/MongoDBClient.scala +++ /dev/null @@ -1,116 +0,0 @@ -package models - -import utils.ConfigHelper - -import org.mongodb.scala.{MongoClient, MongoDatabase, MongoCollection, Document} -import com.mongodb.client.result.{InsertOneResult, UpdateResult} -import org.mongodb.scala.model.{Filters, Projections, Sorts} -import org.bson.conversions.Bson -import org.bson.types.ObjectId - -import scala.concurrent.ExecutionContext.Implicits.global -import scala.concurrent.Future - - -/** - * A MongoDB client for connecting to and interacting with a MongoDB database. - * - * @constructor Creates a new instance of the MongoDBClient class. - */ -class MongoDBClient { - private val mongoUri = ConfigHelper.getString("mongodb.uri") - - // Connects to a MongoDB Client when class is constructed - private val client: MongoClient = this.connect() - - /** - * Connects to a MongoDB database using the default MongoDB connection string. - * - * @return A MongoClient instance. - */ - def connect(): MongoClient = { - 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(database: String): Future[MongoDatabase] = Future { - client.getDatabase(database) - } - - /** - * 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(database: MongoDatabase, collection: String): Future[MongoCollection[Document]] = Future { - database.getCollection(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( - collection: MongoCollection[Document], - filter: Bson = Filters.empty(), - projection: Bson = Projections.excludeId(), - sort: Bson = Sorts.ascending("_id") - ): Future[Seq[Document]] = { - collection.find[Document](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(collection: MongoCollection[Document], document: Document): 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(collection: MongoCollection[Document], 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") - } - }) - } -} diff --git a/backend-services/feed-service/app/repositories/DailyRepository.scala b/backend-services/feed-service/app/repositories/DailyRepository.scala index 48846acf0815abc64462872588908470214347e5..514860f212551d4ba8f6a6cbd670a19707dcfa2d 100644 --- a/backend-services/feed-service/app/repositories/DailyRepository.scala +++ b/backend-services/feed-service/app/repositories/DailyRepository.scala @@ -1,107 +1,26 @@ package repositories -import com.typesafe.config.{Config, ConfigFactory} +import models.Daily +import utils.MongoConnection -import models.{Daily, MongoDBClient} - -import org.mongodb.scala.{MongoDatabase, MongoCollection, Document} -import org.mongodb.scala.model.{Filters, Updates} +import com.typesafe.config.ConfigFactory import org.bson.types.ObjectId -import org.bson.conversions.Bson import scala.concurrent.ExecutionContext.Implicits.global -import scala.concurrent.{Future, Await} -import scala.concurrent.duration._ - -import scala.collection.JavaConverters._ - +import scala.concurrent.Future -class DailyRepository extends MongoDBClient { - // Loads the default configuration - private val config: Config = ConfigFactory.load() - - private val databaseName: String = config.getString("mongo.feedService.db") - private val collectionName: String = config.getString("mongo.dailies.collection") - - /** - * Returns a reference to a MongoDB collection within a database. - * Once Future completes, collection reference is returned. - * - * @return a MongoCollection[Document] object representing the dailies collection. - * @throws TimeoutException if the Future doesn't complete within the 3 second timeout. - */ - private def dailiesCollection: MongoCollection[Document] = { - val futureCollection: Future[MongoCollection[Document]] = for { - database: MongoDatabase <- getDatabase(databaseName) - collection: MongoCollection[Document] <- getCollection(database, collectionName) - } yield collection - - Await.result[MongoCollection[Document]](futureCollection, 3.seconds) - } - - /** - * Gets a Daily record from the given ID. - * - * @return A Future containing a sequence of matching Daily objects. - */ - def getDaily(dailyId: ObjectId): Future[Daily] = { - val filter = Filters.equal("_id", dailyId) - val document: Future[Document] = find(dailiesCollection, filter).map(x => x(0)) - document.map[Daily](doc => DailyRepository.fromMongoDocument(doc)) - } - - /** - * Gets all the Daily records. - * - * @return A Future containing a sequence of matching Daily objects. - */ - def getAllDailies(): Future[Seq[Daily]] = { - val documents: Future[Seq[Document]] = find(dailiesCollection) - documents.map[Seq[Daily]](docs => DailyRepository.fromMongoDocument(docs)) - } - - /** - * Gets all the user's Dailies. - * - * @return A Future containing a sequence of the user's Daily objects. - */ - def getUserDailies(userId: ObjectId): Future[Seq[Daily]] = { - val userFilter: Bson = Filters.equal("user_id", userId) - val documents: Future[Seq[Document]] = find(dailiesCollection, userFilter) - - documents.map[Seq[Daily]](docs => DailyRepository.fromMongoDocument(docs)) - } - - /** - * Gets all the user's Dailies. - * - * @return A Future containing a sequence of the user's Daily objects. - */ - def getUserDailies(userIds: Seq[ObjectId]): Future[Seq[Daily]] = { - // The syntax userIds: _* is used to convert the List of ObjectId instances to a variable-length argument list - val userFilter: Bson = Filters.in("user_id", userIds: _*) - val documents: Future[Seq[Document]] = find(dailiesCollection, userFilter) - - documents.map[Seq[Daily]](docs => DailyRepository.fromMongoDocument(docs)) - } +class DailyRepository extends Repository[Daily] ( + ConfigFactory.load().getString("mongo.feedService.db"), + ConfigFactory.load().getString("mongo.dailies.collection") +) { /** * Inserts a Daily record into the database. * * @return A Future containing the inserted Daily object with the generated ID. */ def insertDaily(daily: Daily): Future[Daily] = { - // Don't supply an ID as Mongo will generate one for us - val document: Document = Document( - "user_id" -> daily.userId, - "question_id" -> daily.questionId, - "content" -> daily.content, - "usersLiked" -> daily.usersLiked, - "createdAt" -> daily.createdAt, - "updatedAt" -> daily.updatedAt - ) - - val result: Future[String] = insertOne(dailiesCollection, document) + val result: Future[String] = MongoConnection.insertOne[Daily](collection, daily) // Return a Daily entity with the generated ID result.flatMap[Daily](id => { @@ -109,35 +28,4 @@ class DailyRepository extends MongoDBClient { Future.successful(updatedDaily) }) } - - /** - * Add a user like to the given Daily record in the database. - */ - def like(daily: Daily, user: ObjectId): Future[Unit] = { - val updatedUsersLiked: Seq[ObjectId] = daily.usersLiked :+ user - val update: Bson = Updates.set("usersLiked", updatedUsersLiked) - val updates: Seq[Bson] = Seq(update) - - val dailyId: ObjectId = daily.id.getOrElse(throw new RuntimeException) - updateOne(dailiesCollection, dailyId, updates) - } -} - - -object DailyRepository { - def fromMongoDocument(document: Document): Daily = { - Daily( - Some(document.getObjectId("_id")), - document.getObjectId("user_id"), - document.getObjectId("question_id"), - document.getString("content"), - document.getList("usersLiked", classOf[ObjectId]).asScala.toSeq, - document.getDate("createdAt"), - document.getDate("updatedAt") - ) - } - - def fromMongoDocument(documents: Seq[Document]): Seq[Daily] = { - documents.map[Daily](document => DailyRepository.fromMongoDocument(document)) - } }