1.1 --- a/src/main/kotlin/de/uapcore/lightpit/servlet/ProjectServlet.kt Mon Oct 30 10:06:22 2023 +0100 1.2 +++ b/src/main/kotlin/de/uapcore/lightpit/servlet/ProjectServlet.kt Mon Oct 30 14:44:36 2023 +0100 1.3 @@ -25,11 +25,8 @@ 1.4 1.5 package de.uapcore.lightpit.servlet 1.6 1.7 -import de.uapcore.lightpit.AbstractServlet 1.8 -import de.uapcore.lightpit.HttpRequest 1.9 -import de.uapcore.lightpit.boolValidator 1.10 +import de.uapcore.lightpit.* 1.11 import de.uapcore.lightpit.dao.DataAccessObject 1.12 -import de.uapcore.lightpit.dateOptValidator 1.13 import de.uapcore.lightpit.entities.* 1.14 import de.uapcore.lightpit.types.* 1.15 import de.uapcore.lightpit.viewmodel.* 1.16 @@ -86,53 +83,6 @@ 1.17 } 1.18 } 1.19 1.20 - private fun activeProjectNavMenu( 1.21 - projects: List<Project>, 1.22 - projectInfo: ProjectInfo, 1.23 - selectedVersion: Version? = null, 1.24 - selectedComponent: Component? = null 1.25 - ) = 1.26 - projectNavMenu( 1.27 - projects, 1.28 - projectInfo.versions, 1.29 - projectInfo.components, 1.30 - projectInfo.project, 1.31 - selectedVersion, 1.32 - selectedComponent 1.33 - ) 1.34 - 1.35 - private sealed interface LookupResult<T> 1.36 - private class NotFound<T> : LookupResult<T> 1.37 - private data class Found<T>(val elem: T?) : LookupResult<T> 1.38 - 1.39 - private fun <T : HasNode> HttpRequest.lookupPathParam(paramName: String, list: List<T>): LookupResult<T> { 1.40 - val node = pathParams[paramName] 1.41 - return if (node == null || node == "-") { 1.42 - Found(null) 1.43 - } else { 1.44 - val result = list.find { it.node == node } 1.45 - if (result == null) { 1.46 - NotFound() 1.47 - } else { 1.48 - Found(result) 1.49 - } 1.50 - } 1.51 - } 1.52 - 1.53 - private fun obtainProjectInfo(http: HttpRequest, dao: DataAccessObject): ProjectInfo? { 1.54 - val project = dao.findProjectByNode(http.pathParams["project"] ?: "") ?: return null 1.55 - 1.56 - val versions: List<Version> = dao.listVersions(project) 1.57 - val components: List<Component> = dao.listComponents(project) 1.58 - 1.59 - return ProjectInfo( 1.60 - project, 1.61 - versions, 1.62 - components, 1.63 - dao.collectIssueSummary(project) 1.64 - ) 1.65 - } 1.66 - 1.67 private fun sanitizeNode(name: String): String { 1.68 val san = name.replace(Regex("[/\\\\]"), "-") 1.69 return if (san.startsWith(".")) { 1.70 @@ -144,46 +94,9 @@ 1.71 1.72 private fun feedPath(project: Project) = "feed/${project.node}/issues.rss" 1.73 1.74 - private data class PathInfos( 1.75 - val projectInfo: ProjectInfo, 1.76 - val version: Version?, 1.77 - val component: Component? 1.78 - ) { 1.79 - val project = projectInfo.project 1.80 - val issuesHref by lazyOf("projects/${project.node}/issues/${version?.node ?: "-"}/${component?.node ?: "-"}/") 1.81 - } 1.82 - 1.83 - private fun withPathInfo(http: HttpRequest, dao: DataAccessObject): PathInfos? { 1.84 - val projectInfo = obtainProjectInfo(http, dao) 1.85 - if (projectInfo == null) { 1.86 - http.response.sendError(404) 1.87 - return null 1.88 - } 1.89 - 1.90 - val version = when (val result = http.lookupPathParam("version", projectInfo.versions)) { 1.91 - is NotFound -> { 1.92 - http.response.sendError(404) 1.93 - return null 1.94 - } 1.95 - is Found -> { 1.96 - result.elem 1.97 - } 1.98 - } 1.99 - val component = when (val result = http.lookupPathParam("component", projectInfo.components)) { 1.100 - is NotFound -> { 1.101 - http.response.sendError(404) 1.102 - return null 1.103 - } 1.104 - is Found -> { 1.105 - result.elem 1.106 - } 1.107 - } 1.108 - 1.109 - return PathInfos(projectInfo, version, component) 1.110 - } 1.111 - 1.112 private fun project(http: HttpRequest, dao: DataAccessObject) { 1.113 - withPathInfo(http, dao)?.run { 1.114 + withPathInfo(http, dao)?.let {path -> 1.115 + val project = path.projectInfo.project 1.116 1.117 val filter = IssueFilter(http) 1.118 1.119 @@ -191,7 +104,12 @@ 1.120 1.121 val relationsMap = if (needRelationsMap) dao.getIssueRelationMap(project, filter.includeDone) else emptyMap() 1.122 1.123 - val issues = dao.listIssues(project, filter.includeDone, version, component) 1.124 + val specificVersion = path.versionInfo !is OptionalPathInfo.All 1.125 + val version = if (path.versionInfo is OptionalPathInfo.Specific) path.versionInfo.elem else null 1.126 + val specificComponent = path.componentInfo !is OptionalPathInfo.All 1.127 + val component = if (path.componentInfo is OptionalPathInfo.Specific) path.componentInfo.elem else null 1.128 + 1.129 + val issues = dao.listIssues(project, filter.includeDone, specificVersion, version, specificComponent, component) 1.130 .sortedWith(IssueSorter(filter.sortPrimary, filter.sortSecondary, filter.sortTertiary)) 1.131 .filter { 1.132 (!filter.onlyMine || (it.assignee?.username ?: "") == (http.remoteUser ?: "<Anonymous>")) && 1.133 @@ -202,14 +120,9 @@ 1.134 1.135 with(http) { 1.136 pageTitle = project.name 1.137 - view = ProjectDetails(projectInfo, issues, filter, version, component) 1.138 + view = ProjectDetails(path, issues, filter) 1.139 feedPath = feedPath(project) 1.140 - navigationMenu = activeProjectNavMenu( 1.141 - dao.listProjects(), 1.142 - projectInfo, 1.143 - version, 1.144 - component 1.145 - ) 1.146 + navigationMenu = projectNavMenu(dao.listProjects(), path) 1.147 styleSheets = listOf("projects") 1.148 javascript = "project-details" 1.149 render("project-details") 1.150 @@ -218,23 +131,18 @@ 1.151 } 1.152 1.153 private fun projectForm(http: HttpRequest, dao: DataAccessObject) { 1.154 + http.styleSheets = listOf("projects") 1.155 if (!http.pathParams.containsKey("project")) { 1.156 http.view = ProjectEditView(Project(-1), dao.listUsers()) 1.157 http.navigationMenu = projectNavMenu(dao.listProjects()) 1.158 + http.render("project-form") 1.159 } else { 1.160 - val projectInfo = obtainProjectInfo(http, dao) 1.161 - if (projectInfo == null) { 1.162 - http.response.sendError(404) 1.163 - return 1.164 + withPathInfo(http, dao)?.let { path -> 1.165 + http.view = ProjectEditView(path.projectInfo.project, dao.listUsers()) 1.166 + http.navigationMenu = projectNavMenu(dao.listProjects(), path) 1.167 + http.render("project-form") 1.168 } 1.169 - http.view = ProjectEditView(projectInfo.project, dao.listUsers()) 1.170 - http.navigationMenu = activeProjectNavMenu( 1.171 - dao.listProjects(), 1.172 - projectInfo 1.173 - ) 1.174 } 1.175 - http.styleSheets = listOf("projects") 1.176 - http.render("project-form") 1.177 } 1.178 1.179 private fun projectCommit(http: HttpRequest, dao: DataAccessObject) { 1.180 @@ -264,77 +172,50 @@ 1.181 } 1.182 1.183 private fun vcsAnalyze(http: HttpRequest, dao: DataAccessObject) { 1.184 - val projectInfo = obtainProjectInfo(http, dao) 1.185 - if (projectInfo == null) { 1.186 - http.response.sendError(404) 1.187 - return 1.188 + withPathInfo(http, dao)?.let { path -> 1.189 + // if analysis is not configured, reject the request 1.190 + if (path.projectInfo.project.vcs == VcsType.None) { 1.191 + http.response.sendError(404) 1.192 + return 1.193 + } 1.194 + 1.195 + // obtain the list of issues for this project to filter cross-project references 1.196 + val knownIds = dao.listIssues(path.projectInfo.project, true).map { it.id } 1.197 + 1.198 + // read the provided commit log and merge only the refs that relate issues from the current project 1.199 + dao.mergeCommitRefs(parseCommitRefs(http.body).filter { knownIds.contains(it.issueId) }) 1.200 } 1.201 - 1.202 - // if analysis is not configured, reject the request 1.203 - if (projectInfo.project.vcs == VcsType.None) { 1.204 - http.response.sendError(404) 1.205 - return 1.206 - } 1.207 - 1.208 - // obtain the list of issues for this project to filter cross-project references 1.209 - val knownIds = dao.listIssues(projectInfo.project, true).map { it.id } 1.210 - 1.211 - // read the provided commit log and merge only the refs that relate issues from the current project 1.212 - dao.mergeCommitRefs(parseCommitRefs(http.body).filter { knownIds.contains(it.issueId) }) 1.213 } 1.214 1.215 private fun versions(http: HttpRequest, dao: DataAccessObject) { 1.216 - val projectInfo = obtainProjectInfo(http, dao) 1.217 - if (projectInfo == null) { 1.218 - http.response.sendError(404) 1.219 - return 1.220 - } 1.221 - 1.222 - with(http) { 1.223 - pageTitle = "${projectInfo.project.name} - ${i18n("navmenu.versions")}" 1.224 - view = VersionsView( 1.225 - projectInfo, 1.226 - dao.listVersionSummaries(projectInfo.project) 1.227 - ) 1.228 - feedPath = feedPath(projectInfo.project) 1.229 - navigationMenu = activeProjectNavMenu( 1.230 - dao.listProjects(), 1.231 - projectInfo 1.232 - ) 1.233 - styleSheets = listOf("projects") 1.234 - javascript = "project-details" 1.235 - render("versions") 1.236 + withPathInfo(http, dao)?.let { path -> 1.237 + with(http) { 1.238 + pageTitle = "${path.projectInfo.project.name} - ${i18n("navmenu.versions")}" 1.239 + view = VersionsView( 1.240 + path.projectInfo, 1.241 + dao.listVersionSummaries(path.projectInfo.project) 1.242 + ) 1.243 + feedPath = feedPath(path.projectInfo.project) 1.244 + navigationMenu = projectNavMenu(dao.listProjects(), path) 1.245 + styleSheets = listOf("projects") 1.246 + javascript = "project-details" 1.247 + render("versions") 1.248 + } 1.249 } 1.250 } 1.251 1.252 private fun versionForm(http: HttpRequest, dao: DataAccessObject) { 1.253 - val projectInfo = obtainProjectInfo(http, dao) 1.254 - if (projectInfo == null) { 1.255 - http.response.sendError(404) 1.256 - return 1.257 - } 1.258 + withPathInfo(http, dao)?.let { path -> 1.259 + val version = if (path.versionInfo is OptionalPathInfo.Specific) 1.260 + path.versionInfo.elem else Version(-1, path.projectInfo.project.id) 1.261 1.262 - val version: Version 1.263 - when (val result = http.lookupPathParam("version", projectInfo.versions)) { 1.264 - is NotFound -> { 1.265 - http.response.sendError(404) 1.266 - return 1.267 + with(http) { 1.268 + view = VersionEditView(path.projectInfo, version) 1.269 + feedPath = feedPath(path.projectInfo.project) 1.270 + navigationMenu = projectNavMenu(dao.listProjects(), path) 1.271 + styleSheets = listOf("projects") 1.272 + render("version-form") 1.273 } 1.274 - is Found -> { 1.275 - version = result.elem ?: Version(-1, projectInfo.project.id) 1.276 - } 1.277 - } 1.278 - 1.279 - with(http) { 1.280 - view = VersionEditView(projectInfo, version) 1.281 - feedPath = feedPath(projectInfo.project) 1.282 - navigationMenu = activeProjectNavMenu( 1.283 - dao.listProjects(), 1.284 - projectInfo, 1.285 - selectedVersion = version 1.286 - ) 1.287 - styleSheets = listOf("projects") 1.288 - render("version-form") 1.289 } 1.290 } 1.291 1.292 @@ -385,57 +266,34 @@ 1.293 } 1.294 1.295 private fun components(http: HttpRequest, dao: DataAccessObject) { 1.296 - val projectInfo = obtainProjectInfo(http, dao) 1.297 - if (projectInfo == null) { 1.298 - http.response.sendError(404) 1.299 - return 1.300 - } 1.301 - 1.302 - with(http) { 1.303 - pageTitle = "${projectInfo.project.name} - ${i18n("navmenu.components")}" 1.304 - view = ComponentsView( 1.305 - projectInfo, 1.306 - dao.listComponentSummaries(projectInfo.project) 1.307 - ) 1.308 - feedPath = feedPath(projectInfo.project) 1.309 - navigationMenu = activeProjectNavMenu( 1.310 - dao.listProjects(), 1.311 - projectInfo 1.312 - ) 1.313 - styleSheets = listOf("projects") 1.314 - javascript = "project-details" 1.315 - render("components") 1.316 + withPathInfo(http, dao)?.let { path -> 1.317 + with(http) { 1.318 + pageTitle = "${path.projectInfo.project.name} - ${i18n("navmenu.components")}" 1.319 + view = ComponentsView( 1.320 + path.projectInfo, 1.321 + dao.listComponentSummaries(path.projectInfo.project) 1.322 + ) 1.323 + feedPath = feedPath(path.projectInfo.project) 1.324 + navigationMenu = projectNavMenu(dao.listProjects(), path) 1.325 + styleSheets = listOf("projects") 1.326 + javascript = "project-details" 1.327 + render("components") 1.328 + } 1.329 } 1.330 } 1.331 1.332 private fun componentForm(http: HttpRequest, dao: DataAccessObject) { 1.333 - val projectInfo = obtainProjectInfo(http, dao) 1.334 - if (projectInfo == null) { 1.335 - http.response.sendError(404) 1.336 - return 1.337 - } 1.338 + withPathInfo(http, dao)?.let { path -> 1.339 + val component = if (path.componentInfo is OptionalPathInfo.Specific) 1.340 + path.componentInfo.elem else Component(-1, path.projectInfo.project.id) 1.341 1.342 - val component: Component 1.343 - when (val result = http.lookupPathParam("component", projectInfo.components)) { 1.344 - is NotFound -> { 1.345 - http.response.sendError(404) 1.346 - return 1.347 + with(http) { 1.348 + view = ComponentEditView(path.projectInfo, component, dao.listUsers()) 1.349 + feedPath = feedPath(path.projectInfo.project) 1.350 + navigationMenu = projectNavMenu(dao.listProjects(), path) 1.351 + styleSheets = listOf("projects") 1.352 + render("component-form") 1.353 } 1.354 - is Found -> { 1.355 - component = result.elem ?: Component(-1, projectInfo.project.id) 1.356 - } 1.357 - } 1.358 - 1.359 - with(http) { 1.360 - view = ComponentEditView(projectInfo, component, dao.listUsers()) 1.361 - feedPath = feedPath(projectInfo.project) 1.362 - navigationMenu = activeProjectNavMenu( 1.363 - dao.listProjects(), 1.364 - projectInfo, 1.365 - selectedComponent = component 1.366 - ) 1.367 - styleSheets = listOf("projects") 1.368 - render("component-form") 1.369 } 1.370 } 1.371 1.372 @@ -484,29 +342,23 @@ 1.373 issue: Issue, 1.374 relationError: String? = null 1.375 ) { 1.376 - withPathInfo(http, dao)?.run { 1.377 + withPathInfo(http, dao)?.let {path -> 1.378 val comments = dao.listComments(issue) 1.379 1.380 with(http) { 1.381 - pageTitle = "${projectInfo.project.name}: #${issue.id} ${issue.subject}" 1.382 + pageTitle = "${path.projectInfo.project.name}: #${issue.id} ${issue.subject}" 1.383 view = IssueDetailView( 1.384 + path, 1.385 issue, 1.386 comments, 1.387 - project, 1.388 - version, 1.389 - component, 1.390 - dao.listIssues(project, true), 1.391 + path.projectInfo.project, 1.392 + dao.listIssues(path.projectInfo.project, true), 1.393 dao.listIssueRelations(issue), 1.394 relationError, 1.395 dao.listCommitRefs(issue) 1.396 ) 1.397 - feedPath = feedPath(projectInfo.project) 1.398 - navigationMenu = activeProjectNavMenu( 1.399 - dao.listProjects(), 1.400 - projectInfo, 1.401 - version, 1.402 - component 1.403 - ) 1.404 + feedPath = feedPath(path.projectInfo.project) 1.405 + navigationMenu = projectNavMenu(dao.listProjects(), path) 1.406 styleSheets = listOf("projects") 1.407 javascript = "issue-editor" 1.408 render("issue-view") 1.409 @@ -515,23 +367,25 @@ 1.410 } 1.411 1.412 private fun issueForm(http: HttpRequest, dao: DataAccessObject) { 1.413 - withPathInfo(http, dao)?.run { 1.414 + withPathInfo(http, dao)?.let { path -> 1.415 val issue = http.pathParams["issue"]?.toIntOrNull()?.let(dao::findIssue) ?: Issue( 1.416 -1, 1.417 - project, 1.418 + path.projectInfo.project, 1.419 ) 1.420 1.421 // for new issues set some defaults 1.422 if (issue.id < 0) { 1.423 // pre-select component, if available in the path info 1.424 - issue.component = component 1.425 + if (path.componentInfo is OptionalPathInfo.Specific) { 1.426 + issue.component = path.componentInfo.elem 1.427 + } 1.428 1.429 // pre-select version, if available in the path info 1.430 - if (version != null) { 1.431 - if (version.status.isReleased) { 1.432 - issue.affected = version 1.433 + if (path.versionInfo is OptionalPathInfo.Specific) { 1.434 + if (path.versionInfo.elem.status.isReleased) { 1.435 + issue.affected = path.versionInfo.elem 1.436 } else { 1.437 - issue.resolved = version 1.438 + issue.resolved = path.versionInfo.elem 1.439 } 1.440 } 1.441 } 1.442 @@ -539,20 +393,14 @@ 1.443 with(http) { 1.444 view = IssueEditView( 1.445 issue, 1.446 - projectInfo.versions, 1.447 - projectInfo.components, 1.448 + path.projectInfo.versions, 1.449 + path.projectInfo.components, 1.450 dao.listUsers(), 1.451 - project, 1.452 - version, 1.453 - component 1.454 + path.projectInfo.project, 1.455 + path 1.456 ) 1.457 - feedPath = feedPath(projectInfo.project) 1.458 - navigationMenu = activeProjectNavMenu( 1.459 - dao.listProjects(), 1.460 - projectInfo, 1.461 - version, 1.462 - component 1.463 - ) 1.464 + feedPath = feedPath(path.projectInfo.project) 1.465 + navigationMenu = projectNavMenu(dao.listProjects(), path) 1.466 styleSheets = listOf("projects") 1.467 javascript = "issue-editor" 1.468 render("issue-form") 1.469 @@ -606,7 +454,7 @@ 1.470 withPathInfo(http, dao)?.run { 1.471 val issue = Issue( 1.472 http.param("id")?.toIntOrNull() ?: -1, 1.473 - project 1.474 + projectInfo.project 1.475 ).apply { 1.476 component = dao.findComponent(http.param("component")?.toIntOrNull() ?: -1) 1.477 category = IssueCategory.valueOf(http.param("category") ?: "")