src/main/kotlin/de/uapcore/lightpit/logic/IssueLogic.kt

changeset 311
bf67e0ff7131
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/kotlin/de/uapcore/lightpit/logic/IssueLogic.kt	Mon Aug 05 18:40:47 2024 +0200
@@ -0,0 +1,223 @@
+package de.uapcore.lightpit.logic
+
+import de.uapcore.lightpit.HttpRequest
+import de.uapcore.lightpit.dao.DataAccessObject
+import de.uapcore.lightpit.dateOptValidator
+import de.uapcore.lightpit.entities.Issue
+import de.uapcore.lightpit.entities.IssueComment
+import de.uapcore.lightpit.entities.IssueRelation
+import de.uapcore.lightpit.entities.Version
+import de.uapcore.lightpit.types.IssueCategory
+import de.uapcore.lightpit.types.IssueStatus
+import de.uapcore.lightpit.types.RelationType
+import de.uapcore.lightpit.viewmodel.IssueDetailView
+import de.uapcore.lightpit.viewmodel.PathInfos
+import de.uapcore.lightpit.viewmodel.PathInfosFull
+import de.uapcore.lightpit.viewmodel.projectNavMenu
+import java.sql.Date
+
+fun Issue.hasChanged(reference: Issue) = !(component == reference.component &&
+        status == reference.status &&
+        category == reference.category &&
+        subject == reference.subject &&
+        description == reference.description &&
+        assignee == reference.assignee &&
+        eta == reference.eta &&
+        affected == reference.affected &&
+        resolved == reference.resolved)
+
+fun Issue.compareEtaTo(date: Date?): Int {
+    val eta = this.eta
+    return if (eta == null && date == null) 0
+    else if (eta == null) 1
+    else if (date == null) -1
+    else eta.compareTo(date)
+}
+
+fun Issue.applyFormData(http: HttpRequest, dao: DataAccessObject): Issue = this.apply {
+    component = dao.findComponent(http.param("component")?.toIntOrNull() ?: -1)
+    category = IssueCategory.valueOf(http.param("category") ?: "")
+    status = IssueStatus.valueOf(http.param("status") ?: "")
+    subject = http.param("subject") ?: ""
+    description = http.param("description") ?: ""
+    assignee = http.param("assignee")?.toIntOrNull()?.let {
+        when (it) {
+            -1 -> null
+            -2 -> (component?.lead ?: project.owner)
+            else -> dao.findUser(it)
+        }
+    }
+    // TODO: process error messages
+    eta = http.param("eta", ::dateOptValidator, null, mutableListOf())
+
+    affected = http.param("affected")?.toIntOrNull()?.takeIf { it > 0 }?.let { Version(it, project.id) }
+    resolved = http.param("resolved")?.toIntOrNull()?.takeIf { it > 0 }?.let { Version(it, project.id) }
+}
+
+fun processIssueForm(issue: Issue, reference: Issue, http: HttpRequest, dao: DataAccessObject) {
+    if (issue.hasChanged(reference)) {
+        dao.updateIssue(issue)
+        dao.insertHistoryEvent(issue)
+    }
+    val newComment = http.param("comment")
+    if (!newComment.isNullOrBlank()) {
+        val comment = IssueComment(-1, issue.id).apply {
+            author = http.remoteUser?.let { dao.findUserByName(it) }
+            comment = newComment
+        }
+        val commentid = dao.insertComment(comment)
+        dao.insertHistoryEvent(issue, comment, commentid)
+    }
+}
+
+fun commitIssueComment(http: HttpRequest, dao: DataAccessObject, pathInfos: PathInfos) {
+    val issue = http.pathParams["issue"]?.toIntOrNull()?.let(dao::findIssue)
+    if (issue == null) {
+        http.response.sendError(404)
+        return
+    }
+    if (processIssueComment(issue, http, dao)) {
+        http.renderCommit("${pathInfos.issuesHref}${issue.id}")
+    }
+}
+
+fun processIssueComment(issue:Issue, http: HttpRequest, dao: DataAccessObject): Boolean {
+    val commentId = http.param("commentid")?.toIntOrNull() ?: -1
+    if (commentId > 0) {
+        val comment = dao.findComment(commentId)
+        if (comment == null) {
+            http.response.sendError(404)
+            return false
+        }
+        val originalAuthor = comment.author?.username
+        if (originalAuthor != null && originalAuthor == http.remoteUser) {
+            val newComment = http.param("comment")
+            if (!newComment.isNullOrBlank()) {
+                comment.comment = newComment
+                dao.updateComment(comment)
+                dao.insertHistoryEvent(issue, comment)
+            }
+        } else {
+            http.response.sendError(403)
+            return false
+        }
+    } else {
+        val comment = IssueComment(-1, issue.id).apply {
+            author = http.remoteUser?.let { dao.findUserByName(it) }
+            comment = http.param("comment") ?: ""
+        }
+        val newId = dao.insertComment(comment)
+        dao.insertHistoryEvent(issue, comment, newId)
+    }
+    return true
+}
+
+fun renderIssueView(
+    http: HttpRequest,
+    dao: DataAccessObject,
+    issue: Issue,
+    pathInfos: PathInfos,
+    relationError: String? = null
+) {
+    val comments = dao.listComments(issue)
+
+    with(http) {
+        pageTitle = "#${issue.id} ${issue.subject} (${issue.project.name})"
+        view = IssueDetailView(
+            issue,
+            comments,
+            dao.listIssues(issue.project, true),
+            dao.listIssueRelations(issue),
+            dao.listCommitRefs(issue),
+            relationError,
+            pathInfos
+        )
+        if (pathInfos is PathInfosFull) {
+            navigationMenu = projectNavMenu(dao.listProjects(), pathInfos)
+        }
+        styleSheets = listOf("projects")
+        javascript = "issue-editor"
+        render("issue-view")
+    }
+}
+
+fun addIssueRelation(http: HttpRequest, dao: DataAccessObject, pathInfos: PathInfos) {
+    val issue = http.pathParams["issue"]?.toIntOrNull()?.let(dao::findIssue)
+    if (issue == null) {
+        http.response.sendError(404)
+        return
+    }
+
+    // determine the relation type
+    val type: Pair<RelationType, Boolean>? = 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, pathInfos, "issue.relations.target.invalid")
+        return
+    }
+
+    // commit the result
+    dao.insertIssueRelation(IssueRelation(issue, targetIssue, type.first, type.second))
+    http.renderCommit("${pathInfos.issuesHref}${issue.id}")
+}
+
+fun removeIssueRelation(http: HttpRequest, dao: DataAccessObject, pathInfos: PathInfos) {
+    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("${pathInfos.issuesHref}${issue.id}")
+}
\ No newline at end of file

mercurial