You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
|
|
package wow.doge.http4sdemo.services
import io.circe.generic.semiauto._ import monix.bio.IO import monix.bio.Task import monix.bio.UIO import monix.reactive.Observable import slick.jdbc.JdbcBackend import slick.jdbc.JdbcProfile import wow.doge.http4sdemo.dto.Author import wow.doge.http4sdemo.dto.Book import wow.doge.http4sdemo.dto.BookSearchMode import wow.doge.http4sdemo.dto.BookSearchMode.AuthorName import wow.doge.http4sdemo.dto.BookSearchMode.BookTitle import wow.doge.http4sdemo.dto.BookUpdate import wow.doge.http4sdemo.dto.NewAuthor import wow.doge.http4sdemo.dto.NewBook import wow.doge.http4sdemo.implicits._ import wow.doge.http4sdemo.slickcodegen.Tables
object LibraryService { sealed trait Error extends Exception { def message: String override def getMessage(): String = message } final case class EntityDoesNotExist(message: String) extends Error final case class EntityAlreadyExists(message: String) extends Error // final case class MyError2(message: String) extends Error
// case object C3 extends Error { val message: String = "C3" }
object Error { implicit val codec = deriveCodec[Error] } }
trait LibraryService {
import LibraryService._
def getBooks: Observable[Book]
def getBookById(id: Int): Task[Option[Book]]
def searchBook(mode: BookSearchMode, value: String): Observable[Book]
def updateBook(id: Int, updateData: BookUpdate): IO[Error, Unit]
def deleteBook(id: Int): Task[Int]
def insertBook(newBook: NewBook): IO[Error, Book]
def insertAuthor(a: NewAuthor): Task[Int]
def booksForAuthor(authorId: Int): Observable[Book]
}
class LibraryServiceImpl( profile: JdbcProfile, dbio: LibraryDbio, db: JdbcBackend.DatabaseDef ) extends LibraryService { import profile.api._
import LibraryService._
def getBooks = db.streamO(dbio.getBooks.transactionally)
def getBookById(id: Int) = db.runL(dbio.getBook(id))
def searchBook(mode: BookSearchMode, value: String): Observable[Book] = mode match { case BookTitle => db.streamO(dbio.getBooksByTitle(value))
case AuthorName => Observable .fromTask((for { _ <- IO.unit id <- IO(value.toInt) author <- db.runL(dbio.getAuthor(id)).flatMap { case None => IO.raiseError( new EntityDoesNotExist(s"Author with id=$id does not exist") ) case Some(value) => IO.pure(value) } books = db .streamO(dbio.getBooksForAuthor(id)) .map(Book.fromBooksRow) } yield books).toTask) .flatten
}
def insertAuthor(a: NewAuthor): Task[Int] = db.runL(dbio.insertAuthor(a))
def updateBook(id: Int, updateData: BookUpdate): IO[Error, Unit] = for { action <- UIO.deferAction(implicit s => UIO(for { mbRow <- dbio.selectBook(id).result.headOption updatedRow <- mbRow match { case Some(value) => println(s"Original value -> $value") println(s"Value to be updated with -> $updateData") DBIO.successful(updateData.update(value)) case None => DBIO.failed( EntityDoesNotExist(s"Book with id=$id does not exist") ) } updateAction = dbio.selectBook(id).update(updatedRow) _ = println(s"SQL = ${updateAction.statements}") _ <- updateAction } yield ()) ) _ <- db .runTryL(action.transactionally.asTry) .mapErrorPartial { case e: Error => e } } yield ()
def deleteBook(id: Int) = db.runL(dbio.deleteBook(id))
def insertBook(newBook: NewBook): IO[Error, Book] = IO.deferAction { implicit s => for { action <- UIO(for { _ <- dbio .selectBookByIsbn(newBook.isbn) .map(Book.fromBooksTableFn) .result .headOption .flatMap { case None => DBIO.successful(()) case Some(_) => DBIO.failed( EntityAlreadyExists( s"Book with isbn=${newBook.isbn} already exists" ) ) } _ <- dbio.getAuthor(newBook.authorId).flatMap { case None => DBIO.failed( EntityDoesNotExist( s"Author with id=${newBook.authorId} does not exist" ) ) case Some(_) => DBIO.successful(()) } book <- dbio.insertBookAndGetBook(newBook) } yield book) book <- db .runTryL(action.transactionally.asTry) .mapErrorPartial { case e: Error => e } } yield book }
def booksForAuthor(authorId: Int) = db.streamO(dbio.getBooksForAuthor(authorId).transactionally) .map(Book.fromBooksRow)
}
class LibraryDbio(val profile: JdbcProfile) { import profile.api._
/* */
def getBooks: StreamingDBIO[Seq[Book], Book] = Query.getBooksInner.result
def insertBookAndGetId(newBook: NewBook): DBIO[Int] = Query.insertBookGetId += newBook
def insertBookAndGetBook(newBook: NewBook): DBIO[Book] = Query.insertBookGetBook += newBook
def insertAuthor(newAuthor: NewAuthor): DBIO[Int] = Query.insertAuthorGetId += newAuthor
def selectBook(id: Int) = Tables.Books.filter(_.bookId === id)
def getAuthor(id: Int) = Query.selectAuthor(id).map(Author.fromAuthorsTableFn).result.headOption
def deleteBook(id: Int) = selectBook(id).delete
def getBook(id: Int) = selectBook(id) .map(Book.fromBooksTableFn) .result .headOption
def selectBookByIsbn(isbn: String) = Tables.Books.filter(_.isbn === isbn)
def getBooksByTitle(title: String) = Tables.Books.filter(_.bookTitle === title).map(Book.fromBooksTableFn).result
def getBooksForAuthor(authorId: Int) = Query.booksForAuthorInner(authorId).result
private object Query {
val getBooksInner = Book.fromBooksTable
val insertBookGetId = NewBook.fromBooksTable.returning(Tables.Books.map(_.bookId))
val insertBookGetBook = NewBook.fromBooksTable.returning(getBooksInner)
val insertAuthorGetId = Tables.Authors .map(a => (a.authorName).mapTo[NewAuthor]) .returning(Tables.Authors.map(_.authorId))
// val insertAuthor = NewAuthor.fromAuthorsTable
def booksForAuthorInner(authorId: Int) = for { b <- Tables.Books a <- Tables.Authors if b.authorId === a.authorId && b.authorId === authorId } yield b
def selectAuthor(authorId: Int) = Tables.Authors.filter(_.authorId === authorId) } }
trait NoopLibraryService extends LibraryService { def getBooks: Observable[Book] = Observable.raiseError(new NotImplementedError)
def getBookById(id: Int): Task[Option[Book]] = IO.terminate(new NotImplementedError)
def searchBook( mode: BookSearchMode, value: String ): Observable[Book] = Observable.raiseError(new NotImplementedError)
def updateBook( id: Int, updateData: BookUpdate ): IO[LibraryService.Error, Unit] = IO.terminate(new NotImplementedError)
def deleteBook(id: Int): Task[Int] = IO.terminate(new NotImplementedError)
def insertBook(newBook: NewBook): IO[LibraryService.Error, Book] = IO.terminate(new NotImplementedError)
def insertAuthor(a: NewAuthor): Task[Int] = IO.terminate(new NotImplementedError)
def booksForAuthor(authorId: Int): Observable[Book] = Observable.raiseError(new NotImplementedError)
}
|