diff -r c357c4e69b9e -r aa22103809cd src/main/kotlin/de/uapcore/lightpit/servlet/ProjectServlet.kt --- a/src/main/kotlin/de/uapcore/lightpit/servlet/ProjectServlet.kt Fri Dec 30 13:21:09 2022 +0100 +++ b/src/main/kotlin/de/uapcore/lightpit/servlet/ProjectServlet.kt Fri Dec 30 19:04:34 2022 +0100 @@ -31,10 +31,7 @@ import de.uapcore.lightpit.dao.DataAccessObject import de.uapcore.lightpit.dateOptValidator import de.uapcore.lightpit.entities.* -import de.uapcore.lightpit.types.IssueCategory -import de.uapcore.lightpit.types.IssueStatus -import de.uapcore.lightpit.types.VersionStatus -import de.uapcore.lightpit.types.WebColor +import de.uapcore.lightpit.types.* import de.uapcore.lightpit.viewmodel.* import jakarta.servlet.annotation.WebServlet import java.sql.Date @@ -63,6 +60,8 @@ get("/%project/issues/%version/%component/%issue", this::issue) get("/%project/issues/%version/%component/%issue/edit", this::issueForm) post("/%project/issues/%version/%component/%issue/comment", this::issueComment) + post("/%project/issues/%version/%component/%issue/relation", this::issueRelation) + get("/%project/issues/%version/%component/%issue/removeRelation", this::issueRemoveRelation) get("/%project/issues/%version/%component/-/create", this::issueForm) post("/%project/issues/%version/%component/-/commit", this::issueCommit) } @@ -440,18 +439,35 @@ } private fun issue(http: HttpRequest, dao: DataAccessObject) { + val issue = http.pathParams["issue"]?.toIntOrNull()?.let(dao::findIssue) + if (issue == null) { + http.response.sendError(404) + return + } + renderIssueView(http, dao, issue) + } + + private fun renderIssueView( + http: HttpRequest, + dao: DataAccessObject, + issue: Issue, + relationError: String? = null + ) { withPathInfo(http, dao)?.run { - val issue = dao.findIssue(http.pathParams["issue"]?.toIntOrNull() ?: -1) - if (issue == null) { - http.response.sendError(404) - return - } - val comments = dao.listComments(issue) with(http) { pageTitle = "${projectInfo.project.name}: #${issue.id} ${issue.subject}" - view = IssueDetailView(issue, comments, project, version, component) + view = IssueDetailView( + issue, + comments, + project, + version, + component, + dao.listIssues(project), + dao.listIssueRelations(issue), + relationError + ) feedPath = feedPath(projectInfo.project) navigationMenu = activeProjectNavMenu( dao.listProjects(), @@ -468,7 +484,7 @@ private fun issueForm(http: HttpRequest, dao: DataAccessObject) { withPathInfo(http, dao)?.run { - val issue = dao.findIssue(http.pathParams["issue"]?.toIntOrNull() ?: -1) ?: Issue( + val issue = http.pathParams["issue"]?.toIntOrNull()?.let(dao::findIssue) ?: Issue( -1, project, ) @@ -514,7 +530,7 @@ private fun issueComment(http: HttpRequest, dao: DataAccessObject) { withPathInfo(http, dao)?.run { - val issue = dao.findIssue(http.pathParams["issue"]?.toIntOrNull() ?: -1) + val issue = http.pathParams["issue"]?.toIntOrNull()?.let(dao::findIssue) if (issue == null) { http.response.sendError(404) return @@ -616,4 +632,88 @@ } } } + + private fun issueRelation(http: HttpRequest, dao: DataAccessObject) { + withPathInfo(http, dao)?.run { + val issue = http.pathParams["issue"]?.toIntOrNull()?.let(dao::findIssue) + if (issue == null) { + http.response.sendError(404) + return + } + + // determine the relation type + val type: Pair? = http.param("type")?.let { + try { + if (it.startsWith("!")) { + Pair(RelationType.valueOf(it.substring(1)), true) + } else { + Pair(RelationType.valueOf(it), false) + } + } catch (_: IllegalArgumentException) { + null + } + } + + // if the relation type was invalid, send HTTP 500 + if (type == null) { + http.response.sendError(500) + return + } + + // determine the target issue + val targetIssue: Issue? = http.param("issue")?.let { + if (it.startsWith("#") && it.length > 1) { + it.substring(1).split(" ", limit = 2)[0].toIntOrNull() + ?.let(dao::findIssue) + ?.takeIf { target -> target.project.id == issue.project.id } + } else { + null + } + } + + // check if the target issue is valid + if (targetIssue == null) { + renderIssueView(http, dao, issue, "issue.relations.target.invalid") + return + } + + // commit the result + dao.insertIssueRelation(IssueRelation(issue, targetIssue, type.first, type.second)) + http.renderCommit("${issuesHref}${issue.id}") + } + } + + private fun issueRemoveRelation(http: HttpRequest, dao: DataAccessObject) { + withPathInfo(http, dao)?.run { + val issue = http.pathParams["issue"]?.toIntOrNull()?.let(dao::findIssue) + if (issue == null) { + http.response.sendError(404) + return + } + + // determine relation + val type = http.param("type")?.let { + try {RelationType.valueOf(it)} + catch (_:IllegalArgumentException) {null} + } + if (type == null) { + http.response.sendError(500) + return + } + val rel = http.param("to")?.toIntOrNull()?.let(dao::findIssue)?.let { + IssueRelation( + issue, + it, + type, + http.param("reverse")?.toBoolean() ?: false + ) + } + + // execute removal, if there is something to remove + rel?.run(dao::deleteIssueRelation) + + // always pretend that the operation was successful - if there was nothing to remove, it's okay + http.renderCommit("${issuesHref}${issue.id}") + } + } } \ No newline at end of file