Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
Daily.scala 6.85 KiB
package models

import repositories.{DailyRepository}
import models.exceptions.{ConflictException, NotFoundException}

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.{Future, Await}
import scala.concurrent.duration._

import org.bson.types.ObjectId
import java.util.Date
import java.time.Instant
import java.text.SimpleDateFormat

import play.api.libs.json.{Json, JsValue, JsString, JsObject, JsArray}

import org.bson.{BsonWriter, BsonReader, BsonType}
import org.bson.codecs.{Codec, EncoderContext, DecoderContext}
import org.bson.conversions.Bson
import org.mongodb.scala.model.Updates

case class Daily(
    id: Option[ObjectId],
    userId: ObjectId,
    questionId: ObjectId,
    content: String,
    usersLiked: Seq[ObjectId],
    createdAt: Date,
    updatedAt: Date
)

object Daily {
    val dailyRepo = new DailyRepository()

    def createDailyAsync(
        userId: ObjectId,
        questionId: ObjectId,
        content: String,
        timeout: Int = 4
    ): Daily = {
        val now: Date = Date.from(Instant.now())
        val daily: Daily = Daily(None, userId, questionId, content, Seq.empty[ObjectId], now, now)
        val future: Future[Daily] = dailyRepo.insertDaily(daily)
        Await.result[Daily](future, timeout.seconds)
    }

    def getAllDailiesAsync(timeout: Int = 4): Seq[Daily] = {
        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.getByValue("user_id", userId)
        Await.result[Seq[Daily]](future, timeout.seconds)
    }

    def getUserFeedAsync(userId: ObjectId, timeout: Int = 4): Seq[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.getByValues[ObjectId]("user_id", friends)
        } yield feed

        Await.result[Seq[Daily]](result, timeout.seconds)
    }

    def likeAsync(dailyId: ObjectId, likerId: ObjectId, timeout: Int = 4): Unit = {
        val result: Future[Unit] = for {
            // Fetch Daily from given ID
            daily: Daily  <- {
                dailyRepo.getById(dailyId).map((oDaily: Option[Daily]) => {
                    if (oDaily.isEmpty) 
                        throw new NotFoundException("No daily with given ID.") 
                    else 
                        oDaily.get
                })
            }

            // Check user has not already liked the Daily
            _ = if (daily.usersLiked.contains(likerId)) throw new ConflictException("User has already liked this Daily.")

            // Check user with given ID exists
            _ <- User.userExists(likerId).map((exists: Boolean) => if (!exists) throw new NotFoundException("No user with given ID."))

            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)
    }

    // Convert from Daily object to JSON (serializing to JSON)
    def toJson(daily: Daily): JsValue = {
        val usersLikedAsJsStrings: Seq[JsString] = daily.usersLiked.map[JsString](id => JsString(id.toString()))

        val dateFormat: SimpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'")
        val formattedCreatedAt: String = dateFormat.format(daily.createdAt)
        val formattedUpdatedAt: String = dateFormat.format(daily.updatedAt)

        val dailyJson = Seq(
            "id" -> JsString(daily.id.getOrElse("").toString()),
            "userId" -> JsString(daily.userId.toString()),
            "questionId" -> JsString(daily.questionId.toString()),
            "content" -> JsString(daily.content),
            "usersLiked" -> JsArray(usersLikedAsJsStrings),
            "createdAt" -> JsString(formattedCreatedAt),
            "updatedAt" -> JsString(formattedUpdatedAt)
        )
        
        Json.toJson[JsObject](JsObject(dailyJson))
    }

    // Convert from Daily set to JSON (serializing to JSON)
    def toJson(dailies: Seq[Daily]): JsValue = {
        val dailiesJson: Seq[JsValue] = dailies.map(daily => Daily.toJson(daily))

        Json.toJson[JsArray](JsArray(dailiesJson))
    }

    def toString(daily: Daily): String =
        return s"Daily(${daily.id.toString()}, ${daily.userId.toString()}, ${daily.questionId.toString()}, ${daily.content}, ${daily.usersLiked.toString()})"

    // Codec instance for serialising/deserialising type User to or from BSON.
    // Implicit keyword lets Scala compiler automatically insert this into the code where it's needed.
    implicit val codec: Codec[Daily] = new Codec[Daily] {
        override def encode(writer: BsonWriter, value: Daily, encoderContext: EncoderContext): Unit = {
            writer.writeStartDocument()
            writer.writeObjectId("user_id", value.userId)
            writer.writeObjectId("question_id", value.questionId)
            writer.writeString("content", value.content)
            writer.writeStartArray("usersLiked")
            value.usersLiked.foreach(writer.writeObjectId)
            writer.writeEndArray()
            writer.writeDateTime("createdAt", value.createdAt.getTime())
            writer.writeDateTime("updatedAt", value.updatedAt.getTime())
            writer.writeEndDocument()
        }

        override def decode(reader: BsonReader, decoderContext: DecoderContext): Daily = {
            reader.readStartDocument()
            println("hi")
            val id = reader.readObjectId("_id")
            println(id)
            val userId = reader.readObjectId("user_id")
            val questionId = reader.readObjectId("question_id")
            val content = reader.readString("content")
            val usersLiked = {
                reader.readName("usersLiked")
                reader.readStartArray()
                val buffer = collection.mutable.Buffer.empty[ObjectId]
                while (reader.readBsonType() != BsonType.END_OF_DOCUMENT) {
                    buffer += reader.readObjectId()
                }
                reader.readEndArray()
                buffer.toSeq
            }
            val createdAt = reader.readDateTime("createdAt")
            val updatedAt = reader.readDateTime("updatedAt")
            reader.readEndDocument()

            val createdAtDate: Date = Date.from(Instant.ofEpochMilli(createdAt))
            val updatedAtDate: Date = Date.from(Instant.ofEpochMilli(updatedAt))

            Daily(Some(id), userId, questionId, content, usersLiked, createdAtDate, updatedAtDate)
        }

        override def getEncoderClass: Class[Daily] = classOf[Daily]
    }
}