diff -r bbf4eb9a71f8 -r bf67e0ff7131 src/main/kotlin/de/uapcore/lightpit/logic/IssueLogic.kt --- /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? = 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