app/controllers/StoragesController.scala (100 lines of code) (raw):
package controllers
import akka.actor.ActorSystem
import akka.stream.Materializer
import auth.BearerTokenAuth
import javax.inject.{Inject, Singleton}
import models.{ProjectEntryRow, StorageEntry, StorageEntryRow, StorageSerializer, StorageType, StorageTypeSerializer}
import play.api.Configuration
import play.api.cache.SyncCacheApi
import play.api.db.slick.DatabaseConfigProvider
import play.api.inject.Injector
import play.api.libs.json._
import play.api.mvc.{Action, BodyParsers, ControllerComponents, Request}
import slick.basic.DatabaseConfig
import slick.jdbc.PostgresProfile
import slick.lifted.TableQuery
import slick.jdbc.PostgresProfile.api._
import scala.concurrent.Future
import scala.util.{Failure, Success}
import scala.concurrent.ExecutionContext.Implicits.global
@Singleton
class StoragesController @Inject()
(override val controllerComponents:ControllerComponents, override val bearerTokenAuth:BearerTokenAuth,
override implicit val config: Configuration, dbConfigProvider: DatabaseConfigProvider, cacheImpl:SyncCacheApi)
(implicit mat:Materializer, system:ActorSystem, injector:Injector)
extends GenericDatabaseObjectController[StorageEntry] with StorageSerializer with StorageTypeSerializer {
implicit val cache:SyncCacheApi = cacheImpl
val knownTypes = List(
StorageType("Local",needsLogin=false,hasSubfolders=true, canVersion = false),
StorageType("ObjectMatrix",needsLogin = true,hasSubfolders = false, canVersion=true),
StorageType("S3",needsLogin = true,hasSubfolders = true,canVersion=true)
)
implicit val dbConfig:DatabaseConfig[PostgresProfile] = dbConfigProvider.get[PostgresProfile]
override def selectid(requestedId: Int) = dbConfig.db.run(
TableQuery[StorageEntryRow].filter(_.id === requestedId).result.asTry
)
override def deleteid(requestedId: Int) = dbConfig.db.run(
TableQuery[StorageEntryRow].filter(_.id === requestedId).delete.asTry
)
override def selectall(startAt:Int, limit:Int) = dbConfig.db.run(
TableQuery[StorageEntryRow].length.result.zip(
TableQuery[StorageEntryRow].drop(startAt).take(limit).result
)
).map(results=>{
Success((
results._1,
results._2.map(_.copy(password=Some("****")))
))
}).recover({
case err:Throwable=>Failure(err)
})
override def jstranslate(result: Seq[StorageEntry]) = result.asInstanceOf[Seq[StorageEntry]] //implicit translation should handle this
override def jstranslate(result: StorageEntry) = result //implicit translation should handle this
override def insert(storageEntry: StorageEntry,uid:String) = dbConfig.db.run(
(TableQuery[StorageEntryRow] returning TableQuery[StorageEntryRow].map(_.id) += storageEntry).asTry
)
/**
* sets the password field in the provided [[StorageEntry]] model to the same value as the current one
* this is so that updates can be made to the record without sending storage passwords to the client and back again every time
* @param itemId item ID to update
* @param updatedEntry updated storage entry
* @return a Future, contianing a Try, containing the updated storage entry with the previous password set
*/
def reconstitutePassword(itemId:Int, updatedEntry:StorageEntry) = {
val maybeRealPasswordFut = selectid(itemId).map(_.map(_.headOption.flatMap(_.password)))
maybeRealPasswordFut.map(_.map(maybeRealPassword => updatedEntry.copy(password = maybeRealPassword)))
}
/**
* protocol method to perform update on an entry. This implementation has a special behaviour where if the password field is set to
* '****' (four asterisks) it means "don't change password" and the previous password is retrieved and placed into the update
* @param itemId item ID to update
* @param entry new record to overwrite it with
*/
override def dbupdate(itemId:Int, entry:StorageEntry) = {
val reconsEntryFuture = if(entry.password.contains("****")){ //if the password is set to this special value, we don't want to change it.
//grab the existing value and use that.
reconstitutePassword(itemId, entry).map({ //this is a bit messy but will do for the time being. We want a Future that
//fails on error, so we can compose later on, but the protocol requires a Future[Try] from selectid. So squash it here.
case Success(result)=>result
case Failure(err)=>throw err
})
} else {
Future(entry)
}
reconsEntryFuture.flatMap(reconsEntry=> {
val newRecord = reconsEntry.id match {
case Some(id) => reconsEntry
case None => reconsEntry.copy(id = Some(itemId))
}
dbConfig.db.run(
TableQuery[StorageEntryRow].filter(_.id === itemId).update(newRecord).asTry
)
})
}
override def validate(request:Request[JsValue]) = request.body.validate[StorageEntry]
def types = IsAuthenticated {uid=> {request=>
Ok(Json.obj("status"->"ok","types"->this.knownTypes))
}}
override def shouldCreateEntry(newEntry: StorageEntry): Either[String, Boolean] = {
logger.debug(s"Got new entry $newEntry")
newEntry.rootpath match {
case Some(rootpath) =>
newEntry.getStorageDriver match {
case Some(storageDriver) =>
if(storageDriver.pathExists(rootpath, 0))
Right(true)
else
Left(s"Path $rootpath does not exist")
case None=>
Left(s"No storage driver defined for storage type ${newEntry.storageType}")
}
case None=>
Left("No root path was set for the storage")
}
}
}