diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/web/TexeraWebApplication.scala b/core/amber/src/main/scala/edu/uci/ics/texera/web/TexeraWebApplication.scala index 573898c7895..6fae80857c4 100644 --- a/core/amber/src/main/scala/edu/uci/ics/texera/web/TexeraWebApplication.scala +++ b/core/amber/src/main/scala/edu/uci/ics/texera/web/TexeraWebApplication.scala @@ -4,11 +4,11 @@ import akka.actor.ActorSystem import com.fasterxml.jackson.module.scala.DefaultScalaModule import com.github.dirkraft.dropwizard.fileassets.FileAssetsBundle import edu.uci.ics.amber.engine.common.AmberUtils -import edu.uci.ics.texera.Utils +import edu.uci.ics.texera.web.resource.{UserDictionaryResource, _} import edu.uci.ics.texera.web.resource.auth.UserResource -import edu.uci.ics.texera.web.resource.dashboard.file.UserFileResource +import edu.uci.ics.texera.web.resource.dashboard.file.{UserFileAccessResource, UserFileResource} import edu.uci.ics.texera.web.resource.dashboard.{WorkflowAccessResource, WorkflowResource} -import edu.uci.ics.texera.web.resource.{UserDictionaryResource, _} +import edu.uci.ics.texera.Utils import io.dropwizard.setup.{Bootstrap, Environment} import io.dropwizard.websockets.WebsocketBundle import org.eclipse.jetty.server.session.SessionHandler @@ -78,13 +78,15 @@ class TexeraWebApplication extends io.dropwizard.Application[TexeraWebConfigurat environment.jersey.register(classOf[MultiPartFeature]) environment.jersey().register(classOf[SystemMetadataResource]) -// environment.jersey().register(classOf[MockKillWorkerResource]) + // environment.jersey().register(classOf[MockKillWorkerResource]) environment.jersey().register(classOf[SchemaPropagationResource]) environment.jersey().register(classOf[UserResource]) environment.jersey().register(classOf[UserDictionaryResource]) - environment.jersey().register(classOf[WorkflowResource]) + environment.jersey().register(classOf[UserFileAccessResource]) environment.jersey().register(classOf[UserFileResource]) environment.jersey().register(classOf[WorkflowAccessResource]) + environment.jersey().register(classOf[WorkflowResource]) + } } diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/web/model/common/AccessEntry.scala b/core/amber/src/main/scala/edu/uci/ics/texera/web/model/common/AccessEntry.scala new file mode 100644 index 00000000000..fa7d70054dc --- /dev/null +++ b/core/amber/src/main/scala/edu/uci/ics/texera/web/model/common/AccessEntry.scala @@ -0,0 +1,3 @@ +package edu.uci.ics.texera.web.model.common + +case class AccessEntry(userName: String, accessLevel: String) {} diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/web/model/jooq/generated/Indexes.java b/core/amber/src/main/scala/edu/uci/ics/texera/web/model/jooq/generated/Indexes.java index 237783a0d61..03fe3b0deef 100644 --- a/core/amber/src/main/scala/edu/uci/ics/texera/web/model/jooq/generated/Indexes.java +++ b/core/amber/src/main/scala/edu/uci/ics/texera/web/model/jooq/generated/Indexes.java @@ -8,6 +8,7 @@ import edu.uci.ics.texera.web.model.jooq.generated.tables.KeywordDictionary; import edu.uci.ics.texera.web.model.jooq.generated.tables.User; import edu.uci.ics.texera.web.model.jooq.generated.tables.UserDictionary; +import edu.uci.ics.texera.web.model.jooq.generated.tables.UserFileAccess; import edu.uci.ics.texera.web.model.jooq.generated.tables.Workflow; import edu.uci.ics.texera.web.model.jooq.generated.tables.WorkflowOfUser; import edu.uci.ics.texera.web.model.jooq.generated.tables.WorkflowUserAccess; @@ -34,6 +35,8 @@ public class Indexes { public static final Index USER_GOOGLE_ID = Indexes0.USER_GOOGLE_ID; public static final Index USER_PRIMARY = Indexes0.USER_PRIMARY; public static final Index USER_DICTIONARY_PRIMARY = Indexes0.USER_DICTIONARY_PRIMARY; + public static final Index USER_FILE_ACCESS_FID = Indexes0.USER_FILE_ACCESS_FID; + public static final Index USER_FILE_ACCESS_PRIMARY = Indexes0.USER_FILE_ACCESS_PRIMARY; public static final Index WORKFLOW_PRIMARY = Indexes0.WORKFLOW_PRIMARY; public static final Index WORKFLOW_OF_USER_PRIMARY = Indexes0.WORKFLOW_OF_USER_PRIMARY; public static final Index WORKFLOW_OF_USER_WID = Indexes0.WORKFLOW_OF_USER_WID; @@ -52,6 +55,8 @@ private static class Indexes0 { public static Index USER_GOOGLE_ID = Internal.createIndex("google_id", User.USER, new OrderField[] { User.USER.GOOGLE_ID }, true); public static Index USER_PRIMARY = Internal.createIndex("PRIMARY", User.USER, new OrderField[] { User.USER.UID }, true); public static Index USER_DICTIONARY_PRIMARY = Internal.createIndex("PRIMARY", UserDictionary.USER_DICTIONARY, new OrderField[] { UserDictionary.USER_DICTIONARY.UID, UserDictionary.USER_DICTIONARY.KEY }, true); + public static Index USER_FILE_ACCESS_FID = Internal.createIndex("fid", UserFileAccess.USER_FILE_ACCESS, new OrderField[] { UserFileAccess.USER_FILE_ACCESS.FID }, false); + public static Index USER_FILE_ACCESS_PRIMARY = Internal.createIndex("PRIMARY", UserFileAccess.USER_FILE_ACCESS, new OrderField[] { UserFileAccess.USER_FILE_ACCESS.UID, UserFileAccess.USER_FILE_ACCESS.FID }, true); public static Index WORKFLOW_PRIMARY = Internal.createIndex("PRIMARY", Workflow.WORKFLOW, new OrderField[] { Workflow.WORKFLOW.WID }, true); public static Index WORKFLOW_OF_USER_PRIMARY = Internal.createIndex("PRIMARY", WorkflowOfUser.WORKFLOW_OF_USER, new OrderField[] { WorkflowOfUser.WORKFLOW_OF_USER.UID, WorkflowOfUser.WORKFLOW_OF_USER.WID }, true); public static Index WORKFLOW_OF_USER_WID = Internal.createIndex("wid", WorkflowOfUser.WORKFLOW_OF_USER, new OrderField[] { WorkflowOfUser.WORKFLOW_OF_USER.WID }, false); diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/web/model/jooq/generated/Keys.java b/core/amber/src/main/scala/edu/uci/ics/texera/web/model/jooq/generated/Keys.java index 8d2669214c6..d7c1176c578 100644 --- a/core/amber/src/main/scala/edu/uci/ics/texera/web/model/jooq/generated/Keys.java +++ b/core/amber/src/main/scala/edu/uci/ics/texera/web/model/jooq/generated/Keys.java @@ -8,12 +8,14 @@ import edu.uci.ics.texera.web.model.jooq.generated.tables.KeywordDictionary; import edu.uci.ics.texera.web.model.jooq.generated.tables.User; import edu.uci.ics.texera.web.model.jooq.generated.tables.UserDictionary; +import edu.uci.ics.texera.web.model.jooq.generated.tables.UserFileAccess; import edu.uci.ics.texera.web.model.jooq.generated.tables.Workflow; import edu.uci.ics.texera.web.model.jooq.generated.tables.WorkflowOfUser; import edu.uci.ics.texera.web.model.jooq.generated.tables.WorkflowUserAccess; import edu.uci.ics.texera.web.model.jooq.generated.tables.records.FileRecord; import edu.uci.ics.texera.web.model.jooq.generated.tables.records.KeywordDictionaryRecord; import edu.uci.ics.texera.web.model.jooq.generated.tables.records.UserDictionaryRecord; +import edu.uci.ics.texera.web.model.jooq.generated.tables.records.UserFileAccessRecord; import edu.uci.ics.texera.web.model.jooq.generated.tables.records.UserRecord; import edu.uci.ics.texera.web.model.jooq.generated.tables.records.WorkflowOfUserRecord; import edu.uci.ics.texera.web.model.jooq.generated.tables.records.WorkflowRecord; @@ -53,6 +55,7 @@ public class Keys { public static final UniqueKey KEY_USER_PRIMARY = UniqueKeys0.KEY_USER_PRIMARY; public static final UniqueKey KEY_USER_GOOGLE_ID = UniqueKeys0.KEY_USER_GOOGLE_ID; public static final UniqueKey KEY_USER_DICTIONARY_PRIMARY = UniqueKeys0.KEY_USER_DICTIONARY_PRIMARY; + public static final UniqueKey KEY_USER_FILE_ACCESS_PRIMARY = UniqueKeys0.KEY_USER_FILE_ACCESS_PRIMARY; public static final UniqueKey KEY_WORKFLOW_PRIMARY = UniqueKeys0.KEY_WORKFLOW_PRIMARY; public static final UniqueKey KEY_WORKFLOW_OF_USER_PRIMARY = UniqueKeys0.KEY_WORKFLOW_OF_USER_PRIMARY; public static final UniqueKey KEY_WORKFLOW_USER_ACCESS_PRIMARY = UniqueKeys0.KEY_WORKFLOW_USER_ACCESS_PRIMARY; @@ -64,6 +67,8 @@ public class Keys { public static final ForeignKey FILE_IBFK_1 = ForeignKeys0.FILE_IBFK_1; public static final ForeignKey KEYWORD_DICTIONARY_IBFK_1 = ForeignKeys0.KEYWORD_DICTIONARY_IBFK_1; public static final ForeignKey USER_DICTIONARY_IBFK_1 = ForeignKeys0.USER_DICTIONARY_IBFK_1; + public static final ForeignKey USER_FILE_ACCESS_IBFK_1 = ForeignKeys0.USER_FILE_ACCESS_IBFK_1; + public static final ForeignKey USER_FILE_ACCESS_IBFK_2 = ForeignKeys0.USER_FILE_ACCESS_IBFK_2; public static final ForeignKey WORKFLOW_OF_USER_IBFK_1 = ForeignKeys0.WORKFLOW_OF_USER_IBFK_1; public static final ForeignKey WORKFLOW_OF_USER_IBFK_2 = ForeignKeys0.WORKFLOW_OF_USER_IBFK_2; public static final ForeignKey WORKFLOW_USER_ACCESS_IBFK_1 = ForeignKeys0.WORKFLOW_USER_ACCESS_IBFK_1; @@ -88,6 +93,7 @@ private static class UniqueKeys0 { public static final UniqueKey KEY_USER_PRIMARY = Internal.createUniqueKey(User.USER, "KEY_user_PRIMARY", User.USER.UID); public static final UniqueKey KEY_USER_GOOGLE_ID = Internal.createUniqueKey(User.USER, "KEY_user_google_id", User.USER.GOOGLE_ID); public static final UniqueKey KEY_USER_DICTIONARY_PRIMARY = Internal.createUniqueKey(UserDictionary.USER_DICTIONARY, "KEY_user_dictionary_PRIMARY", UserDictionary.USER_DICTIONARY.UID, UserDictionary.USER_DICTIONARY.KEY); + public static final UniqueKey KEY_USER_FILE_ACCESS_PRIMARY = Internal.createUniqueKey(UserFileAccess.USER_FILE_ACCESS, "KEY_user_file_access_PRIMARY", UserFileAccess.USER_FILE_ACCESS.UID, UserFileAccess.USER_FILE_ACCESS.FID); public static final UniqueKey KEY_WORKFLOW_PRIMARY = Internal.createUniqueKey(Workflow.WORKFLOW, "KEY_workflow_PRIMARY", Workflow.WORKFLOW.WID); public static final UniqueKey KEY_WORKFLOW_OF_USER_PRIMARY = Internal.createUniqueKey(WorkflowOfUser.WORKFLOW_OF_USER, "KEY_workflow_of_user_PRIMARY", WorkflowOfUser.WORKFLOW_OF_USER.UID, WorkflowOfUser.WORKFLOW_OF_USER.WID); public static final UniqueKey KEY_WORKFLOW_USER_ACCESS_PRIMARY = Internal.createUniqueKey(WorkflowUserAccess.WORKFLOW_USER_ACCESS, "KEY_workflow_user_access_PRIMARY", WorkflowUserAccess.WORKFLOW_USER_ACCESS.UID, WorkflowUserAccess.WORKFLOW_USER_ACCESS.WID); @@ -97,6 +103,8 @@ private static class ForeignKeys0 { public static final ForeignKey FILE_IBFK_1 = Internal.createForeignKey(edu.uci.ics.texera.web.model.jooq.generated.Keys.KEY_USER_PRIMARY, File.FILE, "file_ibfk_1", File.FILE.UID); public static final ForeignKey KEYWORD_DICTIONARY_IBFK_1 = Internal.createForeignKey(edu.uci.ics.texera.web.model.jooq.generated.Keys.KEY_USER_PRIMARY, KeywordDictionary.KEYWORD_DICTIONARY, "keyword_dictionary_ibfk_1", KeywordDictionary.KEYWORD_DICTIONARY.UID); public static final ForeignKey USER_DICTIONARY_IBFK_1 = Internal.createForeignKey(edu.uci.ics.texera.web.model.jooq.generated.Keys.KEY_USER_PRIMARY, UserDictionary.USER_DICTIONARY, "user_dictionary_ibfk_1", UserDictionary.USER_DICTIONARY.UID); + public static final ForeignKey USER_FILE_ACCESS_IBFK_1 = Internal.createForeignKey(edu.uci.ics.texera.web.model.jooq.generated.Keys.KEY_USER_PRIMARY, UserFileAccess.USER_FILE_ACCESS, "user_file_access_ibfk_1", UserFileAccess.USER_FILE_ACCESS.UID); + public static final ForeignKey USER_FILE_ACCESS_IBFK_2 = Internal.createForeignKey(edu.uci.ics.texera.web.model.jooq.generated.Keys.KEY_FILE_PRIMARY, UserFileAccess.USER_FILE_ACCESS, "user_file_access_ibfk_2", UserFileAccess.USER_FILE_ACCESS.FID); public static final ForeignKey WORKFLOW_OF_USER_IBFK_1 = Internal.createForeignKey(edu.uci.ics.texera.web.model.jooq.generated.Keys.KEY_USER_PRIMARY, WorkflowOfUser.WORKFLOW_OF_USER, "workflow_of_user_ibfk_1", WorkflowOfUser.WORKFLOW_OF_USER.UID); public static final ForeignKey WORKFLOW_OF_USER_IBFK_2 = Internal.createForeignKey(edu.uci.ics.texera.web.model.jooq.generated.Keys.KEY_WORKFLOW_PRIMARY, WorkflowOfUser.WORKFLOW_OF_USER, "workflow_of_user_ibfk_2", WorkflowOfUser.WORKFLOW_OF_USER.WID); public static final ForeignKey WORKFLOW_USER_ACCESS_IBFK_1 = Internal.createForeignKey(edu.uci.ics.texera.web.model.jooq.generated.Keys.KEY_USER_PRIMARY, WorkflowUserAccess.WORKFLOW_USER_ACCESS, "workflow_user_access_ibfk_1", WorkflowUserAccess.WORKFLOW_USER_ACCESS.UID); diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/web/model/jooq/generated/Tables.java b/core/amber/src/main/scala/edu/uci/ics/texera/web/model/jooq/generated/Tables.java index e223b21917b..9bdcd3c8b99 100644 --- a/core/amber/src/main/scala/edu/uci/ics/texera/web/model/jooq/generated/Tables.java +++ b/core/amber/src/main/scala/edu/uci/ics/texera/web/model/jooq/generated/Tables.java @@ -8,6 +8,7 @@ import edu.uci.ics.texera.web.model.jooq.generated.tables.KeywordDictionary; import edu.uci.ics.texera.web.model.jooq.generated.tables.User; import edu.uci.ics.texera.web.model.jooq.generated.tables.UserDictionary; +import edu.uci.ics.texera.web.model.jooq.generated.tables.UserFileAccess; import edu.uci.ics.texera.web.model.jooq.generated.tables.Workflow; import edu.uci.ics.texera.web.model.jooq.generated.tables.WorkflowOfUser; import edu.uci.ics.texera.web.model.jooq.generated.tables.WorkflowUserAccess; @@ -39,6 +40,11 @@ public class Tables { */ public static final UserDictionary USER_DICTIONARY = UserDictionary.USER_DICTIONARY; + /** + * The table texera_db.user_file_access. + */ + public static final UserFileAccess USER_FILE_ACCESS = UserFileAccess.USER_FILE_ACCESS; + /** * The table texera_db.workflow. */ diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/web/model/jooq/generated/TexeraDb.java b/core/amber/src/main/scala/edu/uci/ics/texera/web/model/jooq/generated/TexeraDb.java index f7d05fe11ed..f34f2a31c33 100644 --- a/core/amber/src/main/scala/edu/uci/ics/texera/web/model/jooq/generated/TexeraDb.java +++ b/core/amber/src/main/scala/edu/uci/ics/texera/web/model/jooq/generated/TexeraDb.java @@ -8,6 +8,7 @@ import edu.uci.ics.texera.web.model.jooq.generated.tables.KeywordDictionary; import edu.uci.ics.texera.web.model.jooq.generated.tables.User; import edu.uci.ics.texera.web.model.jooq.generated.tables.UserDictionary; +import edu.uci.ics.texera.web.model.jooq.generated.tables.UserFileAccess; import edu.uci.ics.texera.web.model.jooq.generated.tables.Workflow; import edu.uci.ics.texera.web.model.jooq.generated.tables.WorkflowOfUser; import edu.uci.ics.texera.web.model.jooq.generated.tables.WorkflowUserAccess; @@ -27,7 +28,7 @@ @SuppressWarnings({ "all", "unchecked", "rawtypes" }) public class TexeraDb extends SchemaImpl { - private static final long serialVersionUID = 1761177904; + private static final long serialVersionUID = -468602009; /** * The reference instance of texera_db @@ -54,6 +55,11 @@ public class TexeraDb extends SchemaImpl { */ public final UserDictionary USER_DICTIONARY = edu.uci.ics.texera.web.model.jooq.generated.tables.UserDictionary.USER_DICTIONARY; + /** + * The table texera_db.user_file_access. + */ + public final UserFileAccess USER_FILE_ACCESS = edu.uci.ics.texera.web.model.jooq.generated.tables.UserFileAccess.USER_FILE_ACCESS; + /** * The table texera_db.workflow. */ @@ -95,6 +101,7 @@ private final List> getTables0() { KeywordDictionary.KEYWORD_DICTIONARY, User.USER, UserDictionary.USER_DICTIONARY, + UserFileAccess.USER_FILE_ACCESS, Workflow.WORKFLOW, WorkflowOfUser.WORKFLOW_OF_USER, WorkflowUserAccess.WORKFLOW_USER_ACCESS); diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/web/model/jooq/generated/tables/UserFileAccess.java b/core/amber/src/main/scala/edu/uci/ics/texera/web/model/jooq/generated/tables/UserFileAccess.java new file mode 100644 index 00000000000..0f3ad4feca9 --- /dev/null +++ b/core/amber/src/main/scala/edu/uci/ics/texera/web/model/jooq/generated/tables/UserFileAccess.java @@ -0,0 +1,171 @@ +/* + * This file is generated by jOOQ. + */ +package edu.uci.ics.texera.web.model.jooq.generated.tables; + + +import edu.uci.ics.texera.web.model.jooq.generated.Indexes; +import edu.uci.ics.texera.web.model.jooq.generated.Keys; +import edu.uci.ics.texera.web.model.jooq.generated.TexeraDb; +import edu.uci.ics.texera.web.model.jooq.generated.tables.records.UserFileAccessRecord; + +import java.util.Arrays; +import java.util.List; + +import org.jooq.Field; +import org.jooq.ForeignKey; +import org.jooq.Index; +import org.jooq.Name; +import org.jooq.Record; +import org.jooq.Row4; +import org.jooq.Schema; +import org.jooq.Table; +import org.jooq.TableField; +import org.jooq.UniqueKey; +import org.jooq.impl.DSL; +import org.jooq.impl.TableImpl; +import org.jooq.types.UInteger; + + +/** + * This class is generated by jOOQ. + */ +@SuppressWarnings({ "all", "unchecked", "rawtypes" }) +public class UserFileAccess extends TableImpl { + + private static final long serialVersionUID = -1485119794; + + /** + * The reference instance of texera_db.user_file_access + */ + public static final UserFileAccess USER_FILE_ACCESS = new UserFileAccess(); + + /** + * The class holding records for this type + */ + @Override + public Class getRecordType() { + return UserFileAccessRecord.class; + } + + /** + * The column texera_db.user_file_access.uid. + */ + public final TableField UID = createField(DSL.name("uid"), org.jooq.impl.SQLDataType.INTEGERUNSIGNED.nullable(false), this, ""); + + /** + * The column texera_db.user_file_access.fid. + */ + public final TableField FID = createField(DSL.name("fid"), org.jooq.impl.SQLDataType.INTEGERUNSIGNED.nullable(false), this, ""); + + /** + * The column texera_db.user_file_access.read_access. + */ + public final TableField READ_ACCESS = createField(DSL.name("read_access"), org.jooq.impl.SQLDataType.BIT, this, ""); + + /** + * The column texera_db.user_file_access.write_access. + */ + public final TableField WRITE_ACCESS = createField(DSL.name("write_access"), org.jooq.impl.SQLDataType.BIT, this, ""); + + /** + * Create a texera_db.user_file_access table reference + */ + public UserFileAccess() { + this(DSL.name("user_file_access"), null); + } + + /** + * Create an aliased texera_db.user_file_access table reference + */ + public UserFileAccess(String alias) { + this(DSL.name(alias), USER_FILE_ACCESS); + } + + /** + * Create an aliased texera_db.user_file_access table reference + */ + public UserFileAccess(Name alias) { + this(alias, USER_FILE_ACCESS); + } + + private UserFileAccess(Name alias, Table aliased) { + this(alias, aliased, null); + } + + private UserFileAccess(Name alias, Table aliased, Field[] parameters) { + super(alias, null, aliased, parameters, DSL.comment("")); + } + + public UserFileAccess(Table child, ForeignKey key) { + super(child, key, USER_FILE_ACCESS); + } + + @Override + public Schema getSchema() { + return TexeraDb.TEXERA_DB; + } + + @Override + public List getIndexes() { + return Arrays.asList(Indexes.USER_FILE_ACCESS_FID, Indexes.USER_FILE_ACCESS_PRIMARY); + } + + @Override + public UniqueKey getPrimaryKey() { + return Keys.KEY_USER_FILE_ACCESS_PRIMARY; + } + + @Override + public List> getKeys() { + return Arrays.>asList(Keys.KEY_USER_FILE_ACCESS_PRIMARY); + } + + @Override + public List> getReferences() { + return Arrays.>asList(Keys.USER_FILE_ACCESS_IBFK_1, Keys.USER_FILE_ACCESS_IBFK_2); + } + + public User user() { + return new User(this, Keys.USER_FILE_ACCESS_IBFK_1); + } + + public File file() { + return new File(this, Keys.USER_FILE_ACCESS_IBFK_2); + } + + @Override + public UserFileAccess as(String alias) { + return new UserFileAccess(DSL.name(alias), this); + } + + @Override + public UserFileAccess as(Name alias) { + return new UserFileAccess(alias, this); + } + + /** + * Rename this table + */ + @Override + public UserFileAccess rename(String name) { + return new UserFileAccess(DSL.name(name), null); + } + + /** + * Rename this table + */ + @Override + public UserFileAccess rename(Name name) { + return new UserFileAccess(name, null); + } + + // ------------------------------------------------------------------------- + // Row4 type methods + // ------------------------------------------------------------------------- + + @Override + public Row4 fieldsRow() { + return (Row4) super.fieldsRow(); + } +} diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/web/model/jooq/generated/tables/daos/UserFileAccessDao.java b/core/amber/src/main/scala/edu/uci/ics/texera/web/model/jooq/generated/tables/daos/UserFileAccessDao.java new file mode 100644 index 00000000000..c039df7cb45 --- /dev/null +++ b/core/amber/src/main/scala/edu/uci/ics/texera/web/model/jooq/generated/tables/daos/UserFileAccessDao.java @@ -0,0 +1,98 @@ +/* + * This file is generated by jOOQ. + */ +package edu.uci.ics.texera.web.model.jooq.generated.tables.daos; + + +import edu.uci.ics.texera.web.model.jooq.generated.tables.UserFileAccess; +import edu.uci.ics.texera.web.model.jooq.generated.tables.records.UserFileAccessRecord; + +import java.util.List; + +import org.jooq.Configuration; +import org.jooq.Record2; +import org.jooq.impl.DAOImpl; +import org.jooq.types.UInteger; + + +/** + * This class is generated by jOOQ. + */ +@SuppressWarnings({ "all", "unchecked", "rawtypes" }) +public class UserFileAccessDao extends DAOImpl> { + + /** + * Create a new UserFileAccessDao without any configuration + */ + public UserFileAccessDao() { + super(UserFileAccess.USER_FILE_ACCESS, edu.uci.ics.texera.web.model.jooq.generated.tables.pojos.UserFileAccess.class); + } + + /** + * Create a new UserFileAccessDao with an attached configuration + */ + public UserFileAccessDao(Configuration configuration) { + super(UserFileAccess.USER_FILE_ACCESS, edu.uci.ics.texera.web.model.jooq.generated.tables.pojos.UserFileAccess.class, configuration); + } + + @Override + public Record2 getId(edu.uci.ics.texera.web.model.jooq.generated.tables.pojos.UserFileAccess object) { + return compositeKeyRecord(object.getUid(), object.getFid()); + } + + /** + * Fetch records that have uid BETWEEN lowerInclusive AND upperInclusive + */ + public List fetchRangeOfUid(UInteger lowerInclusive, UInteger upperInclusive) { + return fetchRange(UserFileAccess.USER_FILE_ACCESS.UID, lowerInclusive, upperInclusive); + } + + /** + * Fetch records that have uid IN (values) + */ + public List fetchByUid(UInteger... values) { + return fetch(UserFileAccess.USER_FILE_ACCESS.UID, values); + } + + /** + * Fetch records that have fid BETWEEN lowerInclusive AND upperInclusive + */ + public List fetchRangeOfFid(UInteger lowerInclusive, UInteger upperInclusive) { + return fetchRange(UserFileAccess.USER_FILE_ACCESS.FID, lowerInclusive, upperInclusive); + } + + /** + * Fetch records that have fid IN (values) + */ + public List fetchByFid(UInteger... values) { + return fetch(UserFileAccess.USER_FILE_ACCESS.FID, values); + } + + /** + * Fetch records that have read_access BETWEEN lowerInclusive AND upperInclusive + */ + public List fetchRangeOfReadAccess(Boolean lowerInclusive, Boolean upperInclusive) { + return fetchRange(UserFileAccess.USER_FILE_ACCESS.READ_ACCESS, lowerInclusive, upperInclusive); + } + + /** + * Fetch records that have read_access IN (values) + */ + public List fetchByReadAccess(Boolean... values) { + return fetch(UserFileAccess.USER_FILE_ACCESS.READ_ACCESS, values); + } + + /** + * Fetch records that have write_access BETWEEN lowerInclusive AND upperInclusive + */ + public List fetchRangeOfWriteAccess(Boolean lowerInclusive, Boolean upperInclusive) { + return fetchRange(UserFileAccess.USER_FILE_ACCESS.WRITE_ACCESS, lowerInclusive, upperInclusive); + } + + /** + * Fetch records that have write_access IN (values) + */ + public List fetchByWriteAccess(Boolean... values) { + return fetch(UserFileAccess.USER_FILE_ACCESS.WRITE_ACCESS, values); + } +} diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/web/model/jooq/generated/tables/interfaces/IUserFileAccess.java b/core/amber/src/main/scala/edu/uci/ics/texera/web/model/jooq/generated/tables/interfaces/IUserFileAccess.java new file mode 100644 index 00000000000..eeff0eb711b --- /dev/null +++ b/core/amber/src/main/scala/edu/uci/ics/texera/web/model/jooq/generated/tables/interfaces/IUserFileAccess.java @@ -0,0 +1,71 @@ +/* + * This file is generated by jOOQ. + */ +package edu.uci.ics.texera.web.model.jooq.generated.tables.interfaces; + + +import java.io.Serializable; + +import org.jooq.types.UInteger; + + +/** + * This class is generated by jOOQ. + */ +@SuppressWarnings({ "all", "unchecked", "rawtypes" }) +public interface IUserFileAccess extends Serializable { + + /** + * Setter for texera_db.user_file_access.uid. + */ + public void setUid(UInteger value); + + /** + * Getter for texera_db.user_file_access.uid. + */ + public UInteger getUid(); + + /** + * Setter for texera_db.user_file_access.fid. + */ + public void setFid(UInteger value); + + /** + * Getter for texera_db.user_file_access.fid. + */ + public UInteger getFid(); + + /** + * Setter for texera_db.user_file_access.read_access. + */ + public void setReadAccess(Boolean value); + + /** + * Getter for texera_db.user_file_access.read_access. + */ + public Boolean getReadAccess(); + + /** + * Setter for texera_db.user_file_access.write_access. + */ + public void setWriteAccess(Boolean value); + + /** + * Getter for texera_db.user_file_access.write_access. + */ + public Boolean getWriteAccess(); + + // ------------------------------------------------------------------------- + // FROM and INTO + // ------------------------------------------------------------------------- + + /** + * Load data from another generated Record/POJO implementing the common interface IUserFileAccess + */ + public void from(edu.uci.ics.texera.web.model.jooq.generated.tables.interfaces.IUserFileAccess from); + + /** + * Copy data into another generated Record/POJO implementing the common interface IUserFileAccess + */ + public E into(E into); +} diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/web/model/jooq/generated/tables/pojos/UserFileAccess.java b/core/amber/src/main/scala/edu/uci/ics/texera/web/model/jooq/generated/tables/pojos/UserFileAccess.java new file mode 100644 index 00000000000..c6f8687c11a --- /dev/null +++ b/core/amber/src/main/scala/edu/uci/ics/texera/web/model/jooq/generated/tables/pojos/UserFileAccess.java @@ -0,0 +1,116 @@ +/* + * This file is generated by jOOQ. + */ +package edu.uci.ics.texera.web.model.jooq.generated.tables.pojos; + + +import edu.uci.ics.texera.web.model.jooq.generated.tables.interfaces.IUserFileAccess; + +import org.jooq.types.UInteger; + + +/** + * This class is generated by jOOQ. + */ +@SuppressWarnings({ "all", "unchecked", "rawtypes" }) +public class UserFileAccess implements IUserFileAccess { + + private static final long serialVersionUID = 1757479799; + + private UInteger uid; + private UInteger fid; + private Boolean readAccess; + private Boolean writeAccess; + + public UserFileAccess() {} + + public UserFileAccess(IUserFileAccess value) { + this.uid = value.getUid(); + this.fid = value.getFid(); + this.readAccess = value.getReadAccess(); + this.writeAccess = value.getWriteAccess(); + } + + public UserFileAccess( + UInteger uid, + UInteger fid, + Boolean readAccess, + Boolean writeAccess + ) { + this.uid = uid; + this.fid = fid; + this.readAccess = readAccess; + this.writeAccess = writeAccess; + } + + @Override + public UInteger getUid() { + return this.uid; + } + + @Override + public void setUid(UInteger uid) { + this.uid = uid; + } + + @Override + public UInteger getFid() { + return this.fid; + } + + @Override + public void setFid(UInteger fid) { + this.fid = fid; + } + + @Override + public Boolean getReadAccess() { + return this.readAccess; + } + + @Override + public void setReadAccess(Boolean readAccess) { + this.readAccess = readAccess; + } + + @Override + public Boolean getWriteAccess() { + return this.writeAccess; + } + + @Override + public void setWriteAccess(Boolean writeAccess) { + this.writeAccess = writeAccess; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("UserFileAccess ("); + + sb.append(uid); + sb.append(", ").append(fid); + sb.append(", ").append(readAccess); + sb.append(", ").append(writeAccess); + + sb.append(")"); + return sb.toString(); + } + + // ------------------------------------------------------------------------- + // FROM and INTO + // ------------------------------------------------------------------------- + + @Override + public void from(IUserFileAccess from) { + setUid(from.getUid()); + setFid(from.getFid()); + setReadAccess(from.getReadAccess()); + setWriteAccess(from.getWriteAccess()); + } + + @Override + public E into(E into) { + into.from(this); + return into; + } +} diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/web/model/jooq/generated/tables/records/UserFileAccessRecord.java b/core/amber/src/main/scala/edu/uci/ics/texera/web/model/jooq/generated/tables/records/UserFileAccessRecord.java new file mode 100644 index 00000000000..05d2a02ea67 --- /dev/null +++ b/core/amber/src/main/scala/edu/uci/ics/texera/web/model/jooq/generated/tables/records/UserFileAccessRecord.java @@ -0,0 +1,246 @@ +/* + * This file is generated by jOOQ. + */ +package edu.uci.ics.texera.web.model.jooq.generated.tables.records; + + +import edu.uci.ics.texera.web.model.jooq.generated.tables.UserFileAccess; +import edu.uci.ics.texera.web.model.jooq.generated.tables.interfaces.IUserFileAccess; + +import org.jooq.Field; +import org.jooq.Record2; +import org.jooq.Record4; +import org.jooq.Row4; +import org.jooq.impl.UpdatableRecordImpl; +import org.jooq.types.UInteger; + + +/** + * This class is generated by jOOQ. + */ +@SuppressWarnings({ "all", "unchecked", "rawtypes" }) +public class UserFileAccessRecord extends UpdatableRecordImpl implements Record4, IUserFileAccess { + + private static final long serialVersionUID = -81823505; + + /** + * Setter for texera_db.user_file_access.uid. + */ + @Override + public void setUid(UInteger value) { + set(0, value); + } + + /** + * Getter for texera_db.user_file_access.uid. + */ + @Override + public UInteger getUid() { + return (UInteger) get(0); + } + + /** + * Setter for texera_db.user_file_access.fid. + */ + @Override + public void setFid(UInteger value) { + set(1, value); + } + + /** + * Getter for texera_db.user_file_access.fid. + */ + @Override + public UInteger getFid() { + return (UInteger) get(1); + } + + /** + * Setter for texera_db.user_file_access.read_access. + */ + @Override + public void setReadAccess(Boolean value) { + set(2, value); + } + + /** + * Getter for texera_db.user_file_access.read_access. + */ + @Override + public Boolean getReadAccess() { + return (Boolean) get(2); + } + + /** + * Setter for texera_db.user_file_access.write_access. + */ + @Override + public void setWriteAccess(Boolean value) { + set(3, value); + } + + /** + * Getter for texera_db.user_file_access.write_access. + */ + @Override + public Boolean getWriteAccess() { + return (Boolean) get(3); + } + + // ------------------------------------------------------------------------- + // Primary key information + // ------------------------------------------------------------------------- + + @Override + public Record2 key() { + return (Record2) super.key(); + } + + // ------------------------------------------------------------------------- + // Record4 type implementation + // ------------------------------------------------------------------------- + + @Override + public Row4 fieldsRow() { + return (Row4) super.fieldsRow(); + } + + @Override + public Row4 valuesRow() { + return (Row4) super.valuesRow(); + } + + @Override + public Field field1() { + return UserFileAccess.USER_FILE_ACCESS.UID; + } + + @Override + public Field field2() { + return UserFileAccess.USER_FILE_ACCESS.FID; + } + + @Override + public Field field3() { + return UserFileAccess.USER_FILE_ACCESS.READ_ACCESS; + } + + @Override + public Field field4() { + return UserFileAccess.USER_FILE_ACCESS.WRITE_ACCESS; + } + + @Override + public UInteger component1() { + return getUid(); + } + + @Override + public UInteger component2() { + return getFid(); + } + + @Override + public Boolean component3() { + return getReadAccess(); + } + + @Override + public Boolean component4() { + return getWriteAccess(); + } + + @Override + public UInteger value1() { + return getUid(); + } + + @Override + public UInteger value2() { + return getFid(); + } + + @Override + public Boolean value3() { + return getReadAccess(); + } + + @Override + public Boolean value4() { + return getWriteAccess(); + } + + @Override + public UserFileAccessRecord value1(UInteger value) { + setUid(value); + return this; + } + + @Override + public UserFileAccessRecord value2(UInteger value) { + setFid(value); + return this; + } + + @Override + public UserFileAccessRecord value3(Boolean value) { + setReadAccess(value); + return this; + } + + @Override + public UserFileAccessRecord value4(Boolean value) { + setWriteAccess(value); + return this; + } + + @Override + public UserFileAccessRecord values(UInteger value1, UInteger value2, Boolean value3, Boolean value4) { + value1(value1); + value2(value2); + value3(value3); + value4(value4); + return this; + } + + // ------------------------------------------------------------------------- + // FROM and INTO + // ------------------------------------------------------------------------- + + @Override + public void from(IUserFileAccess from) { + setUid(from.getUid()); + setFid(from.getFid()); + setReadAccess(from.getReadAccess()); + setWriteAccess(from.getWriteAccess()); + } + + @Override + public E into(E into) { + into.from(this); + return into; + } + + // ------------------------------------------------------------------------- + // Constructors + // ------------------------------------------------------------------------- + + /** + * Create a detached UserFileAccessRecord + */ + public UserFileAccessRecord() { + super(UserFileAccess.USER_FILE_ACCESS); + } + + /** + * Create a detached, initialised UserFileAccessRecord + */ + public UserFileAccessRecord(UInteger uid, UInteger fid, Boolean readAccess, Boolean writeAccess) { + super(UserFileAccess.USER_FILE_ACCESS); + + set(0, uid); + set(1, fid); + set(2, readAccess); + set(3, writeAccess); + } +} diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/web/resource/dashboard/WorkflowAccessResource.scala b/core/amber/src/main/scala/edu/uci/ics/texera/web/resource/dashboard/WorkflowAccessResource.scala index 0976a983857..55abfab90d8 100644 --- a/core/amber/src/main/scala/edu/uci/ics/texera/web/resource/dashboard/WorkflowAccessResource.scala +++ b/core/amber/src/main/scala/edu/uci/ics/texera/web/resource/dashboard/WorkflowAccessResource.scala @@ -1,5 +1,6 @@ package edu.uci.ics.texera.web.resource.dashboard import edu.uci.ics.texera.web.SqlServer +import edu.uci.ics.texera.web.model.common.AccessEntry import edu.uci.ics.texera.web.model.jooq.generated.Tables.{WORKFLOW_OF_USER, WORKFLOW_USER_ACCESS} import edu.uci.ics.texera.web.model.jooq.generated.tables.daos.{ UserDao, @@ -9,7 +10,6 @@ import edu.uci.ics.texera.web.model.jooq.generated.tables.daos.{ import edu.uci.ics.texera.web.model.jooq.generated.tables.pojos.WorkflowUserAccess import edu.uci.ics.texera.web.resource.auth.UserResource import edu.uci.ics.texera.web.resource.dashboard.WorkflowAccessResource.{ - UserWorkflowAccess, checkAccessLevel, context, hasNoWorkflowAccessRecord @@ -51,6 +51,30 @@ object WorkflowAccessResource { checkAccessLevel(wid, uid).eq(WorkflowAccess.READ) } + /** + * Returns an Access Object based on given wid and uid + * Searches in database for the given uid-wid pair, and returns Access Object based on search result + * + * @param wid workflow id + * @param uid user id, works with workflow id as primary keys in database + * @return Access Object indicating the access level + */ + def checkAccessLevel(wid: UInteger, uid: UInteger): WorkflowAccess.Value = { + val accessDetail = context + .select(WORKFLOW_USER_ACCESS.READ_PRIVILEGE, WORKFLOW_USER_ACCESS.WRITE_PRIVILEGE) + .from(WORKFLOW_USER_ACCESS) + .where(WORKFLOW_USER_ACCESS.WID.eq(wid).and(WORKFLOW_USER_ACCESS.UID.eq(uid))) + .fetch() + if (accessDetail.isEmpty) return WorkflowAccess.NO_RECORD + if (accessDetail.getValue(0, 1) == true) { + WorkflowAccess.WRITE + } else if (accessDetail.getValue(0, 0) == true) { + WorkflowAccess.READ + } else { + WorkflowAccess.NONE + } + } + /** * Identifies whether the given user has write access over the given workflow * @param wid workflow id @@ -81,32 +105,6 @@ object WorkflowAccessResource { checkAccessLevel(wid, uid).eq(WorkflowAccess.NO_RECORD) } - /** - * Returns an Access Object based on given wid and uid - * Searches in database for the given uid-wid pair, and returns Access Object based on search result - * - * @param wid workflow id - * @param uid user id, works with workflow id as primary keys in database - * @return Access Object indicating the access level - */ - def checkAccessLevel(wid: UInteger, uid: UInteger): WorkflowAccess.Value = { - val accessDetail = context - .select(WORKFLOW_USER_ACCESS.READ_PRIVILEGE, WORKFLOW_USER_ACCESS.WRITE_PRIVILEGE) - .from(WORKFLOW_USER_ACCESS) - .where(WORKFLOW_USER_ACCESS.WID.eq(wid).and(WORKFLOW_USER_ACCESS.UID.eq(uid))) - .fetch() - if (accessDetail.isEmpty) return WorkflowAccess.NO_RECORD - if (accessDetail.getValue(0, 1) == true) { - WorkflowAccess.WRITE - } else if (accessDetail.getValue(0, 0) == true) { - WorkflowAccess.READ - } else { - WorkflowAccess.NONE - } - } - - case class UserWorkflowAccess(userName: String, accessLevel: String) {} - object WorkflowAccess extends Enumeration { type Access = Value val READ: WorkflowAccess.Value = Value("Read") @@ -122,7 +120,6 @@ object WorkflowAccessResource { @Path("/workflow-access") @Produces(Array(MediaType.APPLICATION_JSON)) class WorkflowAccessResource() { - private val userDao = new UserDao(context.configuration()) private val workflowOfUserDao = new WorkflowOfUserDao( context.configuration @@ -136,6 +133,21 @@ class WorkflowAccessResource() { context = dslContext } + /** + * This method returns the owner of a workflow + * + * @param wid, the given workflow + * @return ownerName, the owner's name + */ + @GET + @Path("/owner/{wid}") + @Produces(Array(MediaType.APPLICATION_JSON)) + def getWorkflowOwner(@PathParam("wid") wid: UInteger): Response = { + val uid = workflowOfUserDao.fetchByWid(wid).get(0).getUid + val ownerName = userDao.fetchOneByUid(uid).getName + Response.ok(Map("ownerName" -> ownerName)).build() + } + /** * This method identifies the user access level of the given workflow * @@ -170,7 +182,7 @@ class WorkflowAccessResource() { */ @GET @Path("/list/{wid}") - def retrieveGrantedList( + def retrieveGrantedWorkflowAccessList( @PathParam("wid") wid: UInteger, @Session session: HttpSession ): Response = { @@ -183,7 +195,7 @@ class WorkflowAccessResource() { .values(user.getUid, wid) ) ) { - Response.ok(getGrantedList(wid, user.getUid)).build() + Response.ok(getGrantedWorkflowAccessList(wid, user.getUid)).build() } else { Response .status(Response.Status.UNAUTHORIZED) @@ -202,7 +214,7 @@ class WorkflowAccessResource() { * @param uid user id of current user, used to identify ownership * @return a List with corresponding information Ex: [{"Jim": "Read"}] */ - def getGrantedList(wid: UInteger, uid: UInteger): List[UserWorkflowAccess] = { + def getGrantedWorkflowAccessList(wid: UInteger, uid: UInteger): List[AccessEntry] = { val shares = context .select( WORKFLOW_USER_ACCESS.UID, @@ -222,9 +234,9 @@ class WorkflowAccessResource() { case (id, index) => val userName = userDao.fetchOneByUid(id.asInstanceOf[UInteger]).getName if (shares.getValue(index, 2) == true) { - UserWorkflowAccess(userName, "Write") + AccessEntry(userName, "Write") } else { - UserWorkflowAccess(userName, "Read") + AccessEntry(userName, "Read") } }) @@ -241,7 +253,7 @@ class WorkflowAccessResource() { @POST @Path("/revoke/{wid}/{username}") @Produces(Array(MediaType.APPLICATION_JSON)) - def revokeAccess( + def revokeWorkflowAccess( @PathParam("wid") wid: UInteger, @PathParam("username") username: String, @Session session: HttpSession @@ -290,7 +302,7 @@ class WorkflowAccessResource() { */ @POST @Path("/grant/{wid}/{username}/{accessLevel}") - def grantAccess( + def grantWorkflowAccess( @PathParam("wid") wid: UInteger, @PathParam("username") username: String, @PathParam("accessLevel") accessLevel: String, diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/web/resource/dashboard/WorkflowResource.scala b/core/amber/src/main/scala/edu/uci/ics/texera/web/resource/dashboard/WorkflowResource.scala index 6774cd87c5d..71049ef1d39 100644 --- a/core/amber/src/main/scala/edu/uci/ics/texera/web/resource/dashboard/WorkflowResource.scala +++ b/core/amber/src/main/scala/edu/uci/ics/texera/web/resource/dashboard/WorkflowResource.scala @@ -7,6 +7,7 @@ import edu.uci.ics.texera.web.model.jooq.generated.Tables.{ WORKFLOW_USER_ACCESS } import edu.uci.ics.texera.web.model.jooq.generated.tables.daos.{ + UserDao, WorkflowDao, WorkflowOfUserDao, WorkflowUserAccessDao @@ -18,6 +19,7 @@ import edu.uci.ics.texera.web.model.jooq.generated.tables.pojos.{ WorkflowUserAccess } import edu.uci.ics.texera.web.resource.auth.UserResource +import edu.uci.ics.texera.web.resource.dashboard.WorkflowResource.context import io.dropwizard.jersey.sessions.Session import org.jooq.types.UInteger @@ -25,22 +27,37 @@ import java.util import javax.servlet.http.HttpSession import javax.ws.rs._ import javax.ws.rs.core.{MediaType, Response} +import scala.collection.JavaConverters._ +import scala.collection.mutable /** * This file handles various request related to saved-workflows. * It sends mysql queries to the MysqlDB regarding the UserWorkflow Table * The details of UserWorkflowTable can be found in /core/scripts/sql/texera_ddl.sql */ + +case class DashboardWorkflowEntry( + isOwner: Boolean, + accessLevel: String, + ownerName: String, + workflow: Workflow +) +object WorkflowResource { + val context = SqlServer.createDSLContext() +} + @Path("/workflow") @Produces(Array(MediaType.APPLICATION_JSON)) class WorkflowResource { - final private val workflowDao = new WorkflowDao(SqlServer.createDSLContext.configuration) + + final private val workflowDao = new WorkflowDao(context.configuration) final private val workflowOfUserDao = new WorkflowOfUserDao( - SqlServer.createDSLContext.configuration + context.configuration ) final private val workflowUserAccessDao = new WorkflowUserAccessDao( - SqlServer.createDSLContext().configuration() + context.configuration() ) + final private val userDao = new UserDao(context.configuration()) /** * This method returns the current in-session user's workflow list based on all workflows he/she has access to @@ -51,16 +68,37 @@ class WorkflowResource { @GET @Path("/list") @Produces(Array(MediaType.APPLICATION_JSON)) - def retrieveWorkflowsBySessionUser(@Session session: HttpSession): util.List[Workflow] = { + def retrieveWorkflowsBySessionUser( + @Session session: HttpSession + ): util.List[DashboardWorkflowEntry] = { UserResource.getUser(session) match { case Some(user) => - SqlServer.createDSLContext + val dashboardWorkflowEntries: mutable.ArrayBuffer[DashboardWorkflowEntry] = + mutable.ArrayBuffer() + val workflows = context .select() .from(WORKFLOW) .join(WORKFLOW_USER_ACCESS) .on(WORKFLOW_USER_ACCESS.WID.eq(WORKFLOW.WID)) .where(WORKFLOW_USER_ACCESS.UID.eq(user.getUid)) - .fetchInto(classOf[Workflow]) + .fetch() + workflows.asScala.toList.map(workflow => { + val ownerID = workflowOfUserDao + .fetchByWid(workflow.get(1).asInstanceOf[UInteger]) + .get(0) + .getUid + val dashboardWorkflowEntry = DashboardWorkflowEntry( + ownerID + .eq(user.getUid), + WorkflowAccessResource + .checkAccessLevel(workflow.get(1).asInstanceOf[UInteger], user.getUid) + .toString, + userDao.fetchOneByUid(ownerID).getName, + workflowDao.fetchOneByWid(workflow.get(1).asInstanceOf[UInteger]) + ) + dashboardWorkflowEntries += dashboardWorkflowEntry + }) + dashboardWorkflowEntries.toList.asJava case None => new util.ArrayList() } @@ -131,11 +169,32 @@ class WorkflowResource { } } + private def insertWorkflow(workflow: Workflow, user: User): Unit = { + workflowDao.insert(workflow) + workflowOfUserDao.insert(new WorkflowOfUser(user.getUid, workflow.getWid)) + workflowUserAccessDao.insert( + new WorkflowUserAccess( + user.getUid, + workflow.getWid, + true, // readPrivilege + true // writePrivilege + ) + ) + } + + private def workflowOfUserExists(wid: UInteger, uid: UInteger): Boolean = { + workflowOfUserDao.existsById( + context + .newRecord(WORKFLOW_OF_USER.UID, WORKFLOW_OF_USER.WID) + .values(uid, wid) + ) + } + /** * This method creates and insert a new workflow to database * * @param session HttpSession - * @param workflow , a workflow + * @param workflow , a workflow to be created * @return Workflow, which contains the generated wid if not provided */ @POST @@ -149,26 +208,19 @@ class WorkflowResource { Response.status(Response.Status.BAD_REQUEST).build() } else { insertWorkflow(workflow, user) - Response.ok(workflowDao.fetchOneByWid(workflow.getWid)).build() + val resp = DashboardWorkflowEntry( + isOwner = true, + "Write", + user.getName, + workflowDao.fetchOneByWid(workflow.getWid) + ) + Response.ok(resp).build() } case None => Response.status(Response.Status.UNAUTHORIZED).build() } } - private def insertWorkflow(workflow: Workflow, user: User): Unit = { - workflowDao.insert(workflow) - workflowOfUserDao.insert(new WorkflowOfUser(user.getUid, workflow.getWid)) - workflowUserAccessDao.insert( - new WorkflowUserAccess( - user.getUid, - workflow.getWid, - true, // readPrivilege - true // writePrivilege - ) - ) - } - /** * This method deletes the workflow from database * @@ -191,12 +243,4 @@ class WorkflowResource { } } - private def workflowOfUserExists(wid: UInteger, uid: UInteger): Boolean = { - workflowOfUserDao.existsById( - SqlServer.createDSLContext - .newRecord(WORKFLOW_OF_USER.UID, WORKFLOW_OF_USER.WID) - .values(uid, wid) - ) - } - } diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/web/resource/dashboard/file/UserFileAccessResource.scala b/core/amber/src/main/scala/edu/uci/ics/texera/web/resource/dashboard/file/UserFileAccessResource.scala new file mode 100644 index 00000000000..9b52430e3e7 --- /dev/null +++ b/core/amber/src/main/scala/edu/uci/ics/texera/web/resource/dashboard/file/UserFileAccessResource.scala @@ -0,0 +1,225 @@ +package edu.uci.ics.texera.web.resource.dashboard.file + +import edu.uci.ics.texera.web.SqlServer +import edu.uci.ics.texera.web.model.common.AccessEntry +import edu.uci.ics.texera.web.model.jooq.generated.Tables.{FILE, USER_FILE_ACCESS} +import edu.uci.ics.texera.web.model.jooq.generated.tables.daos.{UserDao, UserFileAccessDao} +import edu.uci.ics.texera.web.model.jooq.generated.tables.pojos.UserFileAccess +import edu.uci.ics.texera.web.resource.auth.UserResource +import edu.uci.ics.texera.web.resource.dashboard.file.UserFileAccessResource.context +import io.dropwizard.jersey.sessions.Session +import org.jooq.DSLContext +import org.jooq.types.UInteger + +import javax.servlet.http.HttpSession +import javax.ws.rs._ +import javax.ws.rs.core.{MediaType, Response} +import scala.collection.JavaConverters._ + +/** + * A Utility Class used to for operations related to database + */ +object UserFileAccessResource { + private val context: DSLContext = SqlServer.createDSLContext + + def hasAccessTo(uid: UInteger, fid: UInteger): Boolean = { + context + .fetchExists( + context + .selectFrom(USER_FILE_ACCESS) + .where(USER_FILE_ACCESS.UID.eq(uid).and(USER_FILE_ACCESS.FID.eq(fid))) + ) + } + + def getFileId(ownerName: String, fileName: String): UInteger = { + val userDao = new UserDao(context.configuration) + val uid = userDao.fetchByName(ownerName).get(0).getUid + val file = context + .select(FILE.FID) + .from(FILE) + .where(FILE.UID.eq(uid).and(FILE.NAME.eq(fileName))) + .fetch() + file.getValue(0, 0).asInstanceOf[UInteger] + } + + def getUidOfUser(username: String): UInteger = { + val userDao = new UserDao(context.configuration) + userDao.fetchByName(username).get(0).getUid + } +} + +@Path("/user/file/access") +@Consumes(Array(MediaType.APPLICATION_JSON)) +@Produces(Array(MediaType.APPLICATION_JSON)) +class UserFileAccessResource { + final private val userFileAccessDao = new UserFileAccessDao( + context.configuration + ) + final private val userDao = new UserDao(context.configuration) + + /** + * Retrieves the list of all shared accesses of the target file + * @param fileName the file name of target file to be shared + * @param ownerName the name of the file's owner + * @param session the session identifying the current user + * @return A JSON array of File Accesses, Ex: [{username: TestUser, fileAccess: read}] + */ + @GET + @Path("list/{fileName}/{ownerName}") + def getAllSharedFileAccess( + @PathParam("fileName") fileName: String, + @PathParam("ownerName") ownerName: String, + @Session session: HttpSession + ): Response = { + UserResource.getUser(session) match { + case Some(_) => + val fid = UserFileAccessResource.getFileId(ownerName, fileName) + val fileAccess = context + .select(USER_FILE_ACCESS.UID, USER_FILE_ACCESS.READ_ACCESS, USER_FILE_ACCESS.WRITE_ACCESS) + .from(USER_FILE_ACCESS) + .where(USER_FILE_ACCESS.FID.eq(fid)) + .fetch() + Response + .ok( + fileAccess + .getValues(0) + .asScala + .toList + .zipWithIndex + .map({ + case (uid, index) => + val userName = userDao.fetchOneByUid(uid.asInstanceOf[UInteger]).getName + if (userName == ownerName) { + AccessEntry(userName, "Owner") + } else if (fileAccess.getValue(index, 2) == true) { + AccessEntry(userName, "Write") + } else { + AccessEntry(userName, "Read") + } + }) + ) + .build() + case None => + Response.status(Response.Status.UNAUTHORIZED).entity("Please Login").build() + } + } + + /** + * Checks whether a user has access to a file + * @param fid the fileId of target file to be checked + * @param uid the userId of target user to be checked + * @return success resp if has access, failed resp otherwise + */ + @GET + @Path("hasAccess/{uid}/{fid}") + def hasAccessTo( + @PathParam("uid") uid: UInteger, + @PathParam("fid") fid: UInteger, + @Session session: HttpSession + ): Response = { + UserResource.getUser(session) match { + case Some(_) => + val existence = context + .fetchExists( + context + .selectFrom(USER_FILE_ACCESS) + .where(USER_FILE_ACCESS.UID.eq(uid).and(USER_FILE_ACCESS.FID.eq(fid))) + ) + if (existence) { + Response.ok().entity().build() + } else { + Response.status(Response.Status.BAD_REQUEST).entity("user has no access to file").build() + } + case None => Response.status(Response.Status.UNAUTHORIZED).entity("please login").build() + } + } + + /** + * Grants a specific type of access of a file to a user + * @param fileName the file name of target file to be shared + * @param ownerName the name of the file's owner + * @param username the username of target user to be shared to + * @param accessType the type of access to be shared + * @param session the session identifying the current user + * @return A successful resp if granted, failed resp otherwise + */ + @POST + @Path("grant/{fileName}/{ownerName}/{username}/{accessType}") + def shareFileTo( + @PathParam("username") username: String, + @PathParam("fileName") fileName: String, + @PathParam("ownerName") ownerName: String, + @PathParam("accessType") accessType: String, + @Session session: HttpSession + ): Response = { + UserResource.getUser(session) match { + case Some(_) => + val fid = UserFileAccessResource.getFileId(ownerName, fileName) + val uid: UInteger = + try { + userDao.fetchByName(username).get(0).getUid + } catch { + case _: NullPointerException => + return Response + .status(Response.Status.BAD_REQUEST) + .entity("Target User Does Not Exist") + .build() + } + + if (UserFileAccessResource.hasAccessTo(uid, fid)) { + if (accessType == "read") { + userFileAccessDao.update(new UserFileAccess(uid, fid, true, false)) + } else { + userFileAccessDao.update(new UserFileAccess(uid, fid, true, true)) + } + Response.ok().build() + } else { + if (accessType == "read") { + userFileAccessDao.insert(new UserFileAccess(uid, fid, true, false)) + } else { + userFileAccessDao.insert(new UserFileAccess(uid, fid, true, true)) + } + Response.ok().build() + } + } + } + + /** + * Revoke a user's access to a file + * @param fileName the file name of target file to be shared + * @param ownerName the name of the file's owner + * @param username the username of target user whose access is about to be revoked + * @param session the session identifying the current user + * @return A successful resp if granted, failed resp otherwise + */ + @POST + @Path("/revoke/{fileName}/{ownerName}/{username}") + def revokeFileAccess( + @PathParam("fileName") fileName: String, + @PathParam("ownerName") ownerName: String, + @PathParam("username") username: String, + @Session session: HttpSession + ): Response = { + UserResource.getUser(session) match { + case Some(_) => + val fid = UserFileAccessResource.getFileId(ownerName, fileName) + val uid: UInteger = + try { + userDao.fetchByName(username).get(0).getUid + } catch { + case _: NullPointerException => + return Response + .status(Response.Status.BAD_REQUEST) + .entity("Target User Does Not Exist") + .build() + } + context + .deleteFrom(USER_FILE_ACCESS) + .where(USER_FILE_ACCESS.UID.eq(uid).and(USER_FILE_ACCESS.FID.eq(fid))) + .execute() + Response.ok().build() + case None => + Response.status(Response.Status.UNAUTHORIZED).entity("please login").build() + } + } +} diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/web/resource/dashboard/file/UserFileResource.scala b/core/amber/src/main/scala/edu/uci/ics/texera/web/resource/dashboard/file/UserFileResource.scala index 48df10cce6b..bcf6b60b211 100644 --- a/core/amber/src/main/scala/edu/uci/ics/texera/web/resource/dashboard/file/UserFileResource.scala +++ b/core/amber/src/main/scala/edu/uci/ics/texera/web/resource/dashboard/file/UserFileResource.scala @@ -1,13 +1,15 @@ package edu.uci.ics.texera.web.resource.dashboard.file import edu.uci.ics.texera.web.SqlServer -import edu.uci.ics.texera.web.model.jooq.generated.Tables.FILE -import edu.uci.ics.texera.web.model.jooq.generated.tables.daos.FileDao -import edu.uci.ics.texera.web.model.jooq.generated.tables.pojos.File +import edu.uci.ics.texera.web.model.jooq.generated.Tables.{FILE, USER_FILE_ACCESS} +import edu.uci.ics.texera.web.model.jooq.generated.tables.daos.{FileDao, UserDao, UserFileAccessDao} +import edu.uci.ics.texera.web.model.jooq.generated.tables.pojos.{File, User, UserFileAccess} import edu.uci.ics.texera.web.resource.auth.UserResource +import edu.uci.ics.texera.web.resource.dashboard.file.UserFileResource.context import io.dropwizard.jersey.sessions.Session import org.apache.commons.lang3.tuple.Pair import org.glassfish.jersey.media.multipart.{FormDataContentDisposition, FormDataParam} +import org.jooq.DSLContext import org.jooq.types.UInteger import java.io.InputStream @@ -16,17 +18,32 @@ import java.util import javax.servlet.http.HttpSession import javax.ws.rs._ import javax.ws.rs.core.{MediaType, Response} +import scala.collection.JavaConverters._ +import scala.collection.mutable /** * Model `File` corresponds to `core/new-gui/src/app/common/type/user-file.ts` (frontend). */ +case class DashboardFileEntry( + ownerName: String, + accessLevel: String, + isOwner: Boolean, + file: File +) + +object UserFileResource { + private val context: DSLContext = SqlServer.createDSLContext +} @Path("/user/file") @Consumes(Array(MediaType.APPLICATION_JSON)) @Produces(Array(MediaType.APPLICATION_JSON)) class UserFileResource { - - final private val fileDao = new FileDao(SqlServer.createDSLContext.configuration) + final private val fileDao = new FileDao(context.configuration) + final private val userFileAccessDao = new UserFileAccessDao( + context.configuration + ) + final private val userDao = new UserDao(context.configuration) /** * This method will handle the request to upload a single file. @@ -65,36 +82,98 @@ class UserFileResource { description ) ) + val fid = context + .select(FILE.FID) + .from(FILE) + .where(FILE.UID.eq(userID).and(FILE.NAME.eq(fileName))) + .fetch() + .getValue(0, 0) + .asInstanceOf[UInteger] + userFileAccessDao.insert( + new UserFileAccess(userID, fid, true, true) + ) Response.ok().build() case None => Response.status(Response.Status.UNAUTHORIZED).build() } } + /** + * This method returns a list fo all files accessible by the current user + * @param session the session indicating current logged-in user + * @return + */ @GET @Path("/list") - def listUserFiles(@Session session: HttpSession): util.List[File] = { + def listUserFiles(@Session session: HttpSession): util.List[DashboardFileEntry] = { UserResource.getUser(session) match { - case Some(user) => getUserFileRecord(user.getUid) - case None => new util.ArrayList[File]() + case Some(user) => getUserFileRecord(user) + case None => new util.ArrayList[DashboardFileEntry]() } } + private def getUserFileRecord(user: User): util.List[DashboardFileEntry] = { + val accesses = userFileAccessDao.fetchByUid(user.getUid) + val fileEntries: mutable.ArrayBuffer[DashboardFileEntry] = mutable.ArrayBuffer() + accesses.asScala.toList.map(access => { + val fid = access.getFid + val file = fileDao.fetchOneByFid(fid) + var accessLevel = "None" + if (access.getWriteAccess) { + accessLevel = "Write" + } else if (access.getReadAccess) { + accessLevel = "Read" + } else { + accessLevel = "None" + } + val ownerName = userDao.fetchOneByUid(file.getUid).getName + fileEntries += DashboardFileEntry( + ownerName, + accessLevel, + ownerName == user.getName, + file + ) + }) + fileEntries.toList.asJava + } + + /** + * This method deletes a file from a user's repository + * @param fileName the name of file being deleted + * @param ownerName the name of the file's owner + * @param session the session indicating the current user + * @return + */ @DELETE - @Path("/delete/{fileID}") + @Path("/delete/{fileName}/{ownerName}") def deleteUserFile( - @PathParam("fileID") fileID: UInteger, + @PathParam("fileName") fileName: String, + @PathParam("ownerName") ownerName: String, @Session session: HttpSession ): Response = { UserResource.getUser(session) match { case Some(user) => + val fileID = UserFileAccessResource.getFileId(ownerName, fileName) val userID = user.getUid - // TODO: add user check - val filePath = fileDao.fetchOneByFid(fileID).getPath - UserFileUtils.deleteFile(Paths.get(filePath)) - fileDao.deleteById(fileID) - Response.ok().build() + val hasWriteAccess = context + .select(USER_FILE_ACCESS.WRITE_ACCESS) + .from(USER_FILE_ACCESS) + .where(USER_FILE_ACCESS.UID.eq(userID).and(USER_FILE_ACCESS.FID.eq(fileID))) + .fetch() + .getValue(0, 0) + if (hasWriteAccess == false) { + Response + .status(Response.Status.UNAUTHORIZED) + .entity("You do not have the access to deleting the file") + .build() + } else { + val filePath = fileDao.fetchOneByFid(fileID).getPath + UserFileUtils.deleteFile(Paths.get(filePath)) + fileDao.deleteById(fileID) + Response.ok().build() + } + case None => Response.status(Response.Status.UNAUTHORIZED).build() } @@ -121,12 +200,6 @@ class UserFileResource { } - private def getUserFileRecord(userID: UInteger): util.List[File] = { - - // TODO: verify user in session? - fileDao.fetchByUid(userID) - } - private def validateFileName(fileName: String, userID: UInteger): Pair[Boolean, String] = { if (fileName == null) Pair.of(false, "file name cannot be null") else if (fileName.trim.isEmpty) Pair.of(false, "file name cannot be empty") @@ -135,8 +208,8 @@ class UserFileResource { } private def isFileNameExisted(fileName: String, userID: UInteger): Boolean = - SqlServer.createDSLContext.fetchExists( - SqlServer.createDSLContext + context.fetchExists( + context .selectFrom(FILE) .where(FILE.UID.equal(userID).and(FILE.NAME.equal(fileName))) ) diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/web/resource/dashboard/file/UserFileUtils.scala b/core/amber/src/main/scala/edu/uci/ics/texera/web/resource/dashboard/file/UserFileUtils.scala index e38fc92516c..1ad264b993d 100644 --- a/core/amber/src/main/scala/edu/uci/ics/texera/web/resource/dashboard/file/UserFileUtils.scala +++ b/core/amber/src/main/scala/edu/uci/ics/texera/web/resource/dashboard/file/UserFileUtils.scala @@ -3,36 +3,31 @@ package edu.uci.ics.texera.web.resource.dashboard.file import edu.uci.ics.amber.engine.common.amberexception.WorkflowRuntimeException import edu.uci.ics.amber.error.WorkflowRuntimeError import edu.uci.ics.texera.Utils +import edu.uci.ics.texera.web.SqlServer +import edu.uci.ics.texera.web.model.jooq.generated.tables.daos.FileDao +import org.jooq.types.UInteger import java.io._ -import java.nio.file.{Files, Path} +import java.nio.file.{Files, Path, Paths} object UserFileUtils { - private val FILE_CONTAINER_PATH: Path = + private val FILE_CONTAINER_PATH: Path = { Utils.amberHomePath.resolve("user-resources").resolve("files") + } + private val fileDao = new FileDao(SqlServer.createDSLContext.configuration) def storeFile(fileStream: InputStream, fileName: String, userID: String): Unit = { createFileDirectoryIfNotExist(UserFileUtils.getFileDirectory(userID)) checkFileDuplicate(UserFileUtils.getFilePath(userID, fileName)) writeToFile(UserFileUtils.getFilePath(userID, fileName), fileStream) } - def getFilePath(userID: String, fileName: String): Path = + def getFilePath(userID: String, fileName: String): Path = { getFileDirectory(userID).resolve(fileName) + } def getFileDirectory(userID: String): Path = FILE_CONTAINER_PATH.resolve(userID) - @throws[FileIOException] - def deleteFile(filePath: Path): Unit = { - try Files.deleteIfExists(filePath) - catch { - case e: Exception => - throw FileIOException( - "Error occur when deleting the file " + filePath.toString + ": " + e.getMessage - ) - } - } - @throws[FileIOException] private def createFileDirectoryIfNotExist(directoryPath: Path): Unit = { if (!Files.exists(directoryPath)) @@ -67,6 +62,31 @@ object UserFileUtils { } } + def getFilePathByInfo(ownerName: String, fileName: String, uid: UInteger): Option[Path] = { + val fid = UserFileAccessResource.getFileId(ownerName, fileName) + getFilePathByIds(uid, fid) + } + + def getFilePathByIds(uid: UInteger, fid: UInteger): Option[Path] = { + if (UserFileAccessResource.hasAccessTo(uid, fid)) { + val path = fileDao.fetchByFid(fid).get(0).getPath + Some(Paths.get(path)) + } else { + None + } + } + + @throws[FileIOException] + def deleteFile(filePath: Path): Unit = { + try Files.deleteIfExists(filePath) + catch { + case e: Exception => + throw FileIOException( + "Error occur when deleting the file " + filePath.toString + ": " + e.getMessage + ) + } + } + case class FileIOException(message: String) extends WorkflowRuntimeException( WorkflowRuntimeError( diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/source/scan/ScanSourceOpDesc.scala b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/source/scan/ScanSourceOpDesc.scala index 841ee784808..74e9322d142 100644 --- a/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/source/scan/ScanSourceOpDesc.scala +++ b/core/amber/src/main/scala/edu/uci/ics/texera/workflow/operators/source/scan/ScanSourceOpDesc.scala @@ -57,15 +57,27 @@ abstract class ScanSourceOpDesc extends SourceOperatorDescriptor { override def setContext(workflowContext: WorkflowContext): Unit = { super.setContext(workflowContext) - if (context.userID.isDefined) - // if context has a valid user ID, the fileName will be a file name, + if (fileName.isEmpty) { + throw new RuntimeException("no input file name") + } + + if (context.userID.isDefined) { + // if context has a valid user ID, the fileName will be in the following format: + // ownerName/fileName // resolve fileName to be the actual file path. - filePath = Option( - UserFileUtils.getFilePath(context.userID.get.toString, fileName.get).toString - ) - else + val splitNames = fileName.get.split("/") + filePath = UserFileUtils + .getFilePathByInfo( + ownerName = splitNames.apply(0), + fileName = splitNames.apply(1), + context.userID.get + ) + .map(_.toString) + + } else { // otherwise, the fileName will be inputted by user, which is the filePath. filePath = fileName + } } diff --git a/core/new-gui/src/app/app.module.ts b/core/new-gui/src/app/app.module.ts index 82ad372c006..ef4e470c8c9 100644 --- a/core/new-gui/src/app/app.module.ts +++ b/core/new-gui/src/app/app.module.ts @@ -43,10 +43,10 @@ import { TEXERA_FORMLY_CONFIG } from './common/formly/formly-config'; import { MultiSchemaTypeComponent } from './common/formly/multischema.type'; import { NullTypeComponent } from './common/formly/null.type'; import { ObjectTypeComponent } from './common/formly/object.type'; -import { UserDictionaryUploadService } from './common/service/user/user-dictionary/user-dictionary-upload.service'; -import { UserDictionaryService } from './common/service/user/user-dictionary/user-dictionary.service'; -import { UserFileUploadService } from './common/service/user/user-file/user-file-upload.service'; -import { UserFileService } from './common/service/user/user-file/user-file.service'; +import { UserDictionaryUploadService } from './dashboard/service/user-dictionary/user-dictionary-upload.service'; +import { UserDictionaryService } from './dashboard/service/user-dictionary/user-dictionary.service'; +import { UserFileUploadService } from './dashboard/service/user-file/user-file-upload.service'; +import { UserFileService } from './dashboard/service/user-file/user-file.service'; import { UserService } from './common/service/user/user.service'; import { DashboardComponent } from './dashboard/component/dashboard.component'; import { FeatureBarComponent } from './dashboard/component/feature-bar/feature-bar.component'; @@ -82,7 +82,8 @@ import { WorkflowEditorComponent } from './workspace/component/workflow-editor/w import { WorkspaceComponent } from './workspace/component/workspace.component'; import { ResultDownloadComponent } from './workspace/component/navigation/result-download/result-download.component'; import { GoogleApiModule, NG_GAPI_CONFIG } from 'ng-gapi'; -import { NgbdModalShareAccessComponent } from './dashboard/component/feature-container/saved-workflow-section/ngbd-modal-share-access/ngbd-modal-share-access.component'; +import { NgbdModalWorkflowShareAccessComponent } from './dashboard/component/feature-container/saved-workflow-section/ngbd-modal-share-access/ngbd-modal-workflow-share-access.component'; +import { NgbdModalUserFileShareAccessComponent } from './dashboard/component/feature-container/user-file-section/ngbd-modal-file-share-access/ngbd-modal-user-file-share-access.component'; import { NzCardModule } from 'ng-zorro-antd/card'; import { NzStatisticModule } from 'ng-zorro-antd/statistic'; import { NzTagModule } from 'ng-zorro-antd/tag'; @@ -132,7 +133,8 @@ registerLocaleData(en); CodeEditorDialogComponent, TypeCastingDisplayComponent, ResultDownloadComponent, - NgbdModalShareAccessComponent + NgbdModalWorkflowShareAccessComponent, + NgbdModalUserFileShareAccessComponent ], imports: [ BrowserModule, @@ -194,7 +196,7 @@ registerLocaleData(en); NgbdModalUserLoginComponent, RowModalComponent, NgbdModalFileAddComponent, - NgbdModalShareAccessComponent + NgbdModalWorkflowShareAccessComponent ], providers: [ HttpClientModule, diff --git a/core/new-gui/src/app/common/app-setting.ts b/core/new-gui/src/app/common/app-setting.ts index 0f29cf96667..297040b67a0 100644 --- a/core/new-gui/src/app/common/app-setting.ts +++ b/core/new-gui/src/app/common/app-setting.ts @@ -1,4 +1,4 @@ -import { environment } from './../../environments/environment'; +import { environment } from '../../environments/environment'; export class AppSettings { public static getApiEndpoint(): string { diff --git a/core/new-gui/src/app/common/service/user/stub-user.service.ts b/core/new-gui/src/app/common/service/user/stub-user.service.ts index a741152cfc6..97003035f13 100644 --- a/core/new-gui/src/app/common/service/user/stub-user.service.ts +++ b/core/new-gui/src/app/common/service/user/stub-user.service.ts @@ -4,6 +4,7 @@ import { Observable } from 'rxjs/Observable'; import { Subject } from 'rxjs'; import { User } from '../../type/user'; import { UserService } from './user.service'; +import { PublicInterfaceOf } from '../../util/stub'; export const MOCK_USER_ID = 1; export const MOCK_USER_NAME = 'testUser'; @@ -12,9 +13,6 @@ export const MOCK_USER = { uid: MOCK_USER_ID }; -type PublicInterfaceOf = { - [Member in keyof Class]: Class[Member]; -}; /** * This StubUserService is to test other service's functionality that depends on UserService diff --git a/core/new-gui/src/app/common/service/user/user-file/user-file.service.spec.ts b/core/new-gui/src/app/common/service/user/user-file/user-file.service.spec.ts deleted file mode 100644 index 017f05636df..00000000000 --- a/core/new-gui/src/app/common/service/user/user-file/user-file.service.spec.ts +++ /dev/null @@ -1,68 +0,0 @@ -// import { TestBed } from '@angular/core/testing'; - -// import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; -// import { UserFile } from '../../../type/user-file'; - -// import { UserFileService, USER_FILE_LIST_URL } from './user-file.service'; -// import { UserService } from '../user.service'; -// import { StubUserService, MOCK_USER } from '../stub-user.service'; -// import { AppSettings } from 'src/app/common/app-setting'; - -// const id = 1; -// const name = 'testFile'; -// const path = 'test/path'; -// const description = 'this is a test file'; -// const size = 1024; -// const testFile: UserFile = { -// id: id, -// name: name, -// path: path, -// size: size, -// description: description -// }; - -// describe('UserFileService', () => { -// let httpMock: HttpTestingController; -// let service: UserFileService; - -// beforeEach(() => { -// TestBed.configureTestingModule({ -// providers: [ -// UserFileService, -// { provide: UserService, useClass: StubUserService } -// ], -// imports: [ -// HttpClientTestingModule -// ] -// }); -// httpMock = TestBed.get(HttpTestingController); -// service = TestBed.get(UserFileService); -// }); - -// afterEach(() => { -// httpMock.verify(); -// }); - -// it('should be created', () => { -// expect(service).toBeTruthy(); -// }); - -// it('should contain no files by default', () => { -// expect(service.getUserFiles()).toBeFalsy(); -// }); - - -// it('should refresh file after user login', () => { -// const spy = spyOn(service, 'refreshFiles').and.callThrough(); - -// const stubUserService: StubUserService = TestBed.get(UserService); -// stubUserService.userChangedEvent.next(MOCK_USER); - -// expect(spy).toHaveBeenCalled(); - -// const req = httpMock.expectOne(`${AppSettings.getApiEndpoint()}/${USER_FILE_LIST_URL}`); -// expect(req.request.method).toEqual('GET'); -// req.flush([testFile]); -// }); - -// }); diff --git a/core/new-gui/src/app/common/service/user/user-file/user-file.service.ts b/core/new-gui/src/app/common/service/user/user-file/user-file.service.ts deleted file mode 100644 index d8081d99f9b..00000000000 --- a/core/new-gui/src/app/common/service/user/user-file/user-file.service.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { HttpClient } from '@angular/common/http'; -import { Injectable } from '@angular/core'; -import { Observable, Subject } from 'rxjs'; -import { AppSettings } from '../../../app-setting'; -import { UserFile } from '../../../type/user-file'; -import { UserService } from '../user.service'; - -export const USER_FILE_LIST_URL = 'user/file/list'; -export const USER_FILE_DELETE_URL = 'user/file/delete'; - -@Injectable({ - providedIn: 'root' -}) -export class UserFileService { - private userFiles: UserFile[] = []; - private userFilesChanged = new Subject(); - - constructor( - private http: HttpClient, - private userService: UserService - ) { - this.detectUserChanges(); - } - - /** - * this function will return the fileArray store in the service. - * This is required for HTML page since HTML can only loop through collection instead of index number. - * You can change the UserFile inside the array but do not change the array itself. - */ - public getUserFiles(): ReadonlyArray { - return this.userFiles; - } - - public getUserFilesChangedEvent(): Observable { - return this.userFilesChanged.asObservable(); - } - - /** - * retrieve the files from the backend and store in the user-file service. - * these file can be accessed by function {@link getFileArray} - */ - public refreshFiles(): void { - if (!this.userService.isLogin()) { - return; - } - - this.fetchFileList().subscribe( - files => { - this.userFiles = files; - this.userFilesChanged.next(); - } - ); - } - - /** - * delete the targetFile in the backend. - * this function will automatically refresh the files in the service when succeed. - * @param targetFile - */ - public deleteFile(targetFile: UserFile): void { - this.http.delete(`${AppSettings.getApiEndpoint()}/${USER_FILE_DELETE_URL}/${targetFile.fid}`).subscribe( - () => this.refreshFiles(), - err => alert('Can\'t delete the file: ' + err.error) - ); - } - - /** - * convert the input file size to the human readable size by adding the unit at the end. - * eg. 2048 -> 2.0 KB - * @param fileSize - */ - public addFileSizeUnit(fileSize: number): string { - if (fileSize <= 1024) { - return fileSize + ' Byte'; - } - - let i = 0; - const byteUnits = [' Byte', ' KB', ' MB', ' GB', ' TB', ' PB', ' EB', ' ZB', ' YB']; - while (fileSize > 1024 && i < byteUnits.length - 1) { - fileSize = fileSize / 1024; - i++; - } - return Math.max(fileSize, 0.1).toFixed(1) + byteUnits[i]; - } - - private fetchFileList(): Observable { - return this.http.get(`${AppSettings.getApiEndpoint()}/${USER_FILE_LIST_URL}`); - } - - /** - * refresh the files in the service whenever the user changes. - */ - private detectUserChanges(): void { - this.userService.userChanged().subscribe( - () => { - if (this.userService.isLogin()) { - this.refreshFiles(); - } else { - this.clearUserFile(); - } - } - ); - } - - private clearUserFile(): void { - this.userFiles = []; - this.userFilesChanged.next(); - } - -} diff --git a/core/new-gui/src/app/common/service/user/workflow-access-control/stub-workflow-grant-access.service.ts b/core/new-gui/src/app/common/service/user/workflow-access-control/stub-workflow-grant-access.service.ts deleted file mode 100644 index a626881ee26..00000000000 --- a/core/new-gui/src/app/common/service/user/workflow-access-control/stub-workflow-grant-access.service.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { Injectable } from '@angular/core'; -import { Observable } from 'rxjs/Observable'; -import { of } from 'rxjs'; -import { UserWorkflowAccess, WorkflowGrantAccessService } from './workflow-grant-access.service'; -import { Workflow, WorkflowContent } from '../../../type/workflow'; -import { jsonCast } from '../../../util/storage'; - -export const MOCK_WORKFLOW: Workflow = { - wid: 1, - name: 'project 1', - content: jsonCast(' {"operators":[],"operatorPositions":{},"links":[],"groups":[],"breakpoints":{}}'), - creationTime: 1, - lastModifiedTime: 2, -}; - -type PublicInterfaceOf = { - [Member in keyof Class]: Class[Member]; -}; - -@Injectable() -export class StubWorkflowGrantAccessService implements PublicInterfaceOf { - - - public workflow: Workflow; - - public message: string = 'This is testing'; - - public mapString: UserWorkflowAccess[] = []; - - constructor() { - this.workflow = MOCK_WORKFLOW; - } - - public retrieveGrantedList(workflow: Workflow): Observable[]> { - return of(this.mapString); - } - - public grantAccess(workflow: Workflow, username: string, accessLevel: string): Observable { - return of(); - } - - - public revokeAccess(workflow: Workflow, username: string): Observable { - return of(); - } - - -} diff --git a/core/new-gui/src/app/common/service/user/workflow-persist/workflow-persist.service.spec.ts b/core/new-gui/src/app/common/service/workflow-persist/workflow-persist.service.spec.ts similarity index 92% rename from core/new-gui/src/app/common/service/user/workflow-persist/workflow-persist.service.spec.ts rename to core/new-gui/src/app/common/service/workflow-persist/workflow-persist.service.spec.ts index 809a435db17..ea57e151826 100644 --- a/core/new-gui/src/app/common/service/user/workflow-persist/workflow-persist.service.spec.ts +++ b/core/new-gui/src/app/common/service/workflow-persist/workflow-persist.service.spec.ts @@ -1,8 +1,8 @@ import { TestBed } from '@angular/core/testing'; import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; import { WorkflowPersistService } from './workflow-persist.service'; -import { jsonCast } from '../../../util/storage'; -import { WorkflowContent } from '../../../type/workflow'; +import { jsonCast } from '../../util/storage'; +import { WorkflowContent } from '../../type/workflow'; describe('WorkflowPersistService', () => { let service: WorkflowPersistService; @@ -56,8 +56,8 @@ describe('WorkflowPersistService', () => { it('should check if workflow content and name returned correctly', () => { service.createWorkflow(jsonCast(testContent), 'testname').last().subscribe( (value) => { - expect(value.name).toEqual('testname_copy'); - expect(value.content).toEqual(jsonCast(testContent)); + expect(value.workflow.name).toEqual('testname_copy'); + expect(value.workflow.content).toEqual(jsonCast(testContent)); } ); }); diff --git a/core/new-gui/src/app/common/service/user/workflow-persist/workflow-persist.service.ts b/core/new-gui/src/app/common/service/workflow-persist/workflow-persist.service.ts similarity index 70% rename from core/new-gui/src/app/common/service/user/workflow-persist/workflow-persist.service.ts rename to core/new-gui/src/app/common/service/workflow-persist/workflow-persist.service.ts index 753d5ed29e2..315ed5a1a94 100644 --- a/core/new-gui/src/app/common/service/user/workflow-persist/workflow-persist.service.ts +++ b/core/new-gui/src/app/common/service/workflow-persist/workflow-persist.service.ts @@ -2,9 +2,10 @@ import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { Observable } from 'rxjs/Observable'; import { map } from 'rxjs/operators'; -import { AppSettings } from '../../../app-setting'; -import { Workflow, WorkflowContent } from '../../../type/workflow'; -import { jsonCast } from '../../../util/storage'; +import { AppSettings } from '../../app-setting'; +import { Workflow, WorkflowContent } from '../../type/workflow'; +import { jsonCast } from '../../util/storage'; +import { DashboardWorkflowEntry } from '../../../dashboard/type/dashboard-workflow-entry'; export const WORKFLOW_URL = 'workflow'; export const WORKFLOW_PERSIST_URL = WORKFLOW_URL + '/persist'; @@ -37,13 +38,14 @@ export class WorkflowPersistService { * @param newWorkflowName * @param newWorkflowContent */ - public createWorkflow(newWorkflowContent: WorkflowContent, newWorkflowName: string = 'Untitled workflow'): Observable { - return this.http.post(`${AppSettings.getApiEndpoint()}/${WORKFLOW_CREATE_URL}`, { + public createWorkflow(newWorkflowContent: WorkflowContent, newWorkflowName: string = 'Untitled workflow'): + Observable { + return this.http.post(`${AppSettings.getApiEndpoint()}/${WORKFLOW_CREATE_URL}`, { name: newWorkflowName, content: JSON.stringify(newWorkflowContent) }) - .filter((createdWorkflow: Workflow) => createdWorkflow != null) - .pipe(map(WorkflowPersistService.parseWorkflowInfo)); + .filter((createdWorkflow: DashboardWorkflowEntry) => createdWorkflow != null); + } /** @@ -56,12 +58,21 @@ export class WorkflowPersistService { .pipe(map(WorkflowPersistService.parseWorkflowInfo)); } + /** * retrieves a list of workflows from backend database that belongs to the user in the session. */ - public retrieveWorkflowsBySessionUser(): Observable { - return this.http.get(`${AppSettings.getApiEndpoint()}/${WORKFLOW_LIST_URL}`) - .pipe(map((workflows: Workflow[]) => workflows.map(WorkflowPersistService.parseWorkflowInfo))); + public retrieveWorkflowsBySessionUser(): Observable { + return this.http.get(`${AppSettings.getApiEndpoint()}/${WORKFLOW_LIST_URL}`) + .pipe(map((dashboardWorkflowEntries: DashboardWorkflowEntry[]) => + dashboardWorkflowEntries.map((workflowEntry: DashboardWorkflowEntry) => { + return { + ...workflowEntry, + dashboardWorkflowEntry: WorkflowPersistService.parseWorkflowInfo(workflowEntry.workflow) + }; + }) + ) + ); } /** diff --git a/core/new-gui/src/app/common/util/stub.ts b/core/new-gui/src/app/common/util/stub.ts new file mode 100644 index 00000000000..d4c339278b9 --- /dev/null +++ b/core/new-gui/src/app/common/util/stub.ts @@ -0,0 +1,3 @@ +export type PublicInterfaceOf = { + [Member in keyof Class]: Class[Member]; +}; diff --git a/core/new-gui/src/app/dashboard/component/dashboard.component.ts b/core/new-gui/src/app/dashboard/component/dashboard.component.ts index 1ec60d51410..332abf8e481 100644 --- a/core/new-gui/src/app/dashboard/component/dashboard.component.ts +++ b/core/new-gui/src/app/dashboard/component/dashboard.component.ts @@ -1,5 +1,5 @@ import { Component, OnInit } from '@angular/core'; -import { WorkflowPersistService } from '../../common/service/user/workflow-persist/workflow-persist.service'; +import { WorkflowPersistService } from '../../common/service/workflow-persist/workflow-persist.service'; /** * dashboardComponent is the component which contains all the subcomponents diff --git a/core/new-gui/src/app/dashboard/component/feature-container/saved-workflow-section/ngbd-modal-delete-workflow/ngbd-modal-delete-workflow.component.html b/core/new-gui/src/app/dashboard/component/feature-container/saved-workflow-section/ngbd-modal-delete-workflow/ngbd-modal-delete-workflow.component.html index ba595641e5e..90d1e0f2958 100644 --- a/core/new-gui/src/app/dashboard/component/feature-container/saved-workflow-section/ngbd-modal-delete-workflow/ngbd-modal-delete-workflow.component.html +++ b/core/new-gui/src/app/dashboard/component/feature-container/saved-workflow-section/ngbd-modal-delete-workflow/ngbd-modal-delete-workflow.component.html @@ -7,7 +7,7 @@ diff --git a/core/new-gui/src/app/dashboard/component/feature-container/saved-workflow-section/ngbd-modal-delete-workflow/ngbd-modal-delete-workflow.component.spec.ts b/core/new-gui/src/app/dashboard/component/feature-container/saved-workflow-section/ngbd-modal-delete-workflow/ngbd-modal-delete-workflow.component.spec.ts index 17cf9b503be..c3f479a283b 100644 --- a/core/new-gui/src/app/dashboard/component/feature-container/saved-workflow-section/ngbd-modal-delete-workflow/ngbd-modal-delete-workflow.component.spec.ts +++ b/core/new-gui/src/app/dashboard/component/feature-container/saved-workflow-section/ngbd-modal-delete-workflow/ngbd-modal-delete-workflow.component.spec.ts @@ -8,20 +8,21 @@ import { FormsModule } from '@angular/forms'; import { HttpClientModule } from '@angular/common/http'; import { NgbdModalDeleteWorkflowComponent } from './ngbd-modal-delete-workflow.component'; +import { Workflow, WorkflowContent } from '../../../../../common/type/workflow'; +import { jsonCast } from '../../../../../common/util/storage'; describe('NgbdModalDeleteProjectComponent', () => { let component: NgbdModalDeleteWorkflowComponent; let fixture: ComponentFixture; - let deletecomponent: NgbdModalDeleteWorkflowComponent; - let deletefixture: ComponentFixture; - - const sampleProject = { - name: 'project 1', + let deleteComponent: NgbdModalDeleteWorkflowComponent; + let deleteFixture: ComponentFixture; + const targetWorkflow: Workflow = { + name: 'workflow 1', wid: 4, - content: '{}', - creationTime: '2017-10-25T12:34:50Z', - lastModifiedTime: '2018-01-17T06:26:50Z', + content: jsonCast('{}'), + creationTime: 1, + lastModifiedTime: 2, }; beforeEach(async(() => { @@ -49,12 +50,12 @@ describe('NgbdModalDeleteProjectComponent', () => { }); it('deleteProjectComponent deleteSavedProject return a result of true', () => { - deletefixture = TestBed.createComponent(NgbdModalDeleteWorkflowComponent); - deletecomponent = deletefixture.componentInstance; - deletecomponent.workflow = sampleProject; + deleteFixture = TestBed.createComponent(NgbdModalDeleteWorkflowComponent); + deleteComponent = deleteFixture.componentInstance; + deleteComponent.workflow = targetWorkflow; - spyOn(deletecomponent.activeModal, 'close'); - deletecomponent.deleteSavedWorkflow(); - expect(deletecomponent.activeModal.close).toHaveBeenCalledWith(true); + spyOn(deleteComponent.activeModal, 'close'); + deleteComponent.deleteSavedWorkflow(); + expect(deleteComponent.activeModal.close).toHaveBeenCalledWith(true); }); }); diff --git a/core/new-gui/src/app/dashboard/component/feature-container/saved-workflow-section/ngbd-modal-delete-workflow/ngbd-modal-delete-workflow.component.ts b/core/new-gui/src/app/dashboard/component/feature-container/saved-workflow-section/ngbd-modal-delete-workflow/ngbd-modal-delete-workflow.component.ts index 3a523f142d4..c983872af36 100644 --- a/core/new-gui/src/app/dashboard/component/feature-container/saved-workflow-section/ngbd-modal-delete-workflow/ngbd-modal-delete-workflow.component.ts +++ b/core/new-gui/src/app/dashboard/component/feature-container/saved-workflow-section/ngbd-modal-delete-workflow/ngbd-modal-delete-workflow.component.ts @@ -15,14 +15,13 @@ import { Workflow } from '../../../../../common/type/workflow'; }) export class NgbdModalDeleteWorkflowComponent { - // TODO: rewrite this. - @Input() workflow: { name: string } = {name: 'name'}; + @Input() workflow!: Workflow; constructor(public activeModal: NgbActiveModal) { } /** - * deleteSavedProject sends the user + * deleteSavedWorkflow sends the user * confirm to the main component. It does not call any method in service. */ public deleteSavedWorkflow(): void { diff --git a/core/new-gui/src/app/dashboard/component/feature-container/saved-workflow-section/ngbd-modal-share-access/ngbd-modal-share-access.component.html b/core/new-gui/src/app/dashboard/component/feature-container/saved-workflow-section/ngbd-modal-share-access/ngbd-modal-workflow-share-access.component.html similarity index 83% rename from core/new-gui/src/app/dashboard/component/feature-container/saved-workflow-section/ngbd-modal-share-access/ngbd-modal-share-access.component.html rename to core/new-gui/src/app/dashboard/component/feature-container/saved-workflow-section/ngbd-modal-share-access/ngbd-modal-workflow-share-access.component.html index b02c60aeb0f..998163187e0 100644 --- a/core/new-gui/src/app/dashboard/component/feature-container/saved-workflow-section/ngbd-modal-share-access/ngbd-modal-share-access.component.html +++ b/core/new-gui/src/app/dashboard/component/feature-container/saved-workflow-section/ngbd-modal-share-access/ngbd-modal-workflow-share-access.component.html @@ -4,9 +4,9 @@