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 org.http4s.dsl.Http4sDsl 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 def toResponse = { val dsl = Http4sDsl[Task] import org.http4s.circe.CirceEntityCodec._ import dsl._ implicit val codec = Error.codec this match { case e @ LibraryService.EntityDoesNotExist(message) => NotFound(e: LibraryService.Error).hideErrors case e @ LibraryService.EntityAlreadyExists(message) => BadRequest(e: LibraryService.Error).hideErrors } } } final case class EntityDoesNotExist(message: String) extends Error final case class EntityAlreadyExists(message: String) extends Error // final case class MessageBodyError(cause: MessageBodyFailure) 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] // def convert(e: MessageBodyFailure) = e match {
// case InvalidMessageBodyFailure(details, cause) => ()
// case MalformedMessageBodyFailure(details, cause) => ()
// }
} }
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.getBookById(id))
def searchBook(mode: BookSearchMode, value: String): Observable[Book] = mode match { case BookTitle => db.streamO(dbio.getBooksByTitle(value))
case AuthorName => Observable .fromTask((for { author <- db.runL(dbio.getAuthorByName(value)).flatMap { case None => IO.raiseError( new EntityDoesNotExist( s"Author with name=$value does not exist" ) ) case Some(value) => IO.pure(value) } books = db .streamO(dbio.getBooksForAuthor(author.authorId)) .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 <- Tables.Books.filter(_.bookId === 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 = Tables.Books.filter(_.bookId === 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 { _ <- Tables.Books .filter(_.isbn === 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 getAuthor(id: Int): DBIO[Option[Author]] = Query.selectAuthor(id).map(Author.fromAuthorsTableFn).result.headOption
def getAuthorByName(name: String): DBIO[Option[Author]] = Tables.Authors .filter(_.authorName === name) .map(Author.fromAuthorsTableFn) .result .headOption
def deleteBook(id: Int): DBIO[Int] = Query.selectBookById(id).delete
def getBookById(id: Int): DBIO[Option[Book]] = Query .selectBookById(id) .map(Book.fromBooksTableFn) .result .headOption
def getBooksByTitle(title: String): StreamingDBIO[Seq[Book], Book] = Tables.Books.filter(_.bookTitle === title).map(Book.fromBooksTableFn).result
def getBooksForAuthor( authorId: Int ): StreamingDBIO[Seq[Tables.BooksRow], Tables.BooksRow] = 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)
def selectBookById(id: Int) = Tables.Books.filter(_.bookId === id)
def selectBookByIsbn(isbn: String) = Tables.Books.filter(_.isbn === isbn) } }
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)
}
|