1.1 --- a/src/main/kotlin/de/uapcore/lightpit/servlet/ProjectServlet.kt Fri Dec 30 13:21:09 2022 +0100 1.2 +++ b/src/main/kotlin/de/uapcore/lightpit/servlet/ProjectServlet.kt Fri Dec 30 19:04:34 2022 +0100 1.3 @@ -31,10 +31,7 @@ 1.4 import de.uapcore.lightpit.dao.DataAccessObject 1.5 import de.uapcore.lightpit.dateOptValidator 1.6 import de.uapcore.lightpit.entities.* 1.7 -import de.uapcore.lightpit.types.IssueCategory 1.8 -import de.uapcore.lightpit.types.IssueStatus 1.9 -import de.uapcore.lightpit.types.VersionStatus 1.10 -import de.uapcore.lightpit.types.WebColor 1.11 +import de.uapcore.lightpit.types.* 1.12 import de.uapcore.lightpit.viewmodel.* 1.13 import jakarta.servlet.annotation.WebServlet 1.14 import java.sql.Date 1.15 @@ -63,6 +60,8 @@ 1.16 get("/%project/issues/%version/%component/%issue", this::issue) 1.17 get("/%project/issues/%version/%component/%issue/edit", this::issueForm) 1.18 post("/%project/issues/%version/%component/%issue/comment", this::issueComment) 1.19 + post("/%project/issues/%version/%component/%issue/relation", this::issueRelation) 1.20 + get("/%project/issues/%version/%component/%issue/removeRelation", this::issueRemoveRelation) 1.21 get("/%project/issues/%version/%component/-/create", this::issueForm) 1.22 post("/%project/issues/%version/%component/-/commit", this::issueCommit) 1.23 } 1.24 @@ -440,18 +439,35 @@ 1.25 } 1.26 1.27 private fun issue(http: HttpRequest, dao: DataAccessObject) { 1.28 + val issue = http.pathParams["issue"]?.toIntOrNull()?.let(dao::findIssue) 1.29 + if (issue == null) { 1.30 + http.response.sendError(404) 1.31 + return 1.32 + } 1.33 + renderIssueView(http, dao, issue) 1.34 + } 1.35 + 1.36 + private fun renderIssueView( 1.37 + http: HttpRequest, 1.38 + dao: DataAccessObject, 1.39 + issue: Issue, 1.40 + relationError: String? = null 1.41 + ) { 1.42 withPathInfo(http, dao)?.run { 1.43 - val issue = dao.findIssue(http.pathParams["issue"]?.toIntOrNull() ?: -1) 1.44 - if (issue == null) { 1.45 - http.response.sendError(404) 1.46 - return 1.47 - } 1.48 - 1.49 val comments = dao.listComments(issue) 1.50 1.51 with(http) { 1.52 pageTitle = "${projectInfo.project.name}: #${issue.id} ${issue.subject}" 1.53 - view = IssueDetailView(issue, comments, project, version, component) 1.54 + view = IssueDetailView( 1.55 + issue, 1.56 + comments, 1.57 + project, 1.58 + version, 1.59 + component, 1.60 + dao.listIssues(project), 1.61 + dao.listIssueRelations(issue), 1.62 + relationError 1.63 + ) 1.64 feedPath = feedPath(projectInfo.project) 1.65 navigationMenu = activeProjectNavMenu( 1.66 dao.listProjects(), 1.67 @@ -468,7 +484,7 @@ 1.68 1.69 private fun issueForm(http: HttpRequest, dao: DataAccessObject) { 1.70 withPathInfo(http, dao)?.run { 1.71 - val issue = dao.findIssue(http.pathParams["issue"]?.toIntOrNull() ?: -1) ?: Issue( 1.72 + val issue = http.pathParams["issue"]?.toIntOrNull()?.let(dao::findIssue) ?: Issue( 1.73 -1, 1.74 project, 1.75 ) 1.76 @@ -514,7 +530,7 @@ 1.77 1.78 private fun issueComment(http: HttpRequest, dao: DataAccessObject) { 1.79 withPathInfo(http, dao)?.run { 1.80 - val issue = dao.findIssue(http.pathParams["issue"]?.toIntOrNull() ?: -1) 1.81 + val issue = http.pathParams["issue"]?.toIntOrNull()?.let(dao::findIssue) 1.82 if (issue == null) { 1.83 http.response.sendError(404) 1.84 return 1.85 @@ -616,4 +632,88 @@ 1.86 } 1.87 } 1.88 } 1.89 + 1.90 + private fun issueRelation(http: HttpRequest, dao: DataAccessObject) { 1.91 + withPathInfo(http, dao)?.run { 1.92 + val issue = http.pathParams["issue"]?.toIntOrNull()?.let(dao::findIssue) 1.93 + if (issue == null) { 1.94 + http.response.sendError(404) 1.95 + return 1.96 + } 1.97 + 1.98 + // determine the relation type 1.99 + val type: Pair<RelationType, Boolean>? = http.param("type")?.let { 1.100 + try { 1.101 + if (it.startsWith("!")) { 1.102 + Pair(RelationType.valueOf(it.substring(1)), true) 1.103 + } else { 1.104 + Pair(RelationType.valueOf(it), false) 1.105 + } 1.106 + } catch (_: IllegalArgumentException) { 1.107 + null 1.108 + } 1.109 + } 1.110 + 1.111 + // if the relation type was invalid, send HTTP 500 1.112 + if (type == null) { 1.113 + http.response.sendError(500) 1.114 + return 1.115 + } 1.116 + 1.117 + // determine the target issue 1.118 + val targetIssue: Issue? = http.param("issue")?.let { 1.119 + if (it.startsWith("#") && it.length > 1) { 1.120 + it.substring(1).split(" ", limit = 2)[0].toIntOrNull() 1.121 + ?.let(dao::findIssue) 1.122 + ?.takeIf { target -> target.project.id == issue.project.id } 1.123 + } else { 1.124 + null 1.125 + } 1.126 + } 1.127 + 1.128 + // check if the target issue is valid 1.129 + if (targetIssue == null) { 1.130 + renderIssueView(http, dao, issue, "issue.relations.target.invalid") 1.131 + return 1.132 + } 1.133 + 1.134 + // commit the result 1.135 + dao.insertIssueRelation(IssueRelation(issue, targetIssue, type.first, type.second)) 1.136 + http.renderCommit("${issuesHref}${issue.id}") 1.137 + } 1.138 + } 1.139 + 1.140 + private fun issueRemoveRelation(http: HttpRequest, dao: DataAccessObject) { 1.141 + withPathInfo(http, dao)?.run { 1.142 + val issue = http.pathParams["issue"]?.toIntOrNull()?.let(dao::findIssue) 1.143 + if (issue == null) { 1.144 + http.response.sendError(404) 1.145 + return 1.146 + } 1.147 + 1.148 + // determine relation 1.149 + val type = http.param("type")?.let { 1.150 + try {RelationType.valueOf(it)} 1.151 + catch (_:IllegalArgumentException) {null} 1.152 + } 1.153 + if (type == null) { 1.154 + http.response.sendError(500) 1.155 + return 1.156 + } 1.157 + val rel = http.param("to")?.toIntOrNull()?.let(dao::findIssue)?.let { 1.158 + IssueRelation( 1.159 + issue, 1.160 + it, 1.161 + type, 1.162 + http.param("reverse")?.toBoolean() ?: false 1.163 + ) 1.164 + } 1.165 + 1.166 + // execute removal, if there is something to remove 1.167 + rel?.run(dao::deleteIssueRelation) 1.168 + 1.169 + // always pretend that the operation was successful - if there was nothing to remove, it's okay 1.170 + http.renderCommit("${issuesHref}${issue.id}") 1.171 + } 1.172 + } 1.173 } 1.174 \ No newline at end of file