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

Mon, 05 Aug 2024 18:40:47 +0200

author
Mike Becker <universe@uap-core.de>
date
Mon, 05 Aug 2024 18:40:47 +0200
changeset 311
bf67e0ff7131
permissions
-rw-r--r--

add new global issues page - fixes #404

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}")
}

mercurial