diff --git a/.gitignore b/.gitignore index 1de6590..301464f 100644 --- a/.gitignore +++ b/.gitignore @@ -27,8 +27,8 @@ replay_pid* # ---> JetBrains # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 - -# User-specific stuff +.idea/ +# User-specific stff .idea/**/workspace.xml .idea/**/tasks.xml .idea/**/usage.statistics.xml @@ -50,6 +50,7 @@ replay_pid* .idea/**/uiDesigner.xml .idea/**/dbnavigator.xml + # Gradle .idea/**/gradle.xml .idea/**/libraries diff --git a/backend/.idea/.gitignore b/backend/.idea/.gitignore deleted file mode 100644 index 73f69e0..0000000 --- a/backend/.idea/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml -# Editor-based HTTP Client requests -/httpRequests/ diff --git a/backend/.idea/dataSources/777acb96-8451-4627-b14b-d635cd5e928a.xml b/backend/.idea/dataSources/777acb96-8451-4627-b14b-d635cd5e928a.xml new file mode 100644 index 0000000..8b95204 --- /dev/null +++ b/backend/.idea/dataSources/777acb96-8451-4627-b14b-d635cd5e928a.xml @@ -0,0 +1,1658 @@ + + + + + exact + InnoDB + |root||root||ALTER|G +|root||root|localhost|ALTER|G +|root||root||ALTER ROUTINE|G +|root||root|localhost|ALTER ROUTINE|G +|root||root||BINLOG ADMIN|G +|root||root|localhost|BINLOG ADMIN|G +|root||root||BINLOG MONITOR|G +|root||root|localhost|BINLOG MONITOR|G +|root||root||BINLOG REPLAY|G +|root||root|localhost|BINLOG REPLAY|G +|root||root||CONNECTION ADMIN|G +|root||root|localhost|CONNECTION ADMIN|G +|root||root||CREATE|G +|root||root|localhost|CREATE|G +|root||root||CREATE ROUTINE|G +|root||root|localhost|CREATE ROUTINE|G +|root||root||CREATE TABLESPACE|G +|root||root|localhost|CREATE TABLESPACE|G +|root||root||CREATE TEMPORARY TABLES|G +|root||root|localhost|CREATE TEMPORARY TABLES|G +|root||root||CREATE USER|G +|root||root|localhost|CREATE USER|G +|root||root||CREATE VIEW|G +|root||root|localhost|CREATE VIEW|G +|root||root||DELETE|G +|root||root|localhost|DELETE|G +|root||root||DELETE HISTORY|G +|root||root|localhost|DELETE HISTORY|G +|root||root||DROP|G +|root||root|localhost|DROP|G +|root||root||EVENT|G +|root||root|localhost|EVENT|G +|root||root||EXECUTE|G +|root||root|localhost|EXECUTE|G +|root||root||FEDERATED ADMIN|G +|root||root|localhost|FEDERATED ADMIN|G +|root||root||FILE|G +|root||root|localhost|FILE|G +|root||root||INDEX|G +|root||root|localhost|INDEX|G +|root||root||INSERT|G +|root||root|localhost|INSERT|G +|root||root||LOCK TABLES|G +|root||root|localhost|LOCK TABLES|G +|root||root||PROCESS|G +|root||root|localhost|PROCESS|G +|root||root||READ_ONLY ADMIN|G +|root||root|localhost|READ_ONLY ADMIN|G +|root||root||REFERENCES|G +|root||root|localhost|REFERENCES|G +|root||root||RELOAD|G +|root||root|localhost|RELOAD|G +|root||root||REPLICATION MASTER ADMIN|G +|root||root|localhost|REPLICATION MASTER ADMIN|G +|root||root||REPLICATION SLAVE|G +|root||root|localhost|REPLICATION SLAVE|G +|root||root||REPLICATION SLAVE ADMIN|G +|root||root|localhost|REPLICATION SLAVE ADMIN|G +|root||root||SELECT|G +|root||root|localhost|SELECT|G +|root||root||SET USER|G +|root||root|localhost|SET USER|G +|root||root||SHOW DATABASES|G +|root||root|localhost|SHOW DATABASES|G +|root||root||SHOW VIEW|G +|root||root|localhost|SHOW VIEW|G +|root||root||SHUTDOWN|G +|root||root|localhost|SHUTDOWN|G +|root||root||SLAVE MONITOR|G +|root||root|localhost|SLAVE MONITOR|G +|root||root||SUPER|G +|root||root|localhost|SUPER|G +|root||root||TRIGGER|G +|root||root|localhost|TRIGGER|G +|root||root||UPDATE|G +|root||root|localhost|UPDATE|G +|root||root||grant option|G +|root||root|localhost|grant option|G + 10.9.3 + + + big5 + 1 + + + big5 + + + big5 + + + big5 + + + dec8 + 1 + + + dec8 + + + dec8 + + + dec8 + + + cp850 + 1 + + + cp850 + + + cp850 + + + cp850 + + + hp8 + 1 + + + hp8 + + + hp8 + + + hp8 + + + koi8r + 1 + + + koi8r + + + koi8r + + + koi8r + + + latin1 + + + latin1 + 1 + + + latin1 + + + latin1 + + + latin1 + + + latin1 + + + latin1 + + + latin1 + + + latin1 + + + latin1 + + + latin2 + + + latin2 + 1 + + + latin2 + + + latin2 + + + latin2 + + + latin2 + + + latin2 + + + swe7 + 1 + + + swe7 + + + swe7 + + + swe7 + + + ascii + 1 + + + ascii + + + ascii + + + ascii + + + ujis + 1 + + + ujis + + + ujis + + + ujis + + + sjis + 1 + + + sjis + + + sjis + + + sjis + + + hebrew + 1 + + + hebrew + + + hebrew + + + hebrew + + + tis620 + 1 + + + tis620 + + + tis620 + + + tis620 + + + euckr + 1 + + + euckr + + + euckr + + + euckr + + + koi8u + 1 + + + koi8u + + + koi8u + + + koi8u + + + gb2312 + 1 + + + gb2312 + + + gb2312 + + + gb2312 + + + greek + 1 + + + greek + + + greek + + + greek + + + cp1250 + 1 + + + cp1250 + + + cp1250 + + + cp1250 + + + cp1250 + + + cp1250 + + + cp1250 + + + gbk + 1 + + + gbk + + + gbk + + + gbk + + + latin5 + 1 + + + latin5 + + + latin5 + + + latin5 + + + armscii8 + 1 + + + armscii8 + + + armscii8 + + + armscii8 + + + utf8mb3 + 1 + + + utf8mb3 + + + utf8mb3 + + + utf8mb3 + + + utf8mb3 + + + utf8mb3 + + + utf8mb3 + + + utf8mb3 + + + utf8mb3 + + + utf8mb3 + + + utf8mb3 + + + utf8mb3 + + + utf8mb3 + + + utf8mb3 + + + utf8mb3 + + + utf8mb3 + + + utf8mb3 + + + utf8mb3 + + + utf8mb3 + + + utf8mb3 + + + utf8mb3 + + + utf8mb3 + + + utf8mb3 + + + utf8mb3 + + + utf8mb3 + + + utf8mb3 + + + utf8mb3 + + + utf8mb3 + + + utf8mb3 + + + utf8mb3 + + + utf8mb3 + + + utf8mb3 + + + utf8mb3 + + + utf8mb3 + + + ucs2 + 1 + + + ucs2 + + + ucs2 + + + ucs2 + + + ucs2 + + + ucs2 + + + ucs2 + + + ucs2 + + + ucs2 + + + ucs2 + + + ucs2 + + + ucs2 + + + ucs2 + + + ucs2 + + + ucs2 + + + ucs2 + + + ucs2 + + + ucs2 + + + ucs2 + + + ucs2 + + + ucs2 + + + ucs2 + + + ucs2 + + + ucs2 + + + ucs2 + + + ucs2 + + + ucs2 + + + ucs2 + + + ucs2 + + + ucs2 + + + ucs2 + + + ucs2 + + + ucs2 + + + ucs2 + + + cp866 + 1 + + + cp866 + + + cp866 + + + cp866 + + + keybcs2 + 1 + + + keybcs2 + + + keybcs2 + + + keybcs2 + + + macce + 1 + + + macce + + + macce + + + macce + + + macroman + 1 + + + macroman + + + macroman + + + macroman + + + cp852 + 1 + + + cp852 + + + cp852 + + + cp852 + + + latin7 + + + latin7 + 1 + + + latin7 + + + latin7 + + + latin7 + + + latin7 + + + utf8mb4 + 1 + + + utf8mb4 + + + utf8mb4 + + + utf8mb4 + + + utf8mb4 + + + utf8mb4 + + + utf8mb4 + + + utf8mb4 + + + utf8mb4 + + + utf8mb4 + + + utf8mb4 + + + utf8mb4 + + + utf8mb4 + + + utf8mb4 + + + utf8mb4 + + + utf8mb4 + + + utf8mb4 + + + utf8mb4 + + + utf8mb4 + + + utf8mb4 + + + utf8mb4 + + + utf8mb4 + + + utf8mb4 + + + utf8mb4 + + + utf8mb4 + + + utf8mb4 + + + utf8mb4 + + + utf8mb4 + + + utf8mb4 + + + utf8mb4 + + + utf8mb4 + + + utf8mb4 + + + utf8mb4 + + + cp1251 + + + cp1251 + + + cp1251 + + + cp1251 + 1 + + + cp1251 + + + cp1251 + + + cp1251 + + + utf16 + 1 + + + utf16 + + + utf16 + + + utf16 + + + utf16 + + + utf16 + + + utf16 + + + utf16 + + + utf16 + + + utf16 + + + utf16 + + + utf16 + + + utf16 + + + utf16 + + + utf16 + + + utf16 + + + utf16 + + + utf16 + + + utf16 + + + utf16 + + + utf16 + + + utf16 + + + utf16 + + + utf16 + + + utf16 + + + utf16 + + + utf16 + + + utf16 + + + utf16 + + + utf16 + + + utf16 + + + utf16 + + + utf16 + + + utf16le + 1 + + + utf16le + + + utf16le + + + utf16le + + + cp1256 + 1 + + + cp1256 + + + cp1256 + + + cp1256 + + + cp1257 + + + cp1257 + + + cp1257 + 1 + + + cp1257 + + + cp1257 + + + utf32 + 1 + + + utf32 + + + utf32 + + + utf32 + + + utf32 + + + utf32 + + + utf32 + + + utf32 + + + utf32 + + + utf32 + + + utf32 + + + utf32 + + + utf32 + + + utf32 + + + utf32 + + + utf32 + + + utf32 + + + utf32 + + + utf32 + + + utf32 + + + utf32 + + + utf32 + + + utf32 + + + utf32 + + + utf32 + + + utf32 + + + utf32 + + + utf32 + + + utf32 + + + utf32 + + + utf32 + + + utf32 + + + utf32 + + + binary + 1 + + + geostd8 + 1 + + + geostd8 + + + geostd8 + + + geostd8 + + + cp932 + 1 + + + cp932 + + + cp932 + + + cp932 + + + eucjpms + 1 + + + eucjpms + + + eucjpms + + + eucjpms + + + utf8mb3_general_ci + + + utf8mb3_general_ci + + + utf8mb4_general_ci + + + utf8mb3_general_ci + + + 1 + 2023-09-25.15:13:38 + 2023-09-25.13:13:38 + utf8mb4_general_ci + + + localhost + + + localhost + + + + InnoDB + utf8mb4_general_ci +
+ + InnoDB + utf8mb4_general_ci +
+ + InnoDB + utf8mb4_general_ci +
+ + InnoDB + utf8mb4_general_ci +
+ + InnoDB + utf8mb4_general_ci +
+ + InnoDB + utf8mb4_general_ci +
+ + InnoDB + utf8mb4_general_ci +
+ + InnoDB + utf8mb4_general_ci +
+ + InnoDB + utf8mb4_general_ci +
+ + InnoDB + utf8mb4_general_ci +
+ + InnoDB + utf8mb4_general_ci +
+ + InnoDB + utf8mb4_general_ci +
+ + InnoDB + utf8mb4_general_ci +
+ + 1 + bigint(20)|0s + 1 + 1 + + + varchar(32)|0s + 1 + 2 + + + bigint(20)|0s + 3 + + + project + projectid + projects + + + branchid + btree + 1 + + + project + btree + + + 1 + 1 + PRIMARY + + + varchar(255)|0s + 1 + 1 + + + varchar(255)|0s + 2 + + + datetime(6)|0s + 3 + + + bigint(20)|0s + 4 + + + branch + branchid + branches + + + commitid + btree + 1 + + + branch + btree + + + 1 + 1 + PRIMARY + + + bigint(21)|0s + 1 + 1 + + + bigint(21)|0s + 1 + 2 + + + bigint(21)|0s + 1 + 3 + + + start value when sequences is created or value if RESTART is used + bigint(21)|0s + 1 + 4 + + + increment value + bigint(21)|0s + 1 + 5 + + + bigint(21) unsigned|0s + 1 + 6 + + + 0 if no cycles are allowed, 1 if the sequence should begin a new cycle when maximum_value is passed + tinyint(1) unsigned|0s + 1 + 7 + + + How many cycles have been done + bigint(21)|0s + 1 + 8 + + + 1 + bigint(20)|0s + 1 + 1 + + + bit(1)|0s + 1 + 2 + + + longtext|0s + 3 + + + datetime(6)|0s + 1 + 4 + + + varchar(255)|0s + 1 + 5 + + + bigint(20)|0s + 1 + 6 + + + project + projectid + projects + + + issueid + btree + 1 + + + project + btree + + + 1 + 1 + PRIMARY + + + 1 + bigint(20)|0s + 1 + 1 + + + varchar(255)|0s + 1 + 2 + + + longtext|0s + 1 + 3 + + + varchar(32)|0s + 1 + 4 + + + bigint(20)|0s + 5 + + + project_owner + id + userprojects + + + projectid + btree + 1 + + + project_owner + btree + + + 1 + 1 + PRIMARY + + + 2 + bigint(20)|0s + 1 + 1 + + + varchar(255)|0s + 2 + + + bit(1)|0s + 1 + 3 + + + id + btree + 1 + + + name + btree + 1 + + + 1 + 1 + PRIMARY + + + UKp5y7iih608g0xwvro8nq20o6o + + + 3 + int(11)|0s + 1 + 1 + + + varchar(20)|0s + 2 + + + id + btree + 1 + + + 1 + 1 + PRIMARY + + + bigint(20)|0s + 1 + 1 + + + varchar(255)|0s + 2 + + + bigint(20)|0s + 1 + 3 + + + taskgroupuser + id + users + + + taskgroupid + btree + 1 + + + taskgroupuser + btree + + + 1 + 1 + PRIMARY + + + bigint(20)|0s + 1 + 1 + + + int(11)|0s + 1 + 2 + + + user_id + id + users + + + role_id + id + roles + + + user_id +role_id + btree + 1 + + + role_id + btree + + + 1 + 1 + PRIMARY + + + 1 + bigint(20)|0s + 1 + 1 + + + bigint(20)|0s + 2 + + + user_owner_ship + id + users + + + id + btree + 1 + + + user_owner_ship + btree + + + 1 + 1 + PRIMARY + + + 1 + bigint(20)|0s + 1 + 1 + + + varchar(50)|0s + 2 + + + varchar(120)|0s + 3 + + + varchar(20)|0s + 4 + + + id + btree + 1 + + + email + btree + 1 + + + username + btree + 1 + + + 1 + 1 + PRIMARY + + + UK6dotkott2kjsp8vw4d0m25fb7 + + + UKr43af9ap4edm43mmtq01oddj6 + + + 1 + bigint(20)|0s + 1 + 1 + + + int(11)|0s + 1 + 2 + + + longtext|0s + 3 + + + varchar(255)|0s + 4 + + + varchar(255)|0s + 1 + 5 + + + bigint(20)|0s + 1 + 6 + + + commit + commitid + commits + + + wikipage + wikipageid + wikipages + + + id + btree + 1 + + + commit + btree + + + wikipage + btree + + + 1 + 1 + PRIMARY + + + 1 + bigint(20)|0s + 1 + 1 + + + bigint(20)|0s + 2 + + + project + projectid + projects + + + wikipageid + btree + 1 + + + project + btree + + + 1 + 1 + PRIMARY + +
+
\ No newline at end of file diff --git a/backend/.idea/dataSources/777acb96-8451-4627-b14b-d635cd5e928a/storage_v2/_src_/schema/information_schema.FNRwLQ.meta b/backend/.idea/dataSources/777acb96-8451-4627-b14b-d635cd5e928a/storage_v2/_src_/schema/information_schema.FNRwLQ.meta new file mode 100644 index 0000000..1ff3db2 --- /dev/null +++ b/backend/.idea/dataSources/777acb96-8451-4627-b14b-d635cd5e928a/storage_v2/_src_/schema/information_schema.FNRwLQ.meta @@ -0,0 +1,2 @@ +#n:information_schema +! [null, 0, null, null, -2147483648, -2147483648] diff --git a/backend/.idea/dataSources/777acb96-8451-4627-b14b-d635cd5e928a/storage_v2/_src_/schema/mysql.osA4Bg.meta b/backend/.idea/dataSources/777acb96-8451-4627-b14b-d635cd5e928a/storage_v2/_src_/schema/mysql.osA4Bg.meta new file mode 100644 index 0000000..86a53f1 --- /dev/null +++ b/backend/.idea/dataSources/777acb96-8451-4627-b14b-d635cd5e928a/storage_v2/_src_/schema/mysql.osA4Bg.meta @@ -0,0 +1,2 @@ +#n:mysql +! [null, 0, null, null, -2147483648, -2147483648] diff --git a/backend/.idea/dataSources/777acb96-8451-4627-b14b-d635cd5e928a/storage_v2/_src_/schema/performance_schema.kIw0nw.meta b/backend/.idea/dataSources/777acb96-8451-4627-b14b-d635cd5e928a/storage_v2/_src_/schema/performance_schema.kIw0nw.meta new file mode 100644 index 0000000..9394db1 --- /dev/null +++ b/backend/.idea/dataSources/777acb96-8451-4627-b14b-d635cd5e928a/storage_v2/_src_/schema/performance_schema.kIw0nw.meta @@ -0,0 +1,2 @@ +#n:performance_schema +! [null, 0, null, null, -2147483648, -2147483648] diff --git a/backend/.idea/dataSources/777acb96-8451-4627-b14b-d635cd5e928a/storage_v2/_src_/schema/restservice.YTTb0Q.meta b/backend/.idea/dataSources/777acb96-8451-4627-b14b-d635cd5e928a/storage_v2/_src_/schema/restservice.YTTb0Q.meta new file mode 100644 index 0000000..e0a0128 --- /dev/null +++ b/backend/.idea/dataSources/777acb96-8451-4627-b14b-d635cd5e928a/storage_v2/_src_/schema/restservice.YTTb0Q.meta @@ -0,0 +1,2 @@ +#n:restservice +! [1695647618000, 0, null, null, -2147483648, -2147483648] diff --git a/backend/.idea/misc.xml b/backend/.idea/misc.xml index 42e3abe..67e1e61 100644 --- a/backend/.idea/misc.xml +++ b/backend/.idea/misc.xml @@ -1,3 +1,4 @@ + diff --git a/backend/.idea/uiDesigner.xml b/backend/.idea/uiDesigner.xml deleted file mode 100644 index e96534f..0000000 --- a/backend/.idea/uiDesigner.xml +++ /dev/null @@ -1,124 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/backend/.idea/workspace.xml b/backend/.idea/workspace.xml new file mode 100644 index 0000000..fe49c14 --- /dev/null +++ b/backend/.idea/workspace.xml @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + { + "customColor": "", + "associatedIndex": 6 +} + + + + { + "keyToString": { + "RequestMappingsPanelOrder0": "0", + "RequestMappingsPanelOrder1": "1", + "RequestMappingsPanelWidth0": "75", + "RequestMappingsPanelWidth1": "75", + "RunOnceActivity.OpenProjectViewOnStart": "true", + "RunOnceActivity.ShowReadmeOnStart": "true", + "WebServerToolWindowFactoryState": "false", + "git-widget-placeholder": "issue-7", + "node.js.detected.package.eslint": "true", + "node.js.detected.package.tslint": "true", + "node.js.selected.package.eslint": "(autodetect)", + "node.js.selected.package.tslint": "(autodetect)", + "vue.rearranger.settings.migration": "true" + }, + "keyToStringList": { + "DatabaseDriversLRU": [ + "mariadb" + ] + } +} + + + + + + + + + + 1695647243767 + + + + + + + + + + + + + + file://$PROJECT_DIR$/src/main/java/core/services/TaskgroupService.java + 52 + + + + + \ No newline at end of file diff --git a/backend/.mvn/wrapper/maven-wrapper.properties b/backend/.mvn/wrapper/maven-wrapper.properties deleted file mode 100644 index b7cb93e..0000000 --- a/backend/.mvn/wrapper/maven-wrapper.properties +++ /dev/null @@ -1,2 +0,0 @@ -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.4/apache-maven-3.8.4-bin.zip -wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar diff --git a/backend/src/main/java/core/api/controller/TaskgroupController.java b/backend/src/main/java/core/api/controller/TaskgroupController.java index d6fedf4..2b7e818 100644 --- a/backend/src/main/java/core/api/controller/TaskgroupController.java +++ b/backend/src/main/java/core/api/controller/TaskgroupController.java @@ -10,11 +10,13 @@ import core.services.ServiceResult; import core.services.TaskgroupService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; +import org.springframework.scheduling.config.Task; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.bind.annotation.*; import javax.validation.Valid; import java.util.List; +import java.util.Set; @CrossOrigin(origins = "*", maxAge = 3600) @RestController @@ -74,8 +76,23 @@ public class TaskgroupController { @GetMapping("/taskgroups") public ResponseEntity> listTaskgroupsOfUser() { - List taskgroups = taskgroupService.getTaskgroupsByUser(SecurityContextHolder.getContext().getAuthentication().getName()); + List taskgroups = taskgroupService.getTopTaskgroupsByUser(SecurityContextHolder.getContext().getAuthentication().getName()); List taskgroupEntityInfos = taskgroups.stream().map(TaskgroupEntityInfo::new).toList(); return ResponseEntity.ok(taskgroupEntityInfos); } + + @GetMapping("/taskgroups/{taskgroupID}") + public ResponseEntity getDetails(@PathVariable long taskgroupID) { + PermissionResult taskgroupPermissionResult = taskgroupService.getTaskgroupByIDAndUsername(taskgroupID, SecurityContextHolder.getContext().getAuthentication().getName()); + if(!taskgroupPermissionResult.isHasPermissions()) { + return ResponseEntity.status(403).body(new SimpleStatusResponse("failed")); + } + + if(taskgroupPermissionResult.getExitCode() == ServiceExitCode.MISSING_ENTITY) { + return ResponseEntity.status(404).body(new SimpleStatusResponse("failed")); + } + + Set children = taskgroupPermissionResult.getResult().getChildren(); + return ResponseEntity.ok(children.stream().map(TaskgroupEntityInfo::new)); + } } diff --git a/backend/src/main/java/core/api/models/timemanager/taskgroup/TaskgroupEntityInfo.java b/backend/src/main/java/core/api/models/timemanager/taskgroup/TaskgroupEntityInfo.java index 99fd360..f8027ef 100644 --- a/backend/src/main/java/core/api/models/timemanager/taskgroup/TaskgroupEntityInfo.java +++ b/backend/src/main/java/core/api/models/timemanager/taskgroup/TaskgroupEntityInfo.java @@ -7,9 +7,17 @@ public class TaskgroupEntityInfo { private long taskgroupID; private String taskgroupName; + private TaskgroupEntityInfo parentTaskgroup; + public TaskgroupEntityInfo(Taskgroup taskgroup) { this.taskgroupID = taskgroup.getTaskgroupID(); this.taskgroupName = taskgroup.getTaskgroupName(); + + if(taskgroup.getParent() == null) { + this.parentTaskgroup = null; + } else { + this.parentTaskgroup = new TaskgroupEntityInfo(taskgroup.getParent()); + } } public long getTaskgroupID() { @@ -27,4 +35,12 @@ public class TaskgroupEntityInfo { public void setTaskgroupName(String taskgroupName) { this.taskgroupName = taskgroupName; } + + public TaskgroupEntityInfo getParentTaskgroup() { + return parentTaskgroup; + } + + public void setParentTaskgroup(TaskgroupEntityInfo parentTaskgroup) { + this.parentTaskgroup = parentTaskgroup; + } } diff --git a/backend/src/main/java/core/api/models/timemanager/taskgroup/TaskgroupFieldInfo.java b/backend/src/main/java/core/api/models/timemanager/taskgroup/TaskgroupFieldInfo.java index 0c3116d..ca6dcdf 100644 --- a/backend/src/main/java/core/api/models/timemanager/taskgroup/TaskgroupFieldInfo.java +++ b/backend/src/main/java/core/api/models/timemanager/taskgroup/TaskgroupFieldInfo.java @@ -13,6 +13,8 @@ public class TaskgroupFieldInfo { @Length(max = 255) private String name; + private long parentID; + public String getName() { return name; } @@ -20,4 +22,12 @@ public class TaskgroupFieldInfo { public void setName(String name) { this.name = name; } + + public long getParentID() { + return parentID; + } + + public void setParentID(long parentID) { + this.parentID = parentID; + } } diff --git a/backend/src/main/java/core/entities/timemanager/Taskgroup.java b/backend/src/main/java/core/entities/timemanager/Taskgroup.java index 4043f4d..aca1666 100644 --- a/backend/src/main/java/core/entities/timemanager/Taskgroup.java +++ b/backend/src/main/java/core/entities/timemanager/Taskgroup.java @@ -4,6 +4,7 @@ import core.entities.User; import javax.persistence.*; import javax.validation.constraints.NotBlank; +import java.util.Set; @Entity @Table(name= "taskgroups") @@ -21,6 +22,13 @@ public class Taskgroup { @JoinColumn(name = "taskgroupuser", nullable = false) private User user; + @OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, fetch = FetchType.EAGER) + private Set children; + + @ManyToOne + @JoinColumn(name = "parent_id") + private Taskgroup parent; + public Taskgroup(String taskgroupName, User user) { this.taskgroupName = taskgroupName; this.user = user; @@ -52,4 +60,20 @@ public class Taskgroup { public void setUser(User user) { this.user = user; } + + public Set getChildren() { + return children; + } + + public void setChildren(Set children) { + this.children = children; + } + + public Taskgroup getParent() { + return parent; + } + + public void setParent(Taskgroup parent) { + this.parent = parent; + } } diff --git a/backend/src/main/java/core/repositories/timemanager/TaskgroupRepository.java b/backend/src/main/java/core/repositories/timemanager/TaskgroupRepository.java index 4c17014..b2699ac 100644 --- a/backend/src/main/java/core/repositories/timemanager/TaskgroupRepository.java +++ b/backend/src/main/java/core/repositories/timemanager/TaskgroupRepository.java @@ -18,6 +18,9 @@ public interface TaskgroupRepository extends CrudRepository { @Query("SELECT tg FROM Taskgroup tg WHERE tg.user.username = ?1") List findAllByUser(String username); + @Query("SELECT tg FROM Taskgroup tg WHERE tg.user.username = ?1 AND tg.parent IS NULL") + List findAllTopTaskgroupsByUser(String username); + @Modifying @Transactional void deleteAllByUser(User user); diff --git a/backend/src/main/java/core/services/TaskgroupService.java b/backend/src/main/java/core/services/TaskgroupService.java index d5031c8..63479db 100644 --- a/backend/src/main/java/core/services/TaskgroupService.java +++ b/backend/src/main/java/core/services/TaskgroupService.java @@ -40,8 +40,20 @@ public class TaskgroupService { } if(!taskgroupRepository.existsByTaskgroupNameAndUser(taskData.getName(), user.get())) { + Taskgroup taskgroup; + if(taskData.getParentID() < 0) { + taskgroup = new Taskgroup(taskData.getName(), user.get()); + } else { + Optional parentTaskgroup = taskgroupRepository.findById(taskData.getParentID()); + if(parentTaskgroup.isEmpty()) { + return new ServiceResult<>(ServiceExitCode.MISSING_ENTITY); + } else { + taskgroup = new Taskgroup(taskData.getName(), user.get()); + taskgroup.setParent(parentTaskgroup.get()); + parentTaskgroup.get().getChildren().add(taskgroup); + } + } - Taskgroup taskgroup = new Taskgroup(taskData.getName(), user.get()); taskgroupRepository.save(taskgroup); return new ServiceResult<>(taskgroup); } else { @@ -72,6 +84,10 @@ public class TaskgroupService { return taskgroupRepository.findAllByUser(username); } + public List getTopTaskgroupsByUser(String username) { + return taskgroupRepository.findAllTopTaskgroupsByUser(username); + } + public void deleteTaskgroupByUser(User user) { taskgroupRepository.deleteAllByUser(user); } diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index 114ad5f..259a355 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -1,4 +1,4 @@ -spring.datasource.url=jdbc:mariadb://127.0.0.1:3306/restservice?createDatabaseIfNotExist=true&autoReconnect=true +spring.datasource.url=jdbc:mariadb://127.0.0.1:3306/timemanager?createDatabaseIfNotExist=true&autoReconnect=true spring.datasource.username=root spring.datasource.password=edkvcjReDxJ9Z8hq spring.datasource.driver-class-name=org.mariadb.jdbc.Driver diff --git a/frontend/src/api/api/taskgroup.service.ts b/frontend/src/api/api/taskgroup.service.ts index 2d00598..2cd182c 100644 --- a/frontend/src/api/api/taskgroup.service.ts +++ b/frontend/src/api/api/taskgroup.service.ts @@ -94,6 +94,61 @@ export class TaskgroupService { * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. * @param reportProgress flag to report request and response progress. */ + public taskgroupsAllGet(observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable>; + public taskgroupsAllGet(observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable>>; + public taskgroupsAllGet(observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable>>; + public taskgroupsAllGet(observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable { + + let localVarHeaders = this.defaultHeaders; + + let localVarCredential: string | undefined; + // authentication (API_TOKEN) required + localVarCredential = this.configuration.lookupCredential('API_TOKEN'); + if (localVarCredential) { + localVarHeaders = localVarHeaders.set('Authorization', 'Bearer ' + localVarCredential); + } + + let localVarHttpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept; + if (localVarHttpHeaderAcceptSelected === undefined) { + // to determine the Accept header + const httpHeaderAccepts: string[] = [ + 'application/json' + ]; + localVarHttpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts); + } + if (localVarHttpHeaderAcceptSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected); + } + + let localVarHttpContext: HttpContext | undefined = options && options.context; + if (localVarHttpContext === undefined) { + localVarHttpContext = new HttpContext(); + } + + + let responseType_: 'text' | 'json' = 'json'; + if(localVarHttpHeaderAcceptSelected && localVarHttpHeaderAcceptSelected.startsWith('text')) { + responseType_ = 'text'; + } + + return this.httpClient.get>(`${this.configuration.basePath}/taskgroups/all`, + { + context: localVarHttpContext, + responseType: responseType_, + withCredentials: this.configuration.withCredentials, + headers: localVarHeaders, + observe: observe, + reportProgress: reportProgress + } + ); + } + + /** + * list all top level taskgroups of authorized user + * list all taskgroups of authorized user + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ public taskgroupsGet(observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable>; public taskgroupsGet(observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable>>; public taskgroupsGet(observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable>>; @@ -268,6 +323,65 @@ export class TaskgroupService { ); } + /** + * get details of an existing taskgroup + * get details of an existing taskgroup + * @param taskgroupID internal id of taskgroup + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public taskgroupsTaskgroupIDGet(taskgroupID: number, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable>; + public taskgroupsTaskgroupIDGet(taskgroupID: number, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable>>; + public taskgroupsTaskgroupIDGet(taskgroupID: number, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable>>; + public taskgroupsTaskgroupIDGet(taskgroupID: number, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable { + if (taskgroupID === null || taskgroupID === undefined) { + throw new Error('Required parameter taskgroupID was null or undefined when calling taskgroupsTaskgroupIDGet.'); + } + + let localVarHeaders = this.defaultHeaders; + + let localVarCredential: string | undefined; + // authentication (API_TOKEN) required + localVarCredential = this.configuration.lookupCredential('API_TOKEN'); + if (localVarCredential) { + localVarHeaders = localVarHeaders.set('Authorization', 'Bearer ' + localVarCredential); + } + + let localVarHttpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept; + if (localVarHttpHeaderAcceptSelected === undefined) { + // to determine the Accept header + const httpHeaderAccepts: string[] = [ + 'application/json' + ]; + localVarHttpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts); + } + if (localVarHttpHeaderAcceptSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected); + } + + let localVarHttpContext: HttpContext | undefined = options && options.context; + if (localVarHttpContext === undefined) { + localVarHttpContext = new HttpContext(); + } + + + let responseType_: 'text' | 'json' = 'json'; + if(localVarHttpHeaderAcceptSelected && localVarHttpHeaderAcceptSelected.startsWith('text')) { + responseType_ = 'text'; + } + + return this.httpClient.get>(`${this.configuration.basePath}/taskgroups/${encodeURIComponent(String(taskgroupID))}`, + { + context: localVarHttpContext, + responseType: responseType_, + withCredentials: this.configuration.withCredentials, + headers: localVarHeaders, + observe: observe, + reportProgress: reportProgress + } + ); + } + /** * edits taskgroup * edits taskgroup diff --git a/frontend/src/api/model/taskgroupEntityInfo.ts b/frontend/src/api/model/taskgroupEntityInfo.ts index bbec6ed..56c6186 100644 --- a/frontend/src/api/model/taskgroupEntityInfo.ts +++ b/frontend/src/api/model/taskgroupEntityInfo.ts @@ -20,5 +20,6 @@ export interface TaskgroupEntityInfo { * name of taskgroup */ taskgroupName: string; + parentTaskgroup: TaskgroupEntityInfo; } diff --git a/frontend/src/api/model/taskgroupFieldInfo.ts b/frontend/src/api/model/taskgroupFieldInfo.ts index 66c2b15..11ca1cb 100644 --- a/frontend/src/api/model/taskgroupFieldInfo.ts +++ b/frontend/src/api/model/taskgroupFieldInfo.ts @@ -16,5 +16,9 @@ export interface TaskgroupFieldInfo { * name of taskgroup */ name: string; + /** + * internal id of parent Taskgroup + */ + parentID: number; } diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts index 47103b6..9777643 100644 --- a/frontend/src/app/app-routing.module.ts +++ b/frontend/src/app/app-routing.module.ts @@ -10,7 +10,8 @@ const routes: Routes = [ {path: '', component: MainComponent}, {path: 'admin', component: AdminDashboardComponent}, {path: 'user/settings', component: UserSettingsComponent}, - {path: 'taskgroups', component: TaskgroupDashboardComponent} + {path: 'taskgroups', component: TaskgroupDashboardComponent}, + {path: 'taskgroups/:taskgroupID', component: TaskgroupDashboardComponent} ]; @NgModule({ diff --git a/frontend/src/app/taskgroups/taskgroup-creation/TaskgroupCreationData.ts b/frontend/src/app/taskgroups/taskgroup-creation/TaskgroupCreationData.ts new file mode 100644 index 0000000..19a1942 --- /dev/null +++ b/frontend/src/app/taskgroups/taskgroup-creation/TaskgroupCreationData.ts @@ -0,0 +1,6 @@ +import {TaskgroupEntityInfo} from "../../../api"; + +export interface TaskgroupCreationData { + taskgroup: TaskgroupEntityInfo | undefined, + taskgroupID: number +} diff --git a/frontend/src/app/taskgroups/taskgroup-creation/taskgroup-creation.component.ts b/frontend/src/app/taskgroups/taskgroup-creation/taskgroup-creation.component.ts index 8b299b7..595f7a6 100644 --- a/frontend/src/app/taskgroups/taskgroup-creation/taskgroup-creation.component.ts +++ b/frontend/src/app/taskgroups/taskgroup-creation/taskgroup-creation.component.ts @@ -4,6 +4,8 @@ import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog"; import {TaskgroupEntityInfo, TaskgroupService} from "../../../api"; import {error} from "@angular/compiler/src/util"; import {MatSnackBar} from "@angular/material/snack-bar"; +import {ActivatedRoute} from "@angular/router"; +import {TaskgroupCreationData} from "./TaskgroupCreationData"; @Component({ selector: 'app-taskgroup-creation', @@ -18,11 +20,11 @@ export class TaskgroupCreationComponent implements OnInit { constructor(private dialogRef: MatDialogRef, private taskgroupService: TaskgroupService, private snackbar: MatSnackBar, - @Inject(MAT_DIALOG_DATA) public data: TaskgroupEntityInfo | undefined) { } + @Inject(MAT_DIALOG_DATA) public data: TaskgroupCreationData) { } ngOnInit(): void { - if(this.data != undefined) { - this.nameCtrl.setValue(this.data!.taskgroupName) + if(this.data.taskgroup != undefined) { + this.nameCtrl.setValue(this.data.taskgroup!.taskgroupName) } } @@ -32,9 +34,10 @@ export class TaskgroupCreationComponent implements OnInit { save() { this.pending = true; - if(this.data == undefined) { + if(this.data.taskgroup == undefined) { this.taskgroupService.taskgroupsPut({ - name: this.nameCtrl.value + name: this.nameCtrl.value, + parentID: this.data.taskgroupID, }).subscribe({ next: resp => { this.pending = false; @@ -50,12 +53,13 @@ export class TaskgroupCreationComponent implements OnInit { } }) } else { - this.taskgroupService.taskgroupsTaskgroupIDPost(this.data.taskgroupID, { - name: this.nameCtrl.value + this.taskgroupService.taskgroupsTaskgroupIDPost(this.data.taskgroup!.taskgroupID, { + name: this.nameCtrl.value, + parentID: this.data.taskgroupID }).subscribe({ next: resp => { this.pending = false; - this.data!.taskgroupName = this.nameCtrl.value + this.data.taskgroup!.taskgroupName = this.nameCtrl.value this.dialogRef.close(true); }, error: err => { diff --git a/frontend/src/app/taskgroups/taskgroup-dashboard/taskgroup-dashboard.component.html b/frontend/src/app/taskgroups/taskgroup-dashboard/taskgroup-dashboard.component.html index 747ee96..7fd2200 100644 --- a/frontend/src/app/taskgroups/taskgroup-dashboard/taskgroup-dashboard.component.html +++ b/frontend/src/app/taskgroups/taskgroup-dashboard/taskgroup-dashboard.component.html @@ -9,7 +9,7 @@

{{taskgroup.taskgroupName}}

- +
diff --git a/frontend/src/app/taskgroups/taskgroup-dashboard/taskgroup-dashboard.component.ts b/frontend/src/app/taskgroups/taskgroup-dashboard/taskgroup-dashboard.component.ts index fd65051..cfbaa87 100644 --- a/frontend/src/app/taskgroups/taskgroup-dashboard/taskgroup-dashboard.component.ts +++ b/frontend/src/app/taskgroups/taskgroup-dashboard/taskgroup-dashboard.component.ts @@ -3,6 +3,7 @@ import {MatDialog} from "@angular/material/dialog"; import {TaskgroupCreationComponent} from "../taskgroup-creation/taskgroup-creation.component"; import {TaskgroupEntityInfo, TaskgroupService} from "../../../api"; import {TaskgroupDeletionComponent} from "../taskgroup-deletion/taskgroup-deletion.component"; +import {ActivatedRoute} from "@angular/router"; @Component({ selector: 'app-taskgroup-dashboard', @@ -12,20 +13,34 @@ import {TaskgroupDeletionComponent} from "../taskgroup-deletion/taskgroup-deleti export class TaskgroupDashboardComponent implements OnInit { constructor(private dialog: MatDialog, - private taskgroupService: TaskgroupService) { } + private taskgroupService: TaskgroupService, + private activatedRoute: ActivatedRoute) { } taskgroups: TaskgroupEntityInfo[] = [] + taskgroupID: number = -1; ngOnInit(): void { - this.taskgroupService.taskgroupsGet().subscribe({ - next: resp => { - this.taskgroups = resp; + this.activatedRoute.paramMap.subscribe(params => { + if(params.has('taskgroupID')) { + this.taskgroupID = Number(params.get('taskgroupID')); + this.taskgroupService.taskgroupsTaskgroupIDGet(this.taskgroupID).subscribe({ + next: resp => { + this.taskgroups = resp + } + }) + } else { + this.taskgroupService.taskgroupsGet().subscribe({ + next: resp => { + this.taskgroups = resp; + } + }) } }) + } openTaskgroupCreation() { - const dialogRef = this.dialog.open(TaskgroupCreationComponent, {minWidth: "400px"}) + const dialogRef = this.dialog.open(TaskgroupCreationComponent, {data: {taskgroup: undefined, taskgroupID: this.taskgroupID}, minWidth: "400px"}) dialogRef.afterClosed().subscribe(res => { if(res != undefined) { this.taskgroups.push(res); @@ -34,7 +49,7 @@ export class TaskgroupDashboardComponent implements OnInit { } openTaskgroupEditor(taskgroup: TaskgroupEntityInfo) { - const dialogRef = this.dialog.open(TaskgroupCreationComponent, {data: taskgroup, minWidth: "400px"}); + const dialogRef = this.dialog.open(TaskgroupCreationComponent, {data: {taskgroup: taskgroup, taskgroupID: this.taskgroupID}, minWidth: "400px"}); dialogRef.afterClosed().subscribe(res => { if(res) { const data = this.taskgroups diff --git a/openapi.yaml b/openapi.yaml index 67f719f..af8b5b3 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -564,7 +564,7 @@ paths: example: "failed" enum: - "failed" - /taskgroups: + /taskgroups/all: get: security: - API_TOKEN: [] @@ -581,6 +581,23 @@ paths: type: array items: $ref: '#/components/schemas/TaskgroupEntityInfo' + /taskgroups: + get: + security: + - API_TOKEN: [] + tags: + - taskgroup + summary: list all top level taskgroups of authorized user + description: list all taskgroups of authorized user + responses: + 200: + description: Anfrage erfolgreich + content: + 'application/json': + schema: + type: array + items: + $ref: '#/components/schemas/TaskgroupEntityInfo' put: security: - API_TOKEN: [] @@ -616,6 +633,60 @@ paths: enum: - "failed" /taskgroups/{taskgroupID}: + get: + security: + - API_TOKEN: [] + tags: + - taskgroup + summary: get details of an existing taskgroup + description: get details of an existing taskgroup + parameters: + - name: taskgroupID + in: path + description: internal id of taskgroup + required: true + schema: + type: number + example: 1 + responses: + 200: + description: Anfrage erfolgreich + content: + 'application/json': + schema: + type: array + items: + $ref: '#/components/schemas/TaskgroupEntityInfo' + 403: + description: No permission + content: + 'application/json': + schema: + type: object + required: + - status + properties: + status: + type: string + description: Status + example: "failed" + enum: + - "failed" + 404: + description: Taskgroup does not exist + content: + 'application/json': + schema: + type: object + required: + - status + properties: + status: + type: string + description: Status + example: "failed" + enum: + - "failed" post: security: - API_TOKEN: [] @@ -948,6 +1019,7 @@ components: required: - taskgroupID - taskgroupName + - parentTaskgroup additionalProperties: false properties: taskgroupID: @@ -960,9 +1032,13 @@ components: example: Taskgroup 1 maxLength: 255 minLength: 1 + parentTaskgroup: + type: object + $ref: '#/components/schemas/TaskgroupEntityInfo' TaskgroupFieldInfo: required: - name + - parentID additionalProperties: false properties: name: @@ -970,4 +1046,9 @@ components: description: name of taskgroup example: Taskgroup 1 maxLength: 255 - minLength: 1 \ No newline at end of file + minLength: 1 + parentID: + type: number + description: internal id of parent Taskgroup + example: 1 + \ No newline at end of file