All Downloads are FREE. Search and download functionalities are using the official Maven repository.

sss.openstar.contacts.ContactService.scala Maven / Gradle / Ivy

package sss.openstar.contacts


import java.sql.SQLIntegrityConstraintViolationException
import akka.util.ByteString
import sss.db._
import sss.openstar.UniqueNodeIdentifier
import sss.openstar.contacts.ContactService.{CategoryDetails, Contact, LoggedInUser}
import sss.openstar.schemamigration.SqlSchemaNames.ColumnNames._
import sss.db.ops.DbOps.{DbRunOps, FutureTxOps}
import sss.openstar.schemamigration.SqlSchemaNames

object ContactService {

  trait User {
    val name: UniqueNodeIdentifier
    val avatar: Option[ByteString]
    def setAvatar(avatar: Option[ByteString]): Unit
  }


  case class LoggedInUser(private [contacts] val id: Long,
                          name: UniqueNodeIdentifier,
                          avatar: Option[ByteString]
                         )(implicit private [contacts] val contactService: ContactService) extends User {

    def setAvatar(avatar: Option[ByteString]): Unit  =
      contactService.updateAvatar(id, avatar)

    def update(updatedAvatar: Option[ByteString]): LoggedInUser = {
      setAvatar(updatedAvatar)
      copy(avatar = updatedAvatar)
    }

    def getCategoryDetails(contactName: UniqueNodeIdentifier): Option[CategoryDetails] = {
      require(contactName != name, s"Cant get category details with yourself $contactName")
      contactService.getCategoryDetails(id, contactName)
    }

    def setCategoryDetails(invitee: UniqueNodeIdentifier,
                           invitorCategory: String,
                           inviteeCategory: String,
                           inviteeAvatar: Option[ByteString]): Unit = {

      require(invitee != name, s"Cant set category details with yourself $invitee")

      contactService
        .setCategoryDetails(
          id,
          invitee,
          invitorCategory,
          inviteeCategory,
          inviteeAvatar
        )
    }

    def contacts: Seq[Contact] = contactService.contacts(id)
  }

  case class Contact(
                      private [contacts] val id: Long,
                      name: UniqueNodeIdentifier,
                      avatar: Option[ByteString]
                    )(implicit private [contacts] val contactService: ContactService) extends User {

    def setAvatar(avatar: Option[ByteString]): Unit  =
      contactService.updateAvatar(id, avatar)

  }

  case class CategoryDetails(
                      nameCategoryForContact: String,
                      contact: UniqueNodeIdentifier,
                      contactCategoryForName: String
                    )
}


class ContactService(implicit db:Db) {

  private implicit val contactService: ContactService = this

  lazy private val contactTable = db.table(SqlSchemaNames.TableNames.contactTableName)

  lazy private val contactCategoriesTable = db.table(SqlSchemaNames.TableNames.contactCategoriesTableName)

  def addContact(userId: UniqueNodeIdentifier, avatar: Option[ByteString] = None): Contact = addContactFTx(userId, avatar).dbRunSyncGet

  def addContactFTx(userId: UniqueNodeIdentifier, avatar: Option[ByteString] = None): FutureTx[Contact] = {

    contactTable.insert(Map(
      contactCol -> userId,
      avatarCol -> avatar.map(_.toArray)
    )).map(rowToContact)
      .recoverWithDb {
        case e: SQLIntegrityConstraintViolationException =>
          for {
            rowOpt <- contactTable
              .getRow(
                where(
                  contactCol -> userId
                ))
            updated <- contactTable.updateRow(rowOpt.get + (avatarCol -> avatar.map(_.toArray)))

          } yield rowToContact(updated)

      }
  }

  private [contacts] def updateAvatar(contactId: Long, avatar: Option[ByteString]): Contact =  {
    contactTable
      .updateRow(
        Map(
          avatarCol -> avatar.map(_.toArray),
          idCol -> contactId
        )
      ).map(rowToContact)
  }.dbRunSyncGet

  private [contacts] def getContact(contactId: Long): Contact = rowToContact(contactTable(contactId).dbRunSyncGet)

  private def rowToContact(row: Row): Contact =
    Contact(
      row.id,
      row.string(contactCol),
      row.arrayByteOpt(avatarCol)
        .map(ByteString.apply)
    )

  private def findRowId(contact: UniqueNodeIdentifier): FutureTx[Option[Long]] = findRow(contact).map(_.map(_.id))

  private def findRow(contact: UniqueNodeIdentifier): FutureTx[Option[Row]] =
    contactTable
      .find(
        where(contactCol -> contact)
      )

  def findUser(loggedInUser: UniqueNodeIdentifier): Option[LoggedInUser] = {
    findRow(loggedInUser).dbRunSyncGet.map { row =>
      LoggedInUser(row.id,
        row.string(contactCol),
        row.arrayByteOpt(avatarCol)
          .map(ByteString.apply))
    }
  }

  def findContact(contact: UniqueNodeIdentifier): Option[Contact] =
    findRow(contact).dbRunSyncGet
      .map(rowToContact)

  private [contacts] def getCategoryDetails(
                         meId: Long,
                         contact: UniqueNodeIdentifier): Option[CategoryDetails] = {

    for {
      contactId <- findRowId(contact)
      rowOpt <- contactCategoriesTable.find(where(localContactLnkCol -> meId, remoteContactLnkCol -> contactId))
    } yield rowOpt.map(row => CategoryDetails(row.string(localCategoryCol), contact, row.string(remoteCategoryCol)))

  }.dbRunSyncGet

  private [contacts] def setCategoryDetails(meId: Long,
                 invitee: UniqueNodeIdentifier,
                 invitorCategory: String,
                 inviteeCategory: String,
                 inviteeAvatar: Option[ByteString] = None): Unit = {

    val dbPlan = for {
      getContact <- addContactFTx(invitee, inviteeAvatar)
      newValues = Map(
        localContactLnkCol -> meId,
        localCategoryCol -> invitorCategory,
        remoteContactLnkCol -> getContact.id,
        remoteCategoryCol -> inviteeCategory
      )
      foundOpt <- contactCategoriesTable.find(
        where(
          localContactLnkCol -> meId,
          remoteContactLnkCol -> getContact.id)
      )
      _ <- foundOpt match {
        case Some(row) =>
          contactCategoriesTable.updateRow(row ++ newValues)
        case None =>
          contactCategoriesTable.insert(newValues)
      }
    } yield ()

    dbPlan.dbRunSyncGet
  }

  private[contacts] def contacts(meId: Long): Seq[Contact] = (for {
    categoriesRows <- contactCategoriesTable.filter(
      where(
        localContactLnkCol -> meId
      )
    )
    contacts <- FutureTx.sequence {
      for {
        row <- categoriesRows
        id = row.long(remoteContactLnkCol)
      } yield (contactTable(id))
    }

  } yield (contacts.map(rowToContact))).dbRunSyncGet

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy