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.

261 lines
7.5 KiB

3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
  1. package wow.doge.http4sdemo.services
  2. import io.circe.generic.semiauto._
  3. import monix.bio.IO
  4. import monix.bio.Task
  5. import monix.bio.UIO
  6. import monix.reactive.Observable
  7. import slick.jdbc.JdbcBackend
  8. import slick.jdbc.JdbcProfile
  9. import wow.doge.http4sdemo.dto.Author
  10. import wow.doge.http4sdemo.dto.Book
  11. import wow.doge.http4sdemo.dto.BookSearchMode
  12. import wow.doge.http4sdemo.dto.BookSearchMode.AuthorName
  13. import wow.doge.http4sdemo.dto.BookSearchMode.BookTitle
  14. import wow.doge.http4sdemo.dto.BookUpdate
  15. import wow.doge.http4sdemo.dto.NewAuthor
  16. import wow.doge.http4sdemo.dto.NewBook
  17. import wow.doge.http4sdemo.implicits._
  18. import wow.doge.http4sdemo.slickcodegen.Tables
  19. object LibraryService {
  20. sealed trait Error extends Exception {
  21. def message: String
  22. override def getMessage(): String = message
  23. }
  24. final case class EntityDoesNotExist(message: String) extends Error
  25. final case class EntityAlreadyExists(message: String) extends Error
  26. // final case class MyError2(message: String) extends Error
  27. // case object C3 extends Error { val message: String = "C3" }
  28. object Error {
  29. implicit val codec = deriveCodec[Error]
  30. }
  31. }
  32. trait LibraryService {
  33. import LibraryService._
  34. def getBooks: Observable[Book]
  35. def getBookById(id: Int): Task[Option[Book]]
  36. def searchBook(mode: BookSearchMode, value: String): Observable[Book]
  37. def updateBook(id: Int, updateData: BookUpdate): IO[Error, Unit]
  38. def deleteBook(id: Int): Task[Int]
  39. def insertBook(newBook: NewBook): IO[Error, Book]
  40. def insertAuthor(a: NewAuthor): Task[Int]
  41. def booksForAuthor(authorId: Int): Observable[Book]
  42. }
  43. class LibraryServiceImpl(
  44. profile: JdbcProfile,
  45. dbio: LibraryDbio,
  46. db: JdbcBackend.DatabaseDef
  47. ) extends LibraryService {
  48. import profile.api._
  49. import LibraryService._
  50. def getBooks = db.streamO(dbio.getBooks.transactionally)
  51. def getBookById(id: Int) = db.runL(dbio.getBook(id))
  52. def searchBook(mode: BookSearchMode, value: String): Observable[Book] =
  53. mode match {
  54. case BookTitle =>
  55. db.streamO(dbio.getBooksByTitle(value))
  56. case AuthorName =>
  57. Observable
  58. .fromTask((for {
  59. _ <- IO.unit
  60. id <- IO(value.toInt)
  61. author <- db.runL(dbio.getAuthor(id)).flatMap {
  62. case None =>
  63. IO.raiseError(
  64. new EntityDoesNotExist(s"Author with id=$id does not exist")
  65. )
  66. case Some(value) => IO.pure(value)
  67. }
  68. books = db
  69. .streamO(dbio.getBooksForAuthor(id))
  70. .map(Book.fromBooksRow)
  71. } yield books).toTask)
  72. .flatten
  73. }
  74. def insertAuthor(a: NewAuthor): Task[Int] = db.runL(dbio.insertAuthor(a))
  75. def updateBook(id: Int, updateData: BookUpdate): IO[Error, Unit] =
  76. for {
  77. action <- UIO.deferAction(implicit s =>
  78. UIO(for {
  79. mbRow <- dbio.selectBook(id).result.headOption
  80. updatedRow <- mbRow match {
  81. case Some(value) =>
  82. println(s"Original value -> $value")
  83. println(s"Value to be updated with -> $updateData")
  84. DBIO.successful(updateData.update(value))
  85. case None =>
  86. DBIO.failed(
  87. EntityDoesNotExist(s"Book with id=$id does not exist")
  88. )
  89. }
  90. updateAction = dbio.selectBook(id).update(updatedRow)
  91. _ = println(s"SQL = ${updateAction.statements}")
  92. _ <- updateAction
  93. } yield ())
  94. )
  95. _ <- db
  96. .runTryL(action.transactionally.asTry)
  97. .mapErrorPartial { case e: Error =>
  98. e
  99. }
  100. } yield ()
  101. def deleteBook(id: Int) = db.runL(dbio.deleteBook(id))
  102. def insertBook(newBook: NewBook): IO[Error, Book] =
  103. IO.deferAction { implicit s =>
  104. for {
  105. action <- UIO(for {
  106. _ <- dbio
  107. .selectBookByIsbn(newBook.isbn)
  108. .map(Book.fromBooksTableFn)
  109. .result
  110. .headOption
  111. .flatMap {
  112. case None => DBIO.successful(())
  113. case Some(_) =>
  114. DBIO.failed(
  115. EntityAlreadyExists(
  116. s"Book with isbn=${newBook.isbn} already exists"
  117. )
  118. )
  119. }
  120. _ <- dbio.getAuthor(newBook.authorId).flatMap {
  121. case None =>
  122. DBIO.failed(
  123. EntityDoesNotExist(
  124. s"Author with id=${newBook.authorId} does not exist"
  125. )
  126. )
  127. case Some(_) => DBIO.successful(())
  128. }
  129. book <- dbio.insertBookAndGetBook(newBook)
  130. } yield book)
  131. book <- db
  132. .runTryL(action.transactionally.asTry)
  133. .mapErrorPartial { case e: Error =>
  134. e
  135. }
  136. } yield book
  137. }
  138. def booksForAuthor(authorId: Int) =
  139. db.streamO(dbio.getBooksForAuthor(authorId).transactionally)
  140. .map(Book.fromBooksRow)
  141. }
  142. class LibraryDbio(val profile: JdbcProfile) {
  143. import profile.api._
  144. /* */
  145. def getBooks: StreamingDBIO[Seq[Book], Book] = Query.getBooksInner.result
  146. def insertBookAndGetId(newBook: NewBook): DBIO[Int] =
  147. Query.insertBookGetId += newBook
  148. def insertBookAndGetBook(newBook: NewBook): DBIO[Book] =
  149. Query.insertBookGetBook += newBook
  150. def insertAuthor(newAuthor: NewAuthor): DBIO[Int] =
  151. Query.insertAuthorGetId += newAuthor
  152. def selectBook(id: Int) = Tables.Books.filter(_.bookId === id)
  153. def getAuthor(id: Int) =
  154. Query.selectAuthor(id).map(Author.fromAuthorsTableFn).result.headOption
  155. def deleteBook(id: Int) = selectBook(id).delete
  156. def getBook(id: Int) = selectBook(id)
  157. .map(Book.fromBooksTableFn)
  158. .result
  159. .headOption
  160. def selectBookByIsbn(isbn: String) = Tables.Books.filter(_.isbn === isbn)
  161. def getBooksByTitle(title: String) =
  162. Tables.Books.filter(_.bookTitle === title).map(Book.fromBooksTableFn).result
  163. def getBooksForAuthor(authorId: Int) =
  164. Query.booksForAuthorInner(authorId).result
  165. private object Query {
  166. val getBooksInner = Book.fromBooksTable
  167. val insertBookGetId =
  168. NewBook.fromBooksTable.returning(Tables.Books.map(_.bookId))
  169. val insertBookGetBook = NewBook.fromBooksTable.returning(getBooksInner)
  170. val insertAuthorGetId =
  171. Tables.Authors
  172. .map(a => (a.authorName).mapTo[NewAuthor])
  173. .returning(Tables.Authors.map(_.authorId))
  174. // val insertAuthor = NewAuthor.fromAuthorsTable
  175. def booksForAuthorInner(authorId: Int) = for {
  176. b <- Tables.Books
  177. a <- Tables.Authors
  178. if b.authorId === a.authorId && b.authorId === authorId
  179. } yield b
  180. def selectAuthor(authorId: Int) =
  181. Tables.Authors.filter(_.authorId === authorId)
  182. }
  183. }
  184. trait NoopLibraryService extends LibraryService {
  185. def getBooks: Observable[Book] =
  186. Observable.raiseError(new NotImplementedError)
  187. def getBookById(id: Int): Task[Option[Book]] =
  188. IO.terminate(new NotImplementedError)
  189. def searchBook(
  190. mode: BookSearchMode,
  191. value: String
  192. ): Observable[Book] = Observable.raiseError(new NotImplementedError)
  193. def updateBook(
  194. id: Int,
  195. updateData: BookUpdate
  196. ): IO[LibraryService.Error, Unit] = IO.terminate(new NotImplementedError)
  197. def deleteBook(id: Int): Task[Int] = IO.terminate(new NotImplementedError)
  198. def insertBook(newBook: NewBook): IO[LibraryService.Error, Book] =
  199. IO.terminate(new NotImplementedError)
  200. def insertAuthor(a: NewAuthor): Task[Int] =
  201. IO.terminate(new NotImplementedError)
  202. def booksForAuthor(authorId: Int): Observable[Book] =
  203. Observable.raiseError(new NotImplementedError)
  204. }