src/main/kotlin/de/uapcore/lightpit/servlet/ProjectServlet.kt

Mon, 30 Oct 2023 14:44:36 +0100

author
Mike Becker <universe@uap-core.de>
date
Mon, 30 Oct 2023 14:44:36 +0100
changeset 292
703591e739f4
parent 284
671c1c8fbf1c
permissions
-rw-r--r--

add possibility to show issues w/o version or component - fixes #335

universe@184 1 /*
universe@184 2 * Copyright 2021 Mike Becker. All rights reserved.
universe@184 3 *
universe@184 4 * Redistribution and use in source and binary forms, with or without
universe@184 5 * modification, are permitted provided that the following conditions are met:
universe@184 6 *
universe@184 7 * 1. Redistributions of source code must retain the above copyright
universe@184 8 * notice, this list of conditions and the following disclaimer.
universe@184 9 *
universe@184 10 * 2. Redistributions in binary form must reproduce the above copyright
universe@184 11 * notice, this list of conditions and the following disclaimer in the
universe@184 12 * documentation and/or other materials provided with the distribution.
universe@184 13 *
universe@184 14 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
universe@184 15 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
universe@184 16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
universe@184 17 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
universe@184 18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
universe@184 19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
universe@184 20 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
universe@184 21 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
universe@184 22 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
universe@184 23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
universe@184 24 */
universe@184 25
universe@184 26 package de.uapcore.lightpit.servlet
universe@184 27
universe@292 28 import de.uapcore.lightpit.*
universe@184 29 import de.uapcore.lightpit.dao.DataAccessObject
universe@184 30 import de.uapcore.lightpit.entities.*
universe@263 31 import de.uapcore.lightpit.types.*
universe@184 32 import de.uapcore.lightpit.viewmodel.*
universe@254 33 import jakarta.servlet.annotation.WebServlet
universe@184 34 import java.sql.Date
universe@184 35
universe@184 36 @WebServlet(urlPatterns = ["/projects/*"])
universe@184 37 class ProjectServlet : AbstractServlet() {
universe@184 38
universe@184 39 init {
universe@184 40 get("/", this::projects)
universe@184 41 get("/%project", this::project)
universe@184 42 get("/%project/issues/%version/%component/", this::project)
universe@184 43 get("/%project/edit", this::projectForm)
universe@184 44 get("/-/create", this::projectForm)
universe@184 45 post("/-/commit", this::projectCommit)
universe@284 46 post("/%project/vcs/analyze", this::vcsAnalyze)
universe@184 47
universe@184 48 get("/%project/versions/", this::versions)
universe@184 49 get("/%project/versions/%version/edit", this::versionForm)
universe@184 50 get("/%project/versions/-/create", this::versionForm)
universe@184 51 post("/%project/versions/-/commit", this::versionCommit)
universe@184 52
universe@184 53 get("/%project/components/", this::components)
universe@184 54 get("/%project/components/%component/edit", this::componentForm)
universe@184 55 get("/%project/components/-/create", this::componentForm)
universe@184 56 post("/%project/components/-/commit", this::componentCommit)
universe@184 57
universe@184 58 get("/%project/issues/%version/%component/%issue", this::issue)
universe@184 59 get("/%project/issues/%version/%component/%issue/edit", this::issueForm)
universe@186 60 post("/%project/issues/%version/%component/%issue/comment", this::issueComment)
universe@263 61 post("/%project/issues/%version/%component/%issue/relation", this::issueRelation)
universe@263 62 get("/%project/issues/%version/%component/%issue/removeRelation", this::issueRemoveRelation)
universe@184 63 get("/%project/issues/%version/%component/-/create", this::issueForm)
universe@186 64 post("/%project/issues/%version/%component/-/commit", this::issueCommit)
universe@184 65 }
universe@184 66
universe@193 67 private fun projects(http: HttpRequest, dao: DataAccessObject) {
universe@184 68 val projects = dao.listProjects()
universe@184 69 val projectInfos = projects.map {
universe@184 70 ProjectInfo(
universe@184 71 project = it,
universe@184 72 versions = dao.listVersions(it),
universe@184 73 components = emptyList(), // not required in this view
universe@184 74 issueSummary = dao.collectIssueSummary(it)
universe@184 75 )
universe@184 76 }
universe@184 77
universe@184 78 with(http) {
universe@184 79 view = ProjectsView(projectInfos)
universe@184 80 navigationMenu = projectNavMenu(projects)
universe@184 81 styleSheets = listOf("projects")
universe@184 82 render("projects")
universe@184 83 }
universe@184 84 }
universe@184 85
universe@184 86 private fun sanitizeNode(name: String): String {
universe@184 87 val san = name.replace(Regex("[/\\\\]"), "-")
universe@184 88 return if (san.startsWith(".")) {
universe@184 89 "v$san"
universe@184 90 } else {
universe@184 91 san
universe@184 92 }
universe@184 93 }
universe@184 94
universe@198 95 private fun feedPath(project: Project) = "feed/${project.node}/issues.rss"
universe@198 96
universe@193 97 private fun project(http: HttpRequest, dao: DataAccessObject) {
universe@292 98 withPathInfo(http, dao)?.let {path ->
universe@292 99 val project = path.projectInfo.project
universe@193 100
universe@268 101 val filter = IssueFilter(http)
universe@268 102
universe@268 103 val needRelationsMap = filter.onlyBlocker
universe@268 104
universe@268 105 val relationsMap = if (needRelationsMap) dao.getIssueRelationMap(project, filter.includeDone) else emptyMap()
universe@268 106
universe@292 107 val specificVersion = path.versionInfo !is OptionalPathInfo.All
universe@292 108 val version = if (path.versionInfo is OptionalPathInfo.Specific) path.versionInfo.elem else null
universe@292 109 val specificComponent = path.componentInfo !is OptionalPathInfo.All
universe@292 110 val component = if (path.componentInfo is OptionalPathInfo.Specific) path.componentInfo.elem else null
universe@292 111
universe@292 112 val issues = dao.listIssues(project, filter.includeDone, specificVersion, version, specificComponent, component)
universe@271 113 .sortedWith(IssueSorter(filter.sortPrimary, filter.sortSecondary, filter.sortTertiary))
universe@268 114 .filter {
universe@268 115 (!filter.onlyMine || (it.assignee?.username ?: "") == (http.remoteUser ?: "<Anonymous>")) &&
universe@268 116 (!filter.onlyBlocker || (relationsMap[it.id]?.any { (_,type) -> type.blocking }?:false)) &&
universe@268 117 (filter.status.isEmpty() || filter.status.contains(it.status)) &&
universe@268 118 (filter.category.isEmpty() || filter.category.contains(it.category))
universe@268 119 }
universe@184 120
universe@184 121 with(http) {
universe@205 122 pageTitle = project.name
universe@292 123 view = ProjectDetails(path, issues, filter)
universe@198 124 feedPath = feedPath(project)
universe@292 125 navigationMenu = projectNavMenu(dao.listProjects(), path)
universe@184 126 styleSheets = listOf("projects")
universe@266 127 javascript = "project-details"
universe@184 128 render("project-details")
universe@184 129 }
universe@184 130 }
universe@184 131 }
universe@184 132
universe@193 133 private fun projectForm(http: HttpRequest, dao: DataAccessObject) {
universe@292 134 http.styleSheets = listOf("projects")
universe@200 135 if (!http.pathParams.containsKey("project")) {
universe@200 136 http.view = ProjectEditView(Project(-1), dao.listUsers())
universe@200 137 http.navigationMenu = projectNavMenu(dao.listProjects())
universe@292 138 http.render("project-form")
universe@200 139 } else {
universe@292 140 withPathInfo(http, dao)?.let { path ->
universe@292 141 http.view = ProjectEditView(path.projectInfo.project, dao.listUsers())
universe@292 142 http.navigationMenu = projectNavMenu(dao.listProjects(), path)
universe@292 143 http.render("project-form")
universe@200 144 }
universe@184 145 }
universe@184 146 }
universe@184 147
universe@193 148 private fun projectCommit(http: HttpRequest, dao: DataAccessObject) {
universe@184 149 val project = Project(http.param("id")?.toIntOrNull() ?: -1).apply {
universe@184 150 name = http.param("name") ?: ""
universe@184 151 node = http.param("node") ?: ""
universe@184 152 description = http.param("description") ?: ""
universe@184 153 ordinal = http.param("ordinal")?.toIntOrNull() ?: 0
universe@184 154 repoUrl = http.param("repoUrl") ?: ""
universe@284 155 vcs = VcsType.valueOf(http.param("vcs") ?: "None")
universe@184 156 owner = (http.param("owner")?.toIntOrNull() ?: -1).let {
universe@184 157 if (it < 0) null else dao.findUser(it)
universe@184 158 }
universe@184 159 // intentional defaults
universe@184 160 if (node.isBlank()) node = name
universe@184 161 // sanitizing
universe@184 162 node = sanitizeNode(node)
universe@184 163 }
universe@184 164
universe@184 165 if (project.id < 0) {
universe@184 166 dao.insertProject(project)
universe@184 167 } else {
universe@184 168 dao.updateProject(project)
universe@184 169 }
universe@184 170
universe@184 171 http.renderCommit("projects/${project.node}")
universe@184 172 }
universe@184 173
universe@284 174 private fun vcsAnalyze(http: HttpRequest, dao: DataAccessObject) {
universe@292 175 withPathInfo(http, dao)?.let { path ->
universe@292 176 // if analysis is not configured, reject the request
universe@292 177 if (path.projectInfo.project.vcs == VcsType.None) {
universe@292 178 http.response.sendError(404)
universe@292 179 return
universe@292 180 }
universe@292 181
universe@292 182 // obtain the list of issues for this project to filter cross-project references
universe@292 183 val knownIds = dao.listIssues(path.projectInfo.project, true).map { it.id }
universe@292 184
universe@292 185 // read the provided commit log and merge only the refs that relate issues from the current project
universe@292 186 dao.mergeCommitRefs(parseCommitRefs(http.body).filter { knownIds.contains(it.issueId) })
universe@284 187 }
universe@284 188 }
universe@284 189
universe@193 190 private fun versions(http: HttpRequest, dao: DataAccessObject) {
universe@292 191 withPathInfo(http, dao)?.let { path ->
universe@292 192 with(http) {
universe@292 193 pageTitle = "${path.projectInfo.project.name} - ${i18n("navmenu.versions")}"
universe@292 194 view = VersionsView(
universe@292 195 path.projectInfo,
universe@292 196 dao.listVersionSummaries(path.projectInfo.project)
universe@292 197 )
universe@292 198 feedPath = feedPath(path.projectInfo.project)
universe@292 199 navigationMenu = projectNavMenu(dao.listProjects(), path)
universe@292 200 styleSheets = listOf("projects")
universe@292 201 javascript = "project-details"
universe@292 202 render("versions")
universe@292 203 }
universe@184 204 }
universe@184 205 }
universe@184 206
universe@193 207 private fun versionForm(http: HttpRequest, dao: DataAccessObject) {
universe@292 208 withPathInfo(http, dao)?.let { path ->
universe@292 209 val version = if (path.versionInfo is OptionalPathInfo.Specific)
universe@292 210 path.versionInfo.elem else Version(-1, path.projectInfo.project.id)
universe@184 211
universe@292 212 with(http) {
universe@292 213 view = VersionEditView(path.projectInfo, version)
universe@292 214 feedPath = feedPath(path.projectInfo.project)
universe@292 215 navigationMenu = projectNavMenu(dao.listProjects(), path)
universe@292 216 styleSheets = listOf("projects")
universe@292 217 render("version-form")
universe@184 218 }
universe@184 219 }
universe@184 220 }
universe@184 221
universe@249 222 private fun obtainIdAndProject(http: HttpRequest, dao: DataAccessObject): Pair<Int, Project>? {
universe@184 223 val id = http.param("id")?.toIntOrNull()
universe@184 224 val projectid = http.param("projectid")?.toIntOrNull() ?: -1
universe@184 225 val project = dao.findProject(projectid)
universe@210 226 return if (id == null || project == null) {
universe@184 227 http.response.sendError(400)
universe@210 228 null
universe@210 229 } else {
universe@210 230 Pair(id, project)
universe@184 231 }
universe@210 232 }
universe@184 233
universe@210 234 private fun versionCommit(http: HttpRequest, dao: DataAccessObject) {
universe@210 235 val idParams = obtainIdAndProject(http, dao) ?: return
universe@210 236 val (id, project) = idParams
universe@210 237
universe@210 238 val version = Version(id, project.id).apply {
universe@184 239 name = http.param("name") ?: ""
universe@184 240 node = http.param("node") ?: ""
universe@184 241 ordinal = http.param("ordinal")?.toIntOrNull() ?: 0
universe@184 242 status = http.param("status")?.let(VersionStatus::valueOf) ?: VersionStatus.Future
universe@225 243 // TODO: process error messages
universe@249 244 eol = http.param("eol", ::dateOptValidator, null, mutableListOf())
universe@249 245 release = http.param("release", ::dateOptValidator, null, mutableListOf())
universe@184 246 // intentional defaults
universe@184 247 if (node.isBlank()) node = name
universe@184 248 // sanitizing
universe@184 249 node = sanitizeNode(node)
universe@184 250 }
universe@184 251
universe@225 252 // sanitize eol and release date
universe@225 253 if (version.status.isEndOfLife) {
universe@225 254 if (version.eol == null) version.eol = Date(System.currentTimeMillis())
universe@225 255 } else if (version.status.isReleased) {
universe@225 256 if (version.release == null) version.release = Date(System.currentTimeMillis())
universe@225 257 }
universe@225 258
universe@184 259 if (id < 0) {
universe@184 260 dao.insertVersion(version)
universe@184 261 } else {
universe@184 262 dao.updateVersion(version)
universe@184 263 }
universe@184 264
universe@184 265 http.renderCommit("projects/${project.node}/versions/")
universe@184 266 }
universe@184 267
universe@193 268 private fun components(http: HttpRequest, dao: DataAccessObject) {
universe@292 269 withPathInfo(http, dao)?.let { path ->
universe@292 270 with(http) {
universe@292 271 pageTitle = "${path.projectInfo.project.name} - ${i18n("navmenu.components")}"
universe@292 272 view = ComponentsView(
universe@292 273 path.projectInfo,
universe@292 274 dao.listComponentSummaries(path.projectInfo.project)
universe@292 275 )
universe@292 276 feedPath = feedPath(path.projectInfo.project)
universe@292 277 navigationMenu = projectNavMenu(dao.listProjects(), path)
universe@292 278 styleSheets = listOf("projects")
universe@292 279 javascript = "project-details"
universe@292 280 render("components")
universe@292 281 }
universe@184 282 }
universe@184 283 }
universe@184 284
universe@193 285 private fun componentForm(http: HttpRequest, dao: DataAccessObject) {
universe@292 286 withPathInfo(http, dao)?.let { path ->
universe@292 287 val component = if (path.componentInfo is OptionalPathInfo.Specific)
universe@292 288 path.componentInfo.elem else Component(-1, path.projectInfo.project.id)
universe@184 289
universe@292 290 with(http) {
universe@292 291 view = ComponentEditView(path.projectInfo, component, dao.listUsers())
universe@292 292 feedPath = feedPath(path.projectInfo.project)
universe@292 293 navigationMenu = projectNavMenu(dao.listProjects(), path)
universe@292 294 styleSheets = listOf("projects")
universe@292 295 render("component-form")
universe@184 296 }
universe@184 297 }
universe@184 298 }
universe@184 299
universe@193 300 private fun componentCommit(http: HttpRequest, dao: DataAccessObject) {
universe@210 301 val idParams = obtainIdAndProject(http, dao) ?: return
universe@210 302 val (id, project) = idParams
universe@184 303
universe@210 304 val component = Component(id, project.id).apply {
universe@184 305 name = http.param("name") ?: ""
universe@184 306 node = http.param("node") ?: ""
universe@184 307 ordinal = http.param("ordinal")?.toIntOrNull() ?: 0
universe@184 308 color = WebColor(http.param("color") ?: "#000000")
universe@184 309 description = http.param("description")
universe@227 310 // TODO: process error message
universe@227 311 active = http.param("active", ::boolValidator, true, mutableListOf())
universe@184 312 lead = (http.param("lead")?.toIntOrNull() ?: -1).let {
universe@184 313 if (it < 0) null else dao.findUser(it)
universe@184 314 }
universe@184 315 // intentional defaults
universe@184 316 if (node.isBlank()) node = name
universe@184 317 // sanitizing
universe@184 318 node = sanitizeNode(node)
universe@184 319 }
universe@184 320
universe@184 321 if (id < 0) {
universe@184 322 dao.insertComponent(component)
universe@184 323 } else {
universe@184 324 dao.updateComponent(component)
universe@184 325 }
universe@184 326
universe@184 327 http.renderCommit("projects/${project.node}/components/")
universe@184 328 }
universe@184 329
universe@193 330 private fun issue(http: HttpRequest, dao: DataAccessObject) {
universe@263 331 val issue = http.pathParams["issue"]?.toIntOrNull()?.let(dao::findIssue)
universe@263 332 if (issue == null) {
universe@263 333 http.response.sendError(404)
universe@263 334 return
universe@263 335 }
universe@263 336 renderIssueView(http, dao, issue)
universe@263 337 }
universe@263 338
universe@263 339 private fun renderIssueView(
universe@263 340 http: HttpRequest,
universe@263 341 dao: DataAccessObject,
universe@263 342 issue: Issue,
universe@263 343 relationError: String? = null
universe@263 344 ) {
universe@292 345 withPathInfo(http, dao)?.let {path ->
universe@184 346 val comments = dao.listComments(issue)
universe@184 347
universe@184 348 with(http) {
universe@292 349 pageTitle = "${path.projectInfo.project.name}: #${issue.id} ${issue.subject}"
universe@263 350 view = IssueDetailView(
universe@292 351 path,
universe@263 352 issue,
universe@263 353 comments,
universe@292 354 path.projectInfo.project,
universe@292 355 dao.listIssues(path.projectInfo.project, true),
universe@263 356 dao.listIssueRelations(issue),
universe@284 357 relationError,
universe@284 358 dao.listCommitRefs(issue)
universe@263 359 )
universe@292 360 feedPath = feedPath(path.projectInfo.project)
universe@292 361 navigationMenu = projectNavMenu(dao.listProjects(), path)
universe@184 362 styleSheets = listOf("projects")
universe@207 363 javascript = "issue-editor"
universe@184 364 render("issue-view")
universe@184 365 }
universe@184 366 }
universe@184 367 }
universe@184 368
universe@193 369 private fun issueForm(http: HttpRequest, dao: DataAccessObject) {
universe@292 370 withPathInfo(http, dao)?.let { path ->
universe@263 371 val issue = http.pathParams["issue"]?.toIntOrNull()?.let(dao::findIssue) ?: Issue(
universe@184 372 -1,
universe@292 373 path.projectInfo.project,
universe@184 374 )
universe@184 375
universe@215 376 // for new issues set some defaults
universe@215 377 if (issue.id < 0) {
universe@215 378 // pre-select component, if available in the path info
universe@292 379 if (path.componentInfo is OptionalPathInfo.Specific) {
universe@292 380 issue.component = path.componentInfo.elem
universe@292 381 }
universe@184 382
universe@215 383 // pre-select version, if available in the path info
universe@292 384 if (path.versionInfo is OptionalPathInfo.Specific) {
universe@292 385 if (path.versionInfo.elem.status.isReleased) {
universe@292 386 issue.affected = path.versionInfo.elem
universe@215 387 } else {
universe@292 388 issue.resolved = path.versionInfo.elem
universe@215 389 }
universe@191 390 }
universe@191 391 }
universe@191 392
universe@184 393 with(http) {
universe@184 394 view = IssueEditView(
universe@184 395 issue,
universe@292 396 path.projectInfo.versions,
universe@292 397 path.projectInfo.components,
universe@184 398 dao.listUsers(),
universe@292 399 path.projectInfo.project,
universe@292 400 path
universe@184 401 )
universe@292 402 feedPath = feedPath(path.projectInfo.project)
universe@292 403 navigationMenu = projectNavMenu(dao.listProjects(), path)
universe@184 404 styleSheets = listOf("projects")
universe@207 405 javascript = "issue-editor"
universe@184 406 render("issue-form")
universe@184 407 }
universe@184 408 }
universe@184 409 }
universe@184 410
universe@193 411 private fun issueComment(http: HttpRequest, dao: DataAccessObject) {
universe@184 412 withPathInfo(http, dao)?.run {
universe@263 413 val issue = http.pathParams["issue"]?.toIntOrNull()?.let(dao::findIssue)
universe@184 414 if (issue == null) {
universe@184 415 http.response.sendError(404)
universe@184 416 return
universe@184 417 }
universe@184 418
universe@207 419 val commentId = http.param("commentid")?.toIntOrNull() ?: -1
universe@207 420 if (commentId > 0) {
universe@207 421 val comment = dao.findComment(commentId)
universe@232 422 if (comment == null) {
universe@232 423 http.response.sendError(404)
universe@232 424 return
universe@232 425 }
universe@232 426 val originalAuthor = comment.author?.username
universe@207 427 if (originalAuthor != null && originalAuthor == http.remoteUser) {
universe@232 428 val newComment = http.param("comment")
universe@232 429 if (!newComment.isNullOrBlank()) {
universe@232 430 comment.comment = newComment
universe@232 431 dao.updateComment(comment)
universe@242 432 dao.insertHistoryEvent(issue, comment)
universe@232 433 } else {
universe@247 434 logger.debug("Not updating comment ${comment.id} because nothing changed.")
universe@232 435 }
universe@207 436 } else {
universe@207 437 http.response.sendError(403)
universe@207 438 return
universe@207 439 }
universe@207 440 } else {
universe@207 441 val comment = IssueComment(-1, issue.id).apply {
universe@207 442 author = http.remoteUser?.let { dao.findUserByName(it) }
universe@207 443 comment = http.param("comment") ?: ""
universe@207 444 }
universe@232 445 val newId = dao.insertComment(comment)
universe@242 446 dao.insertHistoryEvent(issue, comment, newId)
universe@184 447 }
universe@184 448
universe@184 449 http.renderCommit("${issuesHref}${issue.id}")
universe@184 450 }
universe@184 451 }
universe@184 452
universe@193 453 private fun issueCommit(http: HttpRequest, dao: DataAccessObject) {
universe@184 454 withPathInfo(http, dao)?.run {
universe@184 455 val issue = Issue(
universe@184 456 http.param("id")?.toIntOrNull() ?: -1,
universe@292 457 projectInfo.project
universe@184 458 ).apply {
universe@184 459 component = dao.findComponent(http.param("component")?.toIntOrNull() ?: -1)
universe@184 460 category = IssueCategory.valueOf(http.param("category") ?: "")
universe@184 461 status = IssueStatus.valueOf(http.param("status") ?: "")
universe@184 462 subject = http.param("subject") ?: ""
universe@184 463 description = http.param("description") ?: ""
universe@184 464 assignee = http.param("assignee")?.toIntOrNull()?.let {
universe@184 465 when (it) {
universe@184 466 -1 -> null
universe@184 467 -2 -> component?.lead
universe@184 468 else -> dao.findUser(it)
universe@184 469 }
universe@184 470 }
universe@225 471 // TODO: process error messages
universe@225 472 eta = http.param("eta", ::dateOptValidator, null, mutableListOf())
universe@184 473
universe@231 474 affected = http.param("affected")?.toIntOrNull()?.takeIf { it > 0 }?.let { Version(it, project.id) }
universe@231 475 resolved = http.param("resolved")?.toIntOrNull()?.takeIf { it > 0 }?.let { Version(it, project.id) }
universe@184 476 }
universe@184 477
universe@186 478 val openId = if (issue.id < 0) {
universe@232 479 val id = dao.insertIssue(issue)
universe@232 480 dao.insertHistoryEvent(issue, id)
universe@232 481 id
universe@186 482 } else {
universe@232 483 val reference = dao.findIssue(issue.id)
universe@232 484 if (reference == null) {
universe@232 485 http.response.sendError(404)
universe@232 486 return
universe@232 487 }
universe@232 488
universe@232 489 if (issue.hasChanged(reference)) {
universe@232 490 dao.updateIssue(issue)
universe@232 491 dao.insertHistoryEvent(issue)
universe@232 492 } else {
universe@247 493 logger.debug("Not updating issue ${issue.id} because nothing changed.")
universe@232 494 }
universe@232 495
universe@214 496 val newComment = http.param("comment")
universe@214 497 if (!newComment.isNullOrBlank()) {
universe@232 498 val comment = IssueComment(-1, issue.id).apply {
universe@214 499 author = http.remoteUser?.let { dao.findUserByName(it) }
universe@214 500 comment = newComment
universe@232 501 }
universe@232 502 val commentid = dao.insertComment(comment)
universe@242 503 dao.insertHistoryEvent(issue, comment, commentid)
universe@214 504 }
universe@186 505 issue.id
universe@186 506 }
universe@186 507
universe@186 508 if (http.param("more") != null) {
universe@185 509 http.renderCommit("${issuesHref}-/create")
universe@185 510 } else {
universe@186 511 http.renderCommit("${issuesHref}${openId}")
universe@185 512 }
universe@184 513 }
universe@184 514 }
universe@263 515
universe@263 516 private fun issueRelation(http: HttpRequest, dao: DataAccessObject) {
universe@263 517 withPathInfo(http, dao)?.run {
universe@263 518 val issue = http.pathParams["issue"]?.toIntOrNull()?.let(dao::findIssue)
universe@263 519 if (issue == null) {
universe@263 520 http.response.sendError(404)
universe@263 521 return
universe@263 522 }
universe@263 523
universe@263 524 // determine the relation type
universe@263 525 val type: Pair<RelationType, Boolean>? = http.param("type")?.let {
universe@263 526 try {
universe@263 527 if (it.startsWith("!")) {
universe@263 528 Pair(RelationType.valueOf(it.substring(1)), true)
universe@263 529 } else {
universe@263 530 Pair(RelationType.valueOf(it), false)
universe@263 531 }
universe@263 532 } catch (_: IllegalArgumentException) {
universe@263 533 null
universe@263 534 }
universe@263 535 }
universe@263 536
universe@263 537 // if the relation type was invalid, send HTTP 500
universe@263 538 if (type == null) {
universe@263 539 http.response.sendError(500)
universe@263 540 return
universe@263 541 }
universe@263 542
universe@263 543 // determine the target issue
universe@263 544 val targetIssue: Issue? = http.param("issue")?.let {
universe@263 545 if (it.startsWith("#") && it.length > 1) {
universe@263 546 it.substring(1).split(" ", limit = 2)[0].toIntOrNull()
universe@263 547 ?.let(dao::findIssue)
universe@263 548 ?.takeIf { target -> target.project.id == issue.project.id }
universe@263 549 } else {
universe@263 550 null
universe@263 551 }
universe@263 552 }
universe@263 553
universe@263 554 // check if the target issue is valid
universe@263 555 if (targetIssue == null) {
universe@263 556 renderIssueView(http, dao, issue, "issue.relations.target.invalid")
universe@263 557 return
universe@263 558 }
universe@263 559
universe@263 560 // commit the result
universe@263 561 dao.insertIssueRelation(IssueRelation(issue, targetIssue, type.first, type.second))
universe@263 562 http.renderCommit("${issuesHref}${issue.id}")
universe@263 563 }
universe@263 564 }
universe@263 565
universe@263 566 private fun issueRemoveRelation(http: HttpRequest, dao: DataAccessObject) {
universe@263 567 withPathInfo(http, dao)?.run {
universe@263 568 val issue = http.pathParams["issue"]?.toIntOrNull()?.let(dao::findIssue)
universe@263 569 if (issue == null) {
universe@263 570 http.response.sendError(404)
universe@263 571 return
universe@263 572 }
universe@263 573
universe@263 574 // determine relation
universe@263 575 val type = http.param("type")?.let {
universe@263 576 try {RelationType.valueOf(it)}
universe@263 577 catch (_:IllegalArgumentException) {null}
universe@263 578 }
universe@263 579 if (type == null) {
universe@263 580 http.response.sendError(500)
universe@263 581 return
universe@263 582 }
universe@263 583 val rel = http.param("to")?.toIntOrNull()?.let(dao::findIssue)?.let {
universe@263 584 IssueRelation(
universe@263 585 issue,
universe@263 586 it,
universe@263 587 type,
universe@263 588 http.param("reverse")?.toBoolean() ?: false
universe@263 589 )
universe@263 590 }
universe@263 591
universe@263 592 // execute removal, if there is something to remove
universe@263 593 rel?.run(dao::deleteIssueRelation)
universe@263 594
universe@263 595 // always pretend that the operation was successful - if there was nothing to remove, it's okay
universe@263 596 http.renderCommit("${issuesHref}${issue.id}")
universe@263 597 }
universe@263 598 }
universe@184 599 }

mercurial