io.finch.petstore.petstoreDb.scala Maven / Gradle / Ivy
The newest version!
package io.finch.petstore
import com.twitter.io.Buf
import com.twitter.util.Future
import scala.collection.mutable
/**
* Provides a great majority of the service methods that allow Users to interact with the Pets in the
* store and to get information about them.
*/
class PetstoreDb {
private[this] val pets = mutable.Map.empty[Long, Pet]
private[this] val tags = mutable.Map.empty[Long, Tag]
private[this] val cat = mutable.Map.empty[Long, Category]
private[this] val orders = mutable.Map.empty[Long, Order]
private[this] val photos = mutable.Map.empty[Long, Buf]
private[this] val users = mutable.Map.empty[Long, User]
/**
* GET: Finds a [[Pet]] by its ID.
*
* @param id The ID of the [[Pet]] we're looking for
* @return The [[Pet]] object
*/
def getPet(id: Long): Future[Pet] = Future(
pets.synchronized {
pets.getOrElse(id, throw MissingPet("Your pet doesn't exist! :("))
}
)
/**
* Helper method for addPet: Adds a [[Tag]] to the tag map.
* This differs from the Swagger example in that the Tag's ID is autogenerated
* rather than passed by the user. This is done to avoid potential memory and storage
* sabotage. Tags with given IDs will be rejected.
* @param inputTag The tag we want to add
* @return The tag just added
*/
private def addTag(inputTag: Tag): Future[Tag] =
tags.synchronized {
inputTag.id match {
case Some(x) => Future.exception(InvalidInput("New tag should not contain an id"))
case _ => tags.synchronized {
val genId = if (tags.isEmpty) 0 else tags.keys.max + 1
tags(genId) = inputTag.copy(id = Some(genId))
Future(tags(genId))
}
}
}
/**
* Helper method for addPet: Adds a [[Category]] to the category map.
* This differs from the Swagger example in that the Category's ID is autogenerated
* rather than passed by the user. This is done to avoid potential memory and storage
* sabotage. Category with given IDs will be rejected.
* @param c The Category we want to add
* @return The Category just added
*/
private def addCat(c: Category): Future[Category] =
cat.synchronized {
c.id match{
case Some(x) => Future.exception(InvalidInput("New Category should not contain an id"))
case None => cat.synchronized{
val genId = if (cat.isEmpty) 0 else cat.keys.max + 1
cat(genId) = c.copy(id = Some(genId))
Future(cat(genId))
}
}
}
/**
* POST: Adds a [[Pet]] to the database, validating that the ID is empty.
*
* @param inputPet the new pet
* @return the id of the new pet
*/
def addPet(inputPet: Pet): Future[Long] =
inputPet.id match {
case Some(_) => Future.exception(InvalidInput("New pet should not contain an id"))
case None => pets.synchronized {
val id = if (pets.isEmpty) 0 else pets.keys.max + 1
pets(id) = inputPet.copy(id = Some(id))
inputPet.tags match{
case Some(tagList) => tagList.map(addTag)
case None => None
}
inputPet.category match{
case Some(c) => addCat(c)
case None => None
}
Future.value(id)
}
}
/**
* PUT: Updates an existing [[Pet]], while validating that a current version of
* the [[Pet]] exists (a.k.a. an existing [[Pet]] has the same id as inputPet).
* @param inputPet The [[Pet]] we want to replace the current [[Pet]] with. Must be passed with the original Pet's ID.
* @return The updated pet
*/
def updatePet(inputPet: Pet): Future[Pet] = inputPet.id match {
case Some(id) =>
if (pets.contains(id)) pets.synchronized {
pets(id) = inputPet
Future.value(inputPet)
} else {
Future.exception(MissingPet("Invalid id: doesn't exist"))
}
case None => Future.exception(MissingIdentifier(s"Missing id for pet: $inputPet"))
}
/**
* Helper method: Allows the user to get all the pets in the database.
* @return A sequence of all pets in the store.
*/
private def allPets: Future[Seq[Pet]] = Future.value(
pets.synchronized(pets.toList.sortBy(_._1).map(_._2))
)
/**
* GET: Find pets by status. Multiple statuses can be provided with comma-separated strings.
* @param findStati The status(es) to filter Pets by.
* @return A sequence of all Pets with the given status(es).
*/
def getPetsByStatus(findStati: Seq[String]): Future[Seq[Pet]] = {
pets.synchronized(
for {
petList <- allPets
} yield petList.filter(p => p.status.exists(c => findStati.contains(c.code)))
)
}
/**
* GET: Find pets by [[Tag]]. Multiple tags can be provided with comma-separated strings.
* @param findTags A sequence of all the Tags we want to find matches for.
* @return A sequence of Pets that contain all given Tags.
*/
def findPetsByTag(findTags: Seq[String]): Future[Seq[Pet]] = {
val matchPets = for {
p <- pets.values
tagList <- p.tags
pTagStr = tagList.map(_.name)
if findTags.forall(pTagStr.contains)
} yield p
Future(matchPets.toSeq.sortBy(_.id))
}
/**
* DELETE: Deletes a [[Pet]] from the database.
* @param id The ID of the Pet to be deleted.
* @return true if deletion was successful. false otherwise.
*/
def deletePet(id: Long): Future[Unit] =
pets.synchronized {
if (pets.contains(id)) {
pets.remove(id)
Future.Unit
} else Future.exception(
MissingPet(s"Pet with id $id does not exist and cannot be deleted")
)
}
/**
* POST: Update a [[Pet]] in the store with form data
* @param petId ID of the Pet to be updated.
* @param n New name of the Pet.
* @param s New status of the Pet.
* @return The updated Pet.
*/
def updatePetNameStatus(petId: Long, n: Option[String], s: Option[Status]): Future[Pet] = {
if (pets.contains(petId)) pets.synchronized {
s.foreach { stat => pets(petId) = pets(petId).copy(status = Some(stat)) }
n.foreach { name => pets(petId) = pets(petId).copy(name = name) }
Future.value(pets(petId))
} else Future.exception(MissingPet("Invalid id: doesn't exist"))
}
/**
* POST: Upload an image.
* @param petId The ID of the pet the image corresponds to.
* @param data The image in byte form.
* @return The url of the uploaded photo.
*/
def addImage(petId: Long, data: Buf): Future[String] =
pets.synchronized {
for {
pet <- getPet(petId)
photoId = photos.synchronized {
val nextId = if (photos.isEmpty) 0 else photos.keys.max + 1
photos(nextId) = data
nextId
}
url = s"/photos/$photoId"
_ <- updatePet(pet.copy(photoUrls = pet.photoUrls :+ url))
} yield url
}
/**
* GET: Returns the current [[Inventory]].
* @return A map of how many pets currently correspond to which Status type.
*/
def getInventory: Future[Inventory] = Future.value(
pets.synchronized {
val stock: Map[Status, Int] = pets.groupBy(_._2.status).flatMap {
case (Some(status), keyVal) => Some(status -> keyVal.size)
case (None, _) => None
}
val available: Int = stock(Available)
val pending: Int = stock(Pending)
val adopted: Int = stock(Adopted)
Inventory(available, pending, adopted)
}
)
/**
* POST: Place an [[Order]] for a [[Pet]].
* @param order The order object to be placed with the petstore.
* @return The autogenerated ID of the order object.
*/
def addOrder(order: Order): Future[Long] =
orders.synchronized {
order.id match{
case Some(_) => Future.exception(InvalidInput("New order should not contain an id"))
case None => orders.synchronized{
val genId = if (orders.isEmpty) 0 else orders.keys.max + 1
orders(genId) = order.copy(id = Some(genId))
Future.value(genId)
}
}
}
/**
* DELETE: Delete purchase [[Order]] by ID
* @param id The ID of the order to delete.
* @return true if deletion was successful. false otherwise.
*/
def deleteOrder(id: Long): Future[Boolean] = Future.value(
orders.synchronized {
if (orders.contains(id)) {
orders.remove(id)
true
} else false
}
)
/**
* GET: Find purchase [[Order]] by ID
* @param id The ID of the order to find.
* @return The Order object in question.
*/
def findOrder(id: Long): Future[Order] = Future.value(
orders.synchronized {
orders.getOrElse(id, throw OrderNotFound("Your order doesn't exist! :("))
}
)
/**
* POST: Create a [[User]].
* @param newGuy The User we want to add to the database.
* @return The user name of the added User.
*/
def addUser(newGuy: User): Future[String] =
users.synchronized {
val inputName: String = newGuy.username
if (users.values.exists(_.username == inputName))
throw RedundantUsername(s"Username $inputName is already taken.")
else {
newGuy.id match {
case Some(_) => Future.exception(InvalidInput("New user should not contain an id"))
case None => users.synchronized {
val genId = if (users.isEmpty) 0 else users.keys.max + 1
users(genId) = newGuy.copy(id = Some(genId))
Future(newGuy.username)
}
}
}
}
/**
* GET: Get [[User]] by username, assume all usernames are unique.
* @param name The username of the User we want to find.
* @return The User in question.
*/
def getUser(name: String): Future[User] =
users.synchronized {
users.values.find(_.username == name) match {
case Some(user) => Future.value(user)
case None => Future.exception(MissingUser("This user doesn't exist!"))
}
}
/**
* DELETE: Delete a [[User]] by their username.
* @param name The username of the User to be deleted.
*/
def deleteUser(name: String): Future[Unit] =
users.synchronized {
getUser(name).flatMap {u:User =>
u.id.foreach{ num =>
users.remove(num)
}
Future.Unit
}
}
/**
* PUT: Update [[User]]. Note that usernames cannot be changed because they are the unique identifiers by which the
* system finds existing users. Although Swagger doesn't specify this, if the username of an existing user is
* changed, the API will no longer be able to find the user or the user's unique id.
* @param betterUser The better, updated version of the old User.
* @return The betterUser.
*/
def updateUser(betterUser: User): Future[User] =
users.synchronized {
for {
user <- getUser(betterUser.username)
u = betterUser.copy(id = user.id)
} yield {
u.id.foreach { id =>
users(id) = u
}
u
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy