backend/app/model/annotations/Workspace.scala (157 lines of code) (raw):

package model.annotations import model._ import model.frontend._ import model.frontend.user.PartialUser import model.user.DBUser import org.neo4j.driver.v1.Value import play.api.libs.json.Json.JsValueWrapper import play.api.libs.json.{Format, JsNumber, JsObject, JsResult, JsString, JsSuccess, JsValue, Json, Reads, Writes} sealed abstract class ProcessingStage object ProcessingStage { case class Processing(tasksRemaining: Int, note: Option[String]) extends ProcessingStage case object Processed extends ProcessingStage case object Failed extends ProcessingStage implicit val format: Format[ProcessingStage] = Format( (value: JsValue) => (value \ "type").validate[String].flatMap { case "processing" => for { tasksRemaining <- (value \ "tasksRemaining").validate[Int] note <- (value \ "note").validateOpt[String] } yield { Processing(tasksRemaining, note) } case "processed" => JsSuccess(Processed) case "failed" => JsSuccess(Failed) } , { case Processing(tasksRemaining, note) => val mandatory = Map("type" -> JsString("processing"), "tasksRemaining" -> JsNumber(tasksRemaining)) val optional = note.map(n => "note" -> JsString(n)) JsObject(mandatory ++ optional) case Processed => Json.obj("type" -> "processed") case Failed => Json.obj("type" -> "failed") } ) } sealed trait WorkspaceEntry { def addedBy: PartialUser def addedOn: Option[Long] def maybeParentId: Option[String] } case class WorkspaceNode( addedBy: PartialUser, addedOn: Option[Long], maybeParentId: Option[String] ) extends WorkspaceEntry case class WorkspaceLeaf( addedBy: PartialUser, addedOn: Option[Long], maybeParentId: Option[String], processingStage: ProcessingStage, uri: String, mimeType: String, // there are nodes in Playground where this is null, which breaks things // if I don't make it optional size: Option[Long], ) extends WorkspaceEntry object WorkspaceEntry { implicit val format = new Format[WorkspaceEntry] { override def writes(entry: WorkspaceEntry): JsValue = entry match { case e: WorkspaceNode => nodeFormat.writes(e) case e: WorkspaceLeaf => leafFormat.writes(e) } override def reads(json: JsValue): JsResult[WorkspaceEntry] = (json \ "uri").toOption match { case Some(_) => leafFormat.reads(json) case _ => nodeFormat.reads(json) } } private val nodeFormat: Format[WorkspaceNode] = Json.format[WorkspaceNode] private val leafFormat: Format[WorkspaceLeaf] = Json.format[WorkspaceLeaf] def fromNeo4jValue( v: Value, createdBy: PartialUser, maybeParentId: Option[String], numberOfTodos: Int, note: Option[String], hasFailures: Boolean ): TreeEntry[WorkspaceEntry] = { val processingStage = if (hasFailures) { ProcessingStage.Failed } else if (numberOfTodos > 0) { ProcessingStage.Processing(numberOfTodos, note) } else { ProcessingStage.Processed } v.get("type").asString() match { case "folder" => TreeNode[WorkspaceNode]( id = v.get("id").asString(), name = v.get("name").asString(), data = WorkspaceNode( addedBy = createdBy, addedOn = v.get("addedOn").optionally(_.asLong()), maybeParentId = maybeParentId, ), children = List.empty ) case "file" => TreeLeaf[WorkspaceLeaf]( id = v.get("id").asString(), name = v.get("name").asString(), // we load the entire tree up front for workspaces, so no leaves are expandable // (they'd only be expandable if they had children which we were not yet returning to the client) isExpandable = false, data = WorkspaceLeaf( addedBy = createdBy, addedOn = v.get("addedOn").optionally(_.asLong()), maybeParentId = maybeParentId, processingStage = processingStage, uri = v.get("uri").asString(), mimeType = v.get("mimeType").asString(), // there are nodes in Playground where this is null, which breaks things // if I don't make it optional size = v.get("addedOn").optionally(_.asLong()), ) ) } } } case class WorkspaceMetadata(id: String, name: String, isPublic: Boolean, tagColor: String, owner: PartialUser, followers: List[PartialUser] ) object WorkspaceMetadata { implicit val write: Writes[WorkspaceMetadata] = Json.writes[WorkspaceMetadata] implicit val read: Reads[WorkspaceMetadata] = Json.reads[WorkspaceMetadata] def fromNeo4jValue(v: Value, owner: DBUser, followers: List[DBUser]): WorkspaceMetadata = { WorkspaceMetadata( v.get("id").asString(), v.get("name").asString(), v.get("isPublic").asBoolean(), v.get("tagColor").asString(), owner.toPartial, followers.map(_.toPartial) ) } } case class Workspace(id: String, name: String, isPublic: Boolean, tagColor: String, owner: PartialUser, followers: List[PartialUser], rootNode: TreeEntry[WorkspaceEntry]) object Workspace { implicit val write = Json.writes[Workspace] implicit val read: Reads[Workspace] = Json.reads[Workspace] def fromMetadataAndRootNode(metadata: WorkspaceMetadata, rootNode: TreeEntry[WorkspaceEntry]) = Workspace( id = metadata.id, name = metadata.name, isPublic = metadata.isPublic, tagColor = metadata.tagColor, owner = metadata.owner, followers = metadata.followers, rootNode = rootNode ) }