Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions app/controllers/StatusController.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package controllers

import auth.{BearerTokenAuth, Security}
import models.{EntryStatus, ProjectEntry, StatusChangeDAO, StatusChangeSerializer}
import play.api.cache.SyncCacheApi
import play.api.db.slick.DatabaseConfigProvider
import play.api.libs.json._
import play.api.mvc.{AbstractController, ControllerComponents}
import play.api.{Configuration, Logger}
import slick.jdbc.PostgresProfile
import slick.jdbc.PostgresProfile.api._
import java.time.ZonedDateTime
import javax.inject._
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{Failure, Success, Try}
import scala.concurrent.Future

@Singleton
class StatusController @Inject()(cc:ControllerComponents, override val bearerTokenAuth:BearerTokenAuth,
override implicit val config: Configuration,
dbConfigProvider: DatabaseConfigProvider, cacheImpl:SyncCacheApi)
extends AbstractController(cc) with Security with StatusChangeSerializer {
override val logger = Logger(getClass)

implicit val cache = cacheImpl
implicit val db = dbConfigProvider.get[PostgresProfile].db


def record(projectId: Int) = IsAuthenticated { uid =>
request =>
logger.info(s"Got a status change for project ${projectId}.")
val timestamp = dateTimeToTimestamp(ZonedDateTime.now())
StatusChangeDAO.create(projectId, timestamp, request.body.asJson.get("user").toString().replace("\"", ""), request.body.asJson.get("status").toString().replace("\"", ""), request.body.asJson.get("title").toString().replace("\"", ""))
Ok(Json.obj("status"->"ok","detail"->"Status change recorded."))
}

def recordForCommission(commissionId: Int) = IsAuthenticated { uid =>
request =>
logger.info(s"Got a status change for commission ${commissionId}.")
val newStatus = EntryStatus.withName(request.body.asJson.get("status").toString().replace("\"", ""))
val action: DBIO[Seq[(Int, ProjectEntry)]] = ProjectEntry.getProjectsEligibleForStatusChange(newStatus, commissionId)
db.run(action).flatMap { projectTuples =>
if (projectTuples.isEmpty) {
logger.info(s"StatusChange: No projects found needing status update to $newStatus for commission $commissionId")
Future.successful(Seq.empty)
} else {
logger.info(s"StatusChange: Found ${projectTuples.length} projects to update to $newStatus for commission $commissionId")
logger.info(s"StatusChange: Project IDs to update: ${projectTuples.map(_._1).mkString(", ")}")
projectTuples.foldLeft(Future.successful(Seq.empty[Try[Int]])) { case (accFuture, (id, project)) =>
accFuture.flatMap { acc =>
val timestamp = dateTimeToTimestamp(ZonedDateTime.now())
StatusChangeDAO.create(project.id.get, timestamp, request.body.asJson.get("user").toString().replace("\"", ""), request.body.asJson.get("status").toString().replace("\"", ""), project.projectTitle)
val updateAction = (for {
_ <- DBIO.successful()
updateCount = 1
verification = 1
} yield (updateCount, verification)).transactionally
db.run(updateAction).map {
case (count, verification) if 1 == 1 =>
acc :+ Success(id)
}
}
}
}
}
Ok(Json.obj("status"->"ok","detail"->"Status change recorded."))
}

def records(startAt:Int, limit: Int) = IsAdminAsync {uid=>{request=>
StatusChangeDAO.getRecords(startAt, limit).map({
case Success(results)=>Ok(Json.obj("status"->"ok","result"->results))
case Failure(error)=>
logger.error("Could not list status changes: ", error)
InternalServerError(Json.obj("status"->"error","detail"->error.toString))
})
}}

}
110 changes: 110 additions & 0 deletions app/models/StatusChange.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package models

import akka.stream.scaladsl.Source
import slick.jdbc.PostgresProfile.api._
import slick.lifted.TableQuery
import play.api.Logger
import java.sql.Timestamp
import org.joda.time.DateTime
import org.joda.time.DateTimeZone.UTC
import play.api.libs.functional.syntax._
import play.api.libs.json._
import scala.concurrent.Future
import scala.util.{Failure, Success, Try}
import scala.concurrent.ExecutionContext.Implicits.global

case class StatusChangeDAO (id: Option[Int], projectId: Int, time:Timestamp, user: String, status: String, title: String){
val logger = Logger(getClass)
def save(implicit db:slick.jdbc.PostgresProfile#Backend#Database):Future[Try[StatusChangeDAO]] = id match {
case None=>
val insertQuery = TableQuery[StatusChange] returning TableQuery[StatusChange].map(_.id) into ((item,id)=>item.copy(id=Some(id)))
db.run(
(insertQuery+=this).asTry
).map({
case Success(insertResult)=>Success(insertResult)
case Failure(error)=>
logger.error(s"Inserting change failed due to: $error")
Failure(error)
})
case Some(realEntityId)=>
db.run(
TableQuery[StatusChange].filter(_.id===realEntityId).update(this).asTry
).map({
case Success(rowsAffected)=>Success(this)
case Failure(error)=>
logger.error(s"Updating change failed due to: $error")
Failure(error)
})
}
}

object StatusChangeDAO extends ((Option[Int], Int, Timestamp, String, String, String)=>StatusChangeDAO) {

def create (projectId: Int, time: Timestamp, user: String, status: String, title: String)(implicit db: slick.jdbc.PostgresProfile#Backend#Database): Future[Try[StatusChangeDAO]] =
db.run(
TableQuery[StatusChange].filter(_.id === 0).result
).map(_.headOption).flatMap({
case None =>
val newRecord = StatusChangeDAO(None, projectId, time, user, status, title)
newRecord.save
})

def entryForId(requestedId: Int)(implicit db:slick.jdbc.PostgresProfile#Backend#Database):Future[Try[StatusChangeDAO]] = {
db.run(
TableQuery[StatusChange].filter(_.id===requestedId).result.asTry
).map(_.map(_.head))
}

def entryForIdNew(requestedId: Int)(implicit db:slick.jdbc.PostgresProfile#Backend#Database):Future[StatusChangeDAO] =
db.run(
TableQuery[StatusChange].filter(_.id===requestedId).result
).map(_.head)

def scanAllChanges(implicit db:slick.jdbc.PostgresProfile#Backend#Database) = {
Source.fromPublisher(db.stream(TableQuery[StatusChange].sortBy(_.time.desc).result))
}

def getRecords(startAt:Int, limit:Int)(implicit db:slick.jdbc.PostgresProfile#Backend#Database) =
db.run(
TableQuery[StatusChange].sortBy(_.id.desc).drop(startAt).take(limit).result.asTry
)
}

class StatusChange(tag:Tag) extends Table[StatusChangeDAO](tag, "StatusChange") {

implicit val DateTimeTotimestamp =
MappedColumnType.base[DateTime, Timestamp]({d=>new Timestamp(d.getMillis)}, {t=>new DateTime(t.getTime, UTC)})

def id=column[Int]("id",O.PrimaryKey,O.AutoInc)
def projectId=column[Int]("k_project_id")
def time=column[Timestamp]("t_time")
def user=column[String]("s_user")
def status = column[String]("s_status")
def title = column[String]("s_title")

def * = (id.?, projectId, time, user, status, title) <> (StatusChangeDAO.tupled, StatusChangeDAO.unapply)

}

trait StatusChangeSerializer extends TimestampSerialization {

implicit val statusChangeWrites:Writes[StatusChangeDAO] = (
(JsPath \ "id").writeNullable[Int] and
(JsPath \ "projectId").write[Int] and
(JsPath \ "time").write[Timestamp] and
(JsPath \ "user").write[String] and
(JsPath \ "status").write[String] and
(JsPath \ "title").write[String]
)(unlift(StatusChangeDAO.unapply))

implicit val statusChangeReads:Reads[StatusChangeDAO] = (
(JsPath \ "id").readNullable[Int] and
(JsPath \ "projectId").read[Int] and
(JsPath \ "time").read[Timestamp] and
(JsPath \ "user").read[String] and
(JsPath \ "status").read[String] and
(JsPath \ "title").read[String]
)(StatusChangeDAO.apply _)
}


27 changes: 27 additions & 0 deletions conf/evolutions/default/34.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# -- !Ups
CREATE TABLE "StatusChange" (
id INTEGER NOT NULL PRIMARY KEY,
K_PROJECT_ID INTEGER NOT NULL,
T_TIME TIMESTAMP WITH TIME ZONE NOT NULL,
S_USER CHARACTER VARYING NOT NULL,
S_STATUS VARCHAR(16) NOT NULL,
S_TITLE CHARACTER VARYING
);

CREATE SEQUENCE "StatusChange_id_seq"
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;

ALTER SEQUENCE "StatusChange_id_seq" OWNED BY "StatusChange".id;

ALTER TABLE public."StatusChange_id_seq" OWNER TO projectlocker;

ALTER TABLE "StatusChange" OWNER TO "projectlocker";

ALTER TABLE ONLY "StatusChange" ALTER COLUMN id SET DEFAULT nextval('"StatusChange_id_seq"'::regclass);

# -- !Downs
DROP TABLE "StatusChange" CASCADE;
110 changes: 20 additions & 90 deletions conf/evolutions/test/34.sql
Original file line number Diff line number Diff line change
@@ -1,97 +1,27 @@
# -- !Ups
INSERT INTO "StorageEntry" (id, S_ROOT_PATH, S_CLIENT_PATH, S_STORAGE_TYPE, S_USER, S_PASSWORD, S_HOST, I_PORT) VALUES (1, '/tmp', NULL, 'Local', 'me', NULL, NULL, NULL);
INSERT INTO "StorageEntry" (id, S_ROOT_PATH, S_CLIENT_PATH, S_STORAGE_TYPE, S_USER, S_PASSWORD, S_HOST, I_PORT) VALUES (2, '/backups/projectfiles', NULL, 'ftp', 'me', '123456abcde', 'ftp.mysite.com', 21);
INSERT INTO "StorageEntry" (id, S_ROOT_PATH, S_CLIENT_PATH, S_STORAGE_TYPE, S_USER, S_PASSWORD, S_HOST, I_PORT, K_BACKS_UP_TO) VALUES (3, '/backups/projectfiles', NULL, 'ftp', 'me', '123456abcde', 'ftp.othermysite.com', 21, 1);
CREATE TABLE "StatusChange" (
id INTEGER NOT NULL PRIMARY KEY,
K_PROJECT_ID INTEGER NOT NULL,
T_TIME TIMESTAMP WITH TIME ZONE NOT NULL,
S_USER CHARACTER VARYING NOT NULL,
S_STATUS VARCHAR(16) NOT NULL,
S_TITLE CHARACTER VARYING
);

INSERT INTO "FileEntry" (id, S_FILEPATH, K_STORAGE_ID, S_USER, I_VERSION, T_CTIME, T_MTIME, T_ATIME, B_HAS_CONTENT) VALUES (1, '/path/to/a/video.mxf', 2, 'me', 1, '2017-01-17 16:55:00.123', '2017-01-17 16:55:00.123', '2017-01-17 16:55:00.123', false);
INSERT INTO "FileEntry" (id, S_FILEPATH, K_STORAGE_ID, S_USER, I_VERSION, T_CTIME, T_MTIME, T_ATIME, B_HAS_CONTENT) VALUES (2, '/path/to/a/file.project', 1, 'you', 1, '2016-12-11 12:21:11.021', '2016-12-11 12:21:11.021', '2016-12-11 12:21:11.021', true);
INSERT INTO "FileEntry" (id, S_FILEPATH, K_STORAGE_ID, S_USER, I_VERSION, T_CTIME, T_MTIME, T_ATIME, B_HAS_CONTENT) VALUES (3, 'realfile', 1, 'you', 1, '2016-12-11 12:21:11.021', '2016-12-11 12:21:11.021', '2016-12-11 12:21:11.021', true);
INSERT INTO "FileEntry" (id, S_FILEPATH, K_STORAGE_ID, S_USER, I_VERSION, T_CTIME, T_MTIME, T_ATIME, B_HAS_CONTENT) VALUES (4, 'testprojectfile', 1, 'you', 1, '2016-12-11 12:21:11.021', '2016-12-11 12:21:11.021', '2016-12-11 12:21:11.021', false);
INSERT INTO "FileEntry" (id, S_FILEPATH, K_STORAGE_ID, S_USER, I_VERSION, T_CTIME, T_MTIME, T_ATIME, B_HAS_CONTENT) VALUES (5, '/path/to/thattestproject', 1, 'you', 1, '2016-12-11 12:21:11.021', '2016-12-11 12:21:11.021', '2016-12-11 12:21:11.021', true);
INSERT INTO "FileEntry" (id, S_FILEPATH, K_STORAGE_ID, S_USER, I_VERSION, T_CTIME, T_MTIME, T_ATIME, B_HAS_CONTENT) VALUES (6, 'project_to_delete.prproj', 1, 'you', 1, '2016-12-11 12:21:11.021', '2016-12-11 12:21:11.021', '2016-12-11 12:21:11.021', false);
INSERT INTO "FileEntry" (id, S_FILEPATH, K_STORAGE_ID, S_USER, I_VERSION, T_CTIME, T_MTIME, T_ATIME, B_HAS_CONTENT) VALUES (57, 'anothertestprojectfile', 1, 'you', 1, '2016-12-11 12:21:11.021', '2016-12-11 12:21:11.021', '2016-12-11 12:21:11.021', false);
INSERT INTO "FileEntry" (id, S_FILEPATH, K_STORAGE_ID, S_USER, I_VERSION, T_CTIME, T_MTIME, T_ATIME, B_HAS_CONTENT) VALUES (58, '/path/to/a/test.mxf', 3, 'me', 1, '2017-01-17 16:55:00.123', '2017-01-17 16:55:00.123', '2017-01-17 16:55:00.123', false);
CREATE SEQUENCE "StatusChange_id_seq"
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;

INSERT INTO "ProjectType" (id, S_NAME, S_OPENS_WITH, S_TARGET_VERSION, S_FILE_EXTENSION) VALUES (1, 'Premiere 2014 test', 'AdobePremierePro.app', '14.0', '.prproj');
INSERT INTO "ProjectType" (id, S_NAME, S_OPENS_WITH, S_TARGET_VERSION, S_FILE_EXTENSION) VALUES (2, 'Prelude 2014 test', 'AdobePrelude.app', '14.0', '.plproj');
INSERT INTO "ProjectType" (id, S_NAME, S_OPENS_WITH, S_TARGET_VERSION, S_FILE_EXTENSION) VALUES (3, 'Cubase test', 'Cubase.app', '6.0', '.cpr');
INSERT INTO "ProjectType" (id, S_NAME, S_OPENS_WITH, S_TARGET_VERSION, S_FILE_EXTENSION) VALUES (4, 'Aftereffects test', 'AdobeAfterEffects.app', '6.0', '.aep');
ALTER SEQUENCE "StatusChange_id_seq" OWNED BY "StatusChange".id;

ALTER TABLE public."StatusChange_id_seq" OWNER TO projectlocker;

INSERT INTO "ProjectEntry" (id, K_PROJECT_TYPE, S_TITLE, T_CREATED,S_USER) VALUES (1, 1, 'InitialTestProject', '2016-12-11 12:21:11.021', 'me');
INSERT INTO "ProjectEntry" (id, K_PROJECT_TYPE, S_TITLE, S_VIDISPINE_ID,T_CREATED,S_USER) VALUES (2, 1, 'AnotherTestProject', 'VX-1234', '2016-12-11 12:21:11.021', 'you');
INSERT INTO "ProjectEntry" (id, K_PROJECT_TYPE, S_TITLE, S_VIDISPINE_ID,T_CREATED,S_USER) VALUES (3, 1, 'ThatTestProject', 'VX-2345', '2016-12-11 12:21:11.021', 'you');
INSERT INTO "ProjectEntry" (id, K_PROJECT_TYPE, S_TITLE, S_VIDISPINE_ID,T_CREATED,S_USER) VALUES (4, 1, 'WhoseTestProject', 'VX-2345', '2016-12-11 12:21:11.021', 'you');
INSERT INTO "ProjectEntry" (id, K_PROJECT_TYPE, S_TITLE, S_VIDISPINE_ID,T_CREATED,S_USER) VALUES (5, 1, 'UpgradeTestProject', 'VX-3456', '2016-12-11 12:21:11.021', 'you');
ALTER TABLE "StatusChange" OWNER TO "projectlocker";

INSERT INTO "ProjectFileAssociation" (id, K_PROJECT_ENTRY, K_FILE_ENTRY) VALUES (1, 1,2);
INSERT INTO "ProjectFileAssociation" (id, K_PROJECT_ENTRY, K_FILE_ENTRY) VALUES (2, 3,5);
ALTER TABLE ONLY "StatusChange" ALTER COLUMN id SET DEFAULT nextval('"StatusChange_id_seq"'::regclass);

INSERT INTO "ProjectTemplate" (id, S_NAME, K_PROJECT_TYPE, K_FILE_REF) VALUES (1, 'Premiere test template 1', 1,5);
INSERT INTO "ProjectTemplate" (id, S_NAME, K_PROJECT_TYPE, K_FILE_REF) VALUES (2, 'Another wonderful test template', 2, 2);
INSERT INTO "ProjectTemplate" (id, S_NAME, K_PROJECT_TYPE, K_FILE_REF) VALUES (3, 'Some random test template', 2, 2);

INSERT INTO "PostrunAction" (id, S_RUNNABLE, S_TITLE, S_OWNER, I_VERSION, T_CTIME) VALUES (1, 'FirstTestScript.py', 'First test postrun', 'system',1, '2018-01-01T12:13:24.000');
INSERT INTO "PostrunAction" (id, S_RUNNABLE, S_TITLE, S_OWNER, I_VERSION, T_CTIME) VALUES (2, 'SecondTestScript.py', 'Second test postrun', 'system',1, '2018-01-01T14:15:31.000');
INSERT INTO "PostrunAction" (id, S_RUNNABLE, S_TITLE, S_OWNER, I_VERSION, T_CTIME) VALUES (3, 'thirdTestScript.py', 'Third test postrun', 'system',1, '2018-01-01T14:15:31.000');
INSERT INTO "PostrunAction" (id, S_RUNNABLE, S_TITLE, S_OWNER, I_VERSION, T_CTIME) VALUES (4, 'fourthTestScript.py', 'Fourth test postrun', 'system',1, '2018-01-01T14:15:31.000');
INSERT INTO "PostrunAction" (id, S_RUNNABLE, S_TITLE, S_OWNER, I_VERSION, T_CTIME) VALUES (5, 'fifthTestScript.py', 'fifth test postrun', 'system',1, '2018-01-01T14:15:31.000');
INSERT INTO "PostrunAction" (id, S_RUNNABLE, S_TITLE, S_OWNER, I_VERSION, T_CTIME) VALUES (6, 'sixthTestScript.py', 'Sixth test postrun', 'system',1, '2018-01-01T14:15:31.000');

INSERT INTO "PostrunAssociationRow" (id, K_POSTRUN, K_PROJECTTYPE) VALUES (1, 1, 1);
INSERT INTO "PostrunAssociationRow" (id, K_POSTRUN, K_PROJECTTYPE) VALUES (2, 2, 1);
INSERT INTO "PostrunAssociationRow" (id, K_POSTRUN, K_PROJECTTYPE) VALUES (4, 5, 1);
INSERT INTO "PostrunAssociationRow" (id, K_POSTRUN, K_PROJECTTYPE) VALUES (3, 2, 4);

INSERT INTO "PostrunDependency" (id,K_SOURCE, K_DEPENDSON) VALUES (1, 1, 5);
INSERT INTO "PostrunDependency" (id,K_SOURCE, K_DEPENDSON) VALUES (2, 1, 6);
INSERT INTO "PostrunDependency" (id,K_SOURCE, K_DEPENDSON) VALUES (3, 4, 5);
INSERT INTO "PostrunDependency" (id,K_SOURCE, K_DEPENDSON) VALUES (4, 2, 1);

INSERT INTO "PlutoWorkingGroup" (id, B_HIDE, S_NAME, S_COMMISSIONER) VALUES (1, false, 'Multimedia Social', 'Paul Boyd');
INSERT INTO "PlutoWorkingGroup" (id, B_HIDE, S_NAME, S_COMMISSIONER) VALUES (2, true, 'Multimedia Anti-Social', 'Boyd Paul');

INSERT INTO "PlutoCommission" (id, I_COLLECTION_ID, S_SITE_ID, T_CREATED, T_UPDATED, S_TITLE, S_STATUS, S_DESCRIPTION, K_WORKING_GROUP) VALUES (1, 1234, 'VX', '2018-01-01T12:13:24.000', '2018-01-01T12:13:24.000', 'My test commission', 'New', 'some very long description goes here', 1);
INSERT INTO "PlutoCommission" (id, I_COLLECTION_ID, S_SITE_ID, T_CREATED, T_UPDATED, S_TITLE, S_STATUS, S_DESCRIPTION, K_WORKING_GROUP) VALUES (2, 2345, 'VX', '2018-01-02T12:13:24.000', '2018-01-02T12:13:24.000', 'My test commission 2', 'In Production', 'some very long description goes here', 1);
INSERT INTO "PlutoCommission" (id, I_COLLECTION_ID, S_SITE_ID, T_CREATED, T_UPDATED, S_TITLE, S_STATUS, S_DESCRIPTION, K_WORKING_GROUP) VALUES (3, 3456, 'VX', '2018-01-03T12:13:24.000', '2018-01-03T12:13:24.000', 'My test commission 3', 'Held', 'some very long description goes here', 1);
INSERT INTO "PlutoCommission" (id, I_COLLECTION_ID, S_SITE_ID, T_CREATED, T_UPDATED, S_TITLE, S_STATUS, S_DESCRIPTION, K_WORKING_GROUP) VALUES (4, 4567, 'VX', '2018-01-04T12:13:24.000', '2018-01-04T12:13:24.000', 'My test commission 4', 'Completed', 'some very long description goes here', 1);

INSERT INTO DEFAULTS (id, S_NAME, S_VALUE) VALUES (1, 'lunch', 'sandwich');
INSERT INTO DEFAULTS (id, S_NAME, S_VALUE) VALUES (2, 'breakfast', 'toast');
INSERT INTO DEFAULTS (id, S_NAME, S_VALUE) VALUES (3, 'dessert', 'nothing');
INSERT INTO DEFAULTS (id, S_NAME, S_VALUE) VALUES (4, 'project_storage_id', 1);

INSERT INTO "ProjectMetadata" (id, K_PROJECT_ENTRY, S_KEY, S_VALUE) VALUES (1, 2, 'first_key', 'first value');
INSERT INTO "ProjectMetadata" (id, K_PROJECT_ENTRY, S_KEY, S_VALUE) VALUES (2, 2, 'second_key', 'second value');

------------------------
SELECT pg_catalog.setval('"ProjectMetadata_id_seq"', 3, true);
SELECT pg_catalog.setval('"Defaults_id_seq"', 4, true);
SELECT pg_catalog.setval('"PlutoCommission_id_seq"', 5, true);
SELECT pg_catalog.setval('"PlutoWorkingGroup_id_seq"', 3, true);
SELECT pg_catalog.setval('"PostrunAction_id_seq"', 7, true);
SELECT pg_catalog.setval('"PostrunAssociationRow_id_seq"', 4, true);
SELECT pg_catalog.setval('"ProjectTemplate_id_seq"', 4, true);
SELECT pg_catalog.setval('"ProjectType_id_seq"', 4, true);
SELECT pg_catalog.setval('"StorageEntry_id_seq"', 3, true);
SELECT pg_catalog.setval('"ProjectFileAssociation_id_seq"', 3, false);
SELECT pg_catalog.setval('"ProjectEntry_id_seq"', 6, false);
SELECT pg_catalog.setval('"FileEntry_id_seq"', 6, true);

# --!Downs
delete from "ProjectMetadata";
delete from "PlutoWorkingGroup";
delete from "PlutoProjectType";
delete from DEFAULTS;
delete from "PlutoCommission";
delete from "PlutoWorkingGroup";
delete from "PostrunDependency";
delete from "PostrunAssociationRow";
delete from "PostrunAction";
delete from "ProjectFileAssociation";
delete from "FileEntry";
delete from "ProjectEntry";
delete from "ProjectTemplate";
delete from "ProjectType";
delete from "StorageEntry";
delete from "play_evolutions";
# -- !Downs
DROP TABLE "StatusChange" CASCADE;
Loading
Loading