FoodController.scala 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. package com.weEat.controllers
  2. import com.weEat.models.{FoodNode => FoodNodeCollection}
  3. import com.weEat.models.{RecipeVersion => RecipeVersionCollection}
  4. import com.weEat.models.FoodNodeView
  5. import com.weEat.services.MongoDBService
  6. import com.weEat.shared.models._
  7. import javax.inject.{Inject,Singleton}
  8. import play.api.libs.json._
  9. import play.api.mvc._
  10. import org.bson.types.ObjectId
  11. import org.mongodb.scala.model.Filters._
  12. import scala.concurrent.Future
  13. import scala.util.{Success,Failure}
  14. import com.weEat.models.Authorization
  15. import scalaoauth2.provider.{AuthInfoRequest,OAuth2ProviderActionBuilders}
  16. import com.weEat.services.OAuth2Service
  17. @Singleton
  18. class FoodController @Inject()(
  19. val controllerComponents: ControllerComponents,
  20. oauth: OAuth2Service,
  21. db: MongoDBService
  22. ) extends BaseController
  23. with OAuth2ProviderActionBuilders {
  24. implicit val ec = scala.concurrent.ExecutionContext.global
  25. import db.withCollection
  26. def get(id: String) = Action.async
  27. { implicit request: Request[AnyContent] =>
  28. withCollection(FoodNodeView) { (collection) =>
  29. collection.find(equal("_id", new ObjectId(id)))
  30. .first()
  31. .toFuture()
  32. .transform({
  33. case Success(null) => Success(NotFound(id))
  34. case Success(x) => Success(Ok(Json.toJson(x)))
  35. case Failure(x) => throw x
  36. })
  37. }.flatten
  38. }
  39. def all() = Action.async
  40. { implicit request: Request[AnyContent] =>
  41. withCollection(FoodNodeView) { (collection) =>
  42. collection.find()
  43. .toFuture()
  44. .transform({
  45. case Success(x) => Success(Ok(Json.toJson(x)))
  46. case Failure(x) => throw x
  47. })
  48. }.flatten
  49. }
  50. def query(q: String) = Action.async
  51. { implicit request: Request[AnyContent] =>
  52. findByName(q).transform({
  53. case Success(x) => Success(Ok(Json.toJson(x)))
  54. case Failure(x) => throw x
  55. })
  56. }
  57. def findByName(q: String): Future[Seq[FoodNodeId]] =
  58. withCollection(FoodNodeView) { (collection) =>
  59. collection.find(regex("name", q, "i")).toFuture()
  60. }.flatten
  61. def getByFdcId(fdcId: Long): Future[Option[USDANodeId]] =
  62. withCollection(FoodNodeView) { (collection) =>
  63. collection.find(equal("fdcId", fdcId))
  64. .first()
  65. .toFuture()
  66. .map((foodNode) => Option(foodNode.asInstanceOf[USDANodeId]))
  67. }.flatten
  68. // def getImage(foodId: String, idx: Int) = Action.async
  69. // { implicit request: Request[AnyContent] =>
  70. // withCollection(FoodImages) {collection =>
  71. // collection.find(and(equal("food", foodId), equal("index", idx)))
  72. // .first()
  73. // .toFuture()
  74. // .transform({
  75. // case Success(img) =>
  76. // Success(Ok.streamed(img.data, img.data.length, img.mime))
  77. // case Failure(x) => throw x
  78. // })
  79. // }.flatten
  80. // ???
  81. // }
  82. // def addImageTo(id: String) = Action.async(parse.byteString)
  83. // { implicit request: Request[ByteString] =>
  84. // withCollection(FoodImages) { images =>
  85. // images.insertOne(FoodImage(None, id, , request.body, request.contentType.get))
  86. // .head().transformWith({
  87. // case Success(img) => withCollection(FoodNode) { foods =>
  88. // foods.findOneAndUpdate(
  89. // equal("id", id),
  90. // push("imageIds", img._id.get)
  91. // ).head().transform({
  92. // case Success(x) => Success(Ok(x))
  93. // case Failure(x) =>
  94. // images.deleteOne(eq("_id", img._id.get))
  95. // throw x
  96. // })
  97. // }
  98. // case Failure(x) => throw x
  99. // })
  100. // }.flatten
  101. // ???
  102. // }
  103. // def deleteImage(foodId: String, imageId: String) = Action.async
  104. // { implicit request: Request[AnyContent] =>
  105. // withCollection(FoodNode) { foods =>
  106. // foods.findOneAndUpdate(
  107. // equal("id", foodId),
  108. // pull("imageIds", imageId)
  109. // ).head().transformWith({
  110. // case Success(x) => images.deleteOne(eq("_id", img._id.get))
  111. // .head().transform({
  112. // case Success(x) => Success(Ok(x))
  113. // case Failure(x) => throw x
  114. // })
  115. // case Failure(x) => throw x
  116. // })
  117. // }.flatten
  118. // ???
  119. // }
  120. def add(uid: Option[String]) = AuthorizedAction[Authorization](oauth)
  121. .async(parse.json)
  122. { implicit request: AuthInfoRequest[JsValue, Authorization] =>
  123. try {
  124. val food = request.body.as[FoodNode].withId(
  125. new ObjectId,
  126. uid.flatMap({ (id) =>
  127. if (request.authInfo.user.hasAdminPermissions) Some(new ObjectId(id))
  128. else None
  129. }).getOrElse(request.authInfo.user.userId)
  130. )
  131. (food match {
  132. case recipeNode: RecipeNodeId => _addRecipe(recipeNode)
  133. case node => _addFood(node)
  134. }).map((food) => Ok(Json.toJson(food.asInstanceOf[FoodNodeId])))
  135. }
  136. catch {
  137. case _: JsResultException => Future.successful(
  138. BadRequest(s"Could not parse json into a Food node.")
  139. )
  140. }
  141. }
  142. private def _addRecipe(node: RecipeNodeId) = {
  143. val (metaNode, versNode) = RecipeMetaNodeId(node)
  144. _addFood(metaNode, (_) => _saveVersionIfRecipe(versNode))
  145. .map((_) => node)
  146. }
  147. private def _addFood(
  148. node: FoodId,
  149. callback: (FoodId) => Future[FoodId] = Future.successful _
  150. ) = withCollection(FoodNodeCollection) { (collection) =>
  151. collection.insertOne(node).map({ (res) => node }).head()
  152. }.flatten
  153. .flatMap(callback)
  154. private def _saveVersionIfRecipe(node: RecipeNodeId): Future[FoodNodeId] =
  155. withCollection(RecipeVersionCollection) { (collection) =>
  156. collection.insertOne(node).map({ (res) => node }).head()
  157. }.flatten
  158. def update(id: String, uid: Option[String]) =
  159. AuthorizedAction[Authorization](oauth)
  160. .async(parse.json)
  161. { implicit request: AuthInfoRequest[JsValue, Authorization] =>
  162. try {
  163. val food = request.body.as[FoodNode].withId(
  164. new ObjectId(id),
  165. uid.flatMap({ (uid) =>
  166. if (request.authInfo.user.hasAdminPermissions) Some(new ObjectId(uid))
  167. else None
  168. // tflucke@[2023-10-07] Should this query for the current uid instead?
  169. }).getOrElse(request.authInfo.user.userId)
  170. )
  171. (food match {
  172. case recipeNode: RecipeNodeId => _updateRecipe(recipeNode)
  173. case node => _updateFood(node)
  174. }).map((food) => Ok(Json.toJson(food.asInstanceOf[FoodNodeId])))
  175. }
  176. catch {
  177. case _: JsResultException => Future.successful(
  178. BadRequest(s"Could not parse json into a Food node.")
  179. )
  180. }
  181. }
  182. private def _updateRecipe(node: RecipeNodeId) = {
  183. val (metaNode, versNode) = RecipeMetaNodeId(node)
  184. _updateFood(metaNode, (_) => _saveVersionIfRecipe(versNode))
  185. .map((_) => node)
  186. }
  187. private def _updateFood(
  188. node: FoodId,
  189. callback: (FoodId) => Future[FoodId] = Future.successful _
  190. ) = withCollection(FoodNodeCollection) { (collection) =>
  191. collection.replaceOne(and(
  192. equal("_id", node._id),
  193. equal("uid", node.uid)
  194. ), node)
  195. .head()
  196. .transform({
  197. case Success(res) if (res.getModifiedCount > 0) => Success(node)
  198. case Success(res) => Failure(new NoSuchElementException("User " +
  199. s"${node.uid} does not have a food node ${node._id}"))
  200. case Failure(e) => Failure(e)
  201. })
  202. }.flatten
  203. .flatMap(callback)
  204. }