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.

270 lines
7.9 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
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.getBookById(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. author <- db.runL(dbio.getAuthorByName(value)).flatMap {
  60. case None =>
  61. IO.raiseError(
  62. new EntityDoesNotExist(
  63. s"Author with name=$value does not exist"
  64. )
  65. )
  66. case Some(value) => IO.pure(value)
  67. }
  68. books = db
  69. .streamO(dbio.getBooksForAuthor(author.authorId))
  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 <- Tables.Books.filter(_.bookId === 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 = Tables.Books.filter(_.bookId === 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. _ <- Tables.Books
  107. .filter(_.isbn === 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 getAuthor(id: Int): DBIO[Option[Author]] =
  153. Query.selectAuthor(id).map(Author.fromAuthorsTableFn).result.headOption
  154. def getAuthorByName(name: String): DBIO[Option[Author]] =
  155. Tables.Authors
  156. .filter(_.authorName === name)
  157. .map(Author.fromAuthorsTableFn)
  158. .result
  159. .headOption
  160. def deleteBook(id: Int): DBIO[Int] = Query.selectBookById(id).delete
  161. def getBookById(id: Int): DBIO[Option[Book]] = Query
  162. .selectBookById(id)
  163. .map(Book.fromBooksTableFn)
  164. .result
  165. .headOption
  166. def getBooksByTitle(title: String): StreamingDBIO[Seq[Book], Book] =
  167. Tables.Books.filter(_.bookTitle === title).map(Book.fromBooksTableFn).result
  168. def getBooksForAuthor(
  169. authorId: Int
  170. ): StreamingDBIO[Seq[Tables.BooksRow], Tables.BooksRow] =
  171. Query.booksForAuthorInner(authorId).result
  172. private object Query {
  173. val getBooksInner = Book.fromBooksTable
  174. val insertBookGetId =
  175. NewBook.fromBooksTable.returning(Tables.Books.map(_.bookId))
  176. val insertBookGetBook = NewBook.fromBooksTable.returning(getBooksInner)
  177. val insertAuthorGetId =
  178. Tables.Authors
  179. .map(a => (a.authorName).mapTo[NewAuthor])
  180. .returning(Tables.Authors.map(_.authorId))
  181. // val insertAuthor = NewAuthor.fromAuthorsTable
  182. def booksForAuthorInner(authorId: Int) = for {
  183. b <- Tables.Books
  184. a <- Tables.Authors
  185. if b.authorId === a.authorId && b.authorId === authorId
  186. } yield b
  187. def selectAuthor(authorId: Int) =
  188. Tables.Authors.filter(_.authorId === authorId)
  189. def selectBookById(id: Int) = Tables.Books.filter(_.bookId === id)
  190. def selectBookByIsbn(isbn: String) = Tables.Books.filter(_.isbn === isbn)
  191. }
  192. }
  193. trait NoopLibraryService extends LibraryService {
  194. def getBooks: Observable[Book] =
  195. Observable.raiseError(new NotImplementedError)
  196. def getBookById(id: Int): Task[Option[Book]] =
  197. IO.terminate(new NotImplementedError)
  198. def searchBook(
  199. mode: BookSearchMode,
  200. value: String
  201. ): Observable[Book] = Observable.raiseError(new NotImplementedError)
  202. def updateBook(
  203. id: Int,
  204. updateData: BookUpdate
  205. ): IO[LibraryService.Error, Unit] = IO.terminate(new NotImplementedError)
  206. def deleteBook(id: Int): Task[Int] = IO.terminate(new NotImplementedError)
  207. def insertBook(newBook: NewBook): IO[LibraryService.Error, Book] =
  208. IO.terminate(new NotImplementedError)
  209. def insertAuthor(a: NewAuthor): Task[Int] =
  210. IO.terminate(new NotImplementedError)
  211. def booksForAuthor(authorId: Int): Observable[Book] =
  212. Observable.raiseError(new NotImplementedError)
  213. }