package com.weEat.controllers import com.weEat.models.{FoodNode => FoodNodeCollection} import com.weEat.models.{RecipeVersion => RecipeVersionCollection} import com.weEat.models.FoodNodeView import com.weEat.services.MongoDBService import com.weEat.shared.models._ import javax.inject.{Inject,Singleton} import play.api.libs.json._ import play.api.mvc._ import org.bson.types.ObjectId import org.mongodb.scala.model.Filters._ import scala.concurrent.Future import scala.util.{Success,Failure} import com.weEat.models.Authorization import scalaoauth2.provider.{AuthInfoRequest,OAuth2ProviderActionBuilders} import com.weEat.services.OAuth2Service @Singleton class FoodController @Inject()( val controllerComponents: ControllerComponents, oauth: OAuth2Service, db: MongoDBService ) extends BaseController with OAuth2ProviderActionBuilders { implicit val ec = scala.concurrent.ExecutionContext.global import db.withCollection def get(id: String) = Action.async { implicit request: Request[AnyContent] => withCollection(FoodNodeView) { (collection) => collection.find(equal("_id", new ObjectId(id))) .first() .toFuture() .transform({ case Success(null) => Success(NotFound(id)) case Success(x) => Success(Ok(Json.toJson(x))) case Failure(x) => throw x }) }.flatten } def all() = Action.async { implicit request: Request[AnyContent] => withCollection(FoodNodeView) { (collection) => collection.find() .toFuture() .transform({ case Success(x) => Success(Ok(Json.toJson(x))) case Failure(x) => throw x }) }.flatten } def query(q: String) = Action.async { implicit request: Request[AnyContent] => findByName(q).transform({ case Success(x) => Success(Ok(Json.toJson(x))) case Failure(x) => throw x }) } def findByName(q: String): Future[Seq[FoodNodeId]] = withCollection(FoodNodeView) { (collection) => collection.find(regex("name", q, "i")).toFuture() }.flatten def getByFdcId(fdcId: Long): Future[Option[USDANodeId]] = withCollection(FoodNodeView) { (collection) => collection.find(equal("fdcId", fdcId)) .first() .toFuture() .map((foodNode) => Option(foodNode.asInstanceOf[USDANodeId])) }.flatten // def getImage(foodId: String, idx: Int) = Action.async // { implicit request: Request[AnyContent] => // withCollection(FoodImages) {collection => // collection.find(and(equal("food", foodId), equal("index", idx))) // .first() // .toFuture() // .transform({ // case Success(img) => // Success(Ok.streamed(img.data, img.data.length, img.mime)) // case Failure(x) => throw x // }) // }.flatten // ??? // } // def addImageTo(id: String) = Action.async(parse.byteString) // { implicit request: Request[ByteString] => // withCollection(FoodImages) { images => // images.insertOne(FoodImage(None, id, , request.body, request.contentType.get)) // .head().transformWith({ // case Success(img) => withCollection(FoodNode) { foods => // foods.findOneAndUpdate( // equal("id", id), // push("imageIds", img._id.get) // ).head().transform({ // case Success(x) => Success(Ok(x)) // case Failure(x) => // images.deleteOne(eq("_id", img._id.get)) // throw x // }) // } // case Failure(x) => throw x // }) // }.flatten // ??? // } // def deleteImage(foodId: String, imageId: String) = Action.async // { implicit request: Request[AnyContent] => // withCollection(FoodNode) { foods => // foods.findOneAndUpdate( // equal("id", foodId), // pull("imageIds", imageId) // ).head().transformWith({ // case Success(x) => images.deleteOne(eq("_id", img._id.get)) // .head().transform({ // case Success(x) => Success(Ok(x)) // case Failure(x) => throw x // }) // case Failure(x) => throw x // }) // }.flatten // ??? // } def add(uid: Option[String]) = AuthorizedAction[Authorization](oauth) .async(parse.json) { implicit request: AuthInfoRequest[JsValue, Authorization] => try { val food = request.body.as[FoodNode].withId( new ObjectId, uid.flatMap({ (id) => if (request.authInfo.user.hasAdminPermissions) Some(new ObjectId(id)) else None }).getOrElse(request.authInfo.user.userId) ) (food match { case recipeNode: RecipeNodeId => _addRecipe(recipeNode) case node => _addFood(node) }).map((food) => Ok(Json.toJson(food.asInstanceOf[FoodNodeId]))) } catch { case _: JsResultException => Future.successful( BadRequest(s"Could not parse json into a Food node.") ) } } private def _addRecipe(node: RecipeNodeId) = { val (metaNode, versNode) = RecipeMetaNodeId(node) _addFood(metaNode, (_) => _saveVersionIfRecipe(versNode)) .map((_) => node) } private def _addFood( node: FoodId, callback: (FoodId) => Future[FoodId] = Future.successful _ ) = withCollection(FoodNodeCollection) { (collection) => collection.insertOne(node).map({ (res) => node }).head() }.flatten .flatMap(callback) private def _saveVersionIfRecipe(node: RecipeNodeId): Future[FoodNodeId] = withCollection(RecipeVersionCollection) { (collection) => collection.insertOne(node).map({ (res) => node }).head() }.flatten def update(id: String, uid: Option[String]) = AuthorizedAction[Authorization](oauth) .async(parse.json) { implicit request: AuthInfoRequest[JsValue, Authorization] => try { val food = request.body.as[FoodNode].withId( new ObjectId(id), uid.flatMap({ (uid) => if (request.authInfo.user.hasAdminPermissions) Some(new ObjectId(uid)) else None // tflucke@[2023-10-07] Should this query for the current uid instead? }).getOrElse(request.authInfo.user.userId) ) (food match { case recipeNode: RecipeNodeId => _updateRecipe(recipeNode) case node => _updateFood(node) }).map((food) => Ok(Json.toJson(food.asInstanceOf[FoodNodeId]))) } catch { case _: JsResultException => Future.successful( BadRequest(s"Could not parse json into a Food node.") ) } } private def _updateRecipe(node: RecipeNodeId) = { val (metaNode, versNode) = RecipeMetaNodeId(node) _updateFood(metaNode, (_) => _saveVersionIfRecipe(versNode)) .map((_) => node) } private def _updateFood( node: FoodId, callback: (FoodId) => Future[FoodId] = Future.successful _ ) = withCollection(FoodNodeCollection) { (collection) => collection.replaceOne(and( equal("_id", node._id), equal("uid", node.uid) ), node) .head() .transform({ case Success(res) if (res.getModifiedCount > 0) => Success(node) case Success(res) => Failure(new NoSuchElementException("User " + s"${node.uid} does not have a food node ${node._id}")) case Failure(e) => Failure(e) }) }.flatten .flatMap(callback) }