#109 adds Stasi that collects intel for the feed

Thu, 19 Aug 2021 17:20:43 +0200

author
Mike Becker <universe@uap-core.de>
date
Thu, 19 Aug 2021 17:20:43 +0200
changeset 232
296e12ff8d1c
parent 231
dcb1d5a7ea3a
child 233
9219e2d4117b

#109 adds Stasi that collects intel for the feed

setup/postgres/psql_create_tables.sql file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/dao/DataAccessObject.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/dao/PostgresDataAccessObject.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/entities/Issue.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/entities/User.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/servlet/ProjectServlet.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/types/IssueHistoryType.kt file | annotate | diff | comparison | revisions
--- a/setup/postgres/psql_create_tables.sql	Thu Aug 19 14:51:04 2021 +0200
+++ b/setup/postgres/psql_create_tables.sql	Thu Aug 19 17:20:43 2021 +0200
@@ -101,6 +101,35 @@
     resolved    integer references lpit_version (versionid)
 );
 
+create type issue_history_event as enum (
+    'New',
+    'Update',
+    'NewComment',
+    'UpdateComment'
+    );
+
+create table lpit_issue_history_event
+(
+    eventid serial primary key,
+    issueid integer                  not null references lpit_issue (issueid) on delete cascade,
+    time    timestamp with time zone not null default now(),
+    type    issue_history_event      not null
+);
+
+create table lpit_issue_history_data
+(
+    eventid     integer        not null references lpit_issue_history_event (eventid) on delete cascade,
+    component   text,
+    status      issue_status   not null,
+    category    issue_category not null,
+    subject     text           not null,
+    description text,
+    assignee    text,
+    eta         date,
+    affected    text,
+    resolved    text
+);
+
 create table lpit_issue_comment
 (
     commentid   serial primary key,
@@ -111,3 +140,11 @@
     updatecount integer                  not null default 0,
     comment     text                     not null
 );
+
+create table lpit_issue_comment_history
+(
+    commentid integer not null references lpit_issue_comment (commentid) on delete cascade,
+    eventid   integer not null references lpit_issue_history_event (eventid) on delete cascade,
+    comment   text    not null
+);
+
--- a/src/main/kotlin/de/uapcore/lightpit/dao/DataAccessObject.kt	Thu Aug 19 14:51:04 2021 +0200
+++ b/src/main/kotlin/de/uapcore/lightpit/dao/DataAccessObject.kt	Thu Aug 19 17:20:43 2021 +0200
@@ -76,6 +76,9 @@
 
     fun listComments(issue: Issue): List<IssueComment>
     fun findComment(id: Int): IssueComment?
-    fun insertComment(issueComment: IssueComment)
+    fun insertComment(issueComment: IssueComment): Int
     fun updateComment(issueComment: IssueComment)
-}
\ No newline at end of file
+
+    fun insertHistoryEvent(issue: Issue, newId: Int = 0)
+    fun insertHistoryEvent(issueComment: IssueComment, newId: Int = 0)
+}
--- a/src/main/kotlin/de/uapcore/lightpit/dao/PostgresDataAccessObject.kt	Thu Aug 19 14:51:04 2021 +0200
+++ b/src/main/kotlin/de/uapcore/lightpit/dao/PostgresDataAccessObject.kt	Thu Aug 19 17:20:43 2021 +0200
@@ -26,6 +26,7 @@
 package de.uapcore.lightpit.dao
 
 import de.uapcore.lightpit.entities.*
+import de.uapcore.lightpit.types.IssueHistoryType
 import de.uapcore.lightpit.types.WebColor
 import de.uapcore.lightpit.util.*
 import de.uapcore.lightpit.viewmodel.ComponentSummary
@@ -565,7 +566,7 @@
         val id = withStatement(
             """
             insert into lpit_issue (component, status, category, subject, description, assignee, eta, affected, resolved, project)
-            values (?, ?::issue_status, ?::issue_category, ?, ?, ?, ?, ?)
+            values (?, ?::issue_status, ?::issue_category, ?, ?, ?, ?, ?, ?, ?)
             returning issueid
             """.trimIndent()
         ) {
@@ -591,6 +592,36 @@
         }
     }
 
+    override fun insertHistoryEvent(issue: Issue, newId: Int) {
+        val type = if (newId > 0) IssueHistoryType.New else IssueHistoryType.Update
+        val issueid = if (newId > 0) newId else issue.id
+
+        val eventid =
+            withStatement("insert into lpit_issue_history_event(issueid, type) values (?,?::issue_history_event) returning eventid") {
+                setInt(1, issueid)
+                setEnum(2, type)
+                querySingle { it.getInt(1) }!!
+            }
+        withStatement(
+            """
+            insert into lpit_issue_history_data (component, status, category, subject, description, assignee, eta, affected, resolved, eventid)
+            values (?, ?::issue_status, ?::issue_category, ?, ?, ?, ?, ?, ?, ?)
+            """.trimIndent()
+        ) {
+            setStringOrNull(1, issue.component?.name)
+            setEnum(2, issue.status)
+            setEnum(3, issue.category)
+            setString(4, issue.subject)
+            setStringOrNull(5, issue.description)
+            setStringOrNull(6, issue.assignee?.shortDisplayname)
+            setDateOrNull(7, issue.eta)
+            setStringOrNull(8, issue.affected?.name)
+            setStringOrNull(9, issue.resolved?.name)
+            setInt(10, eventid)
+            executeUpdate()
+        }
+    }
+
     //</editor-fold>
 
     //<editor-fold desc="IssueComment">
@@ -616,20 +647,20 @@
             querySingle { it.extractIssueComment() }
         }
 
-    override fun insertComment(issueComment: IssueComment) {
+    override fun insertComment(issueComment: IssueComment): Int =
         useStatement("update lpit_issue set updated = now() where issueid = ?") { updateIssueDate ->
-            withStatement("insert into lpit_issue_comment (issueid, comment, userid) values (?, ? ,?)") {
+            withStatement("insert into lpit_issue_comment (issueid, comment, userid) values (?, ? ,?) returning commentid") {
                 with(issueComment) {
                     updateIssueDate.setInt(1, issueid)
                     setInt(1, issueid)
                     setStringSafe(2, comment)
                     setIntOrNull(3, author?.id)
                 }
-                executeUpdate()
+                val commentid = querySingle { it.getInt(1) }!!
                 updateIssueDate.executeUpdate()
+                commentid
             }
         }
-    }
 
     override fun updateComment(issueComment: IssueComment) {
         useStatement("update lpit_issue set updated = now() where issueid = ?") { updateIssueDate ->
@@ -644,5 +675,25 @@
             }
         }
     }
+
+
+    override fun insertHistoryEvent(issueComment: IssueComment, newId: Int) {
+        val type = if (newId > 0) IssueHistoryType.NewComment else IssueHistoryType.UpdateComment
+        val commentid = if (newId > 0) newId else issueComment.id
+
+        val eventid =
+            withStatement("insert into lpit_issue_history_event(issueid, type) values (?,?::issue_history_event) returning eventid") {
+                setInt(1, issueComment.issueid)
+                setEnum(2, type)
+                querySingle { it.getInt(1) }!!
+            }
+        withStatement("insert into lpit_issue_comment_history (commentid, eventid, comment) values (?,?,?)") {
+            setInt(1, commentid)
+            setInt(2, eventid)
+            setString(3, issueComment.comment)
+            executeUpdate()
+        }
+    }
+
     //</editor-fold>
 }
\ No newline at end of file
--- a/src/main/kotlin/de/uapcore/lightpit/entities/Issue.kt	Thu Aug 19 14:51:04 2021 +0200
+++ b/src/main/kotlin/de/uapcore/lightpit/entities/Issue.kt	Thu Aug 19 17:20:43 2021 +0200
@@ -52,5 +52,14 @@
      * An issue is overdue, if it is not done and the ETA is before the current time.
      */
     val overdue get() = status.phase != IssueStatusPhase.Done && eta?.before(Date(System.currentTimeMillis())) ?: false
+
+    fun hasChanged(reference: Issue) = !(component == reference.component &&
+            status == reference.status &&
+            category == reference.category &&
+            subject == reference.subject &&
+            description == reference.description &&
+            eta == reference.eta &&
+            affected == reference.affected &&
+            resolved == reference.resolved)
 }
 
--- a/src/main/kotlin/de/uapcore/lightpit/entities/User.kt	Thu Aug 19 14:51:04 2021 +0200
+++ b/src/main/kotlin/de/uapcore/lightpit/entities/User.kt	Thu Aug 19 17:20:43 2021 +0200
@@ -31,11 +31,18 @@
     var givenname: String? = null
     var lastname: String? = null
 
+    /**
+     * The display name without mail address.
+     */
     val shortDisplayname: String
         get() {
             val str = "${givenname ?: ""} ${lastname ?: ""}"
             return if (str.isBlank()) username else str.trim()
         }
 
+    /**
+     * Shows the full name plus mail address.
+     * If neither given name nor lastname are provided, the username is used instead.
+     */
     val displayname: String get() = if (mail.isNullOrBlank()) shortDisplayname else "$shortDisplayname <$mail>"
 }
\ No newline at end of file
--- a/src/main/kotlin/de/uapcore/lightpit/servlet/ProjectServlet.kt	Thu Aug 19 14:51:04 2021 +0200
+++ b/src/main/kotlin/de/uapcore/lightpit/servlet/ProjectServlet.kt	Thu Aug 19 17:20:43 2021 +0200
@@ -25,11 +25,8 @@
 
 package de.uapcore.lightpit.servlet
 
-import de.uapcore.lightpit.AbstractServlet
-import de.uapcore.lightpit.HttpRequest
-import de.uapcore.lightpit.boolValidator
+import de.uapcore.lightpit.*
 import de.uapcore.lightpit.dao.DataAccessObject
-import de.uapcore.lightpit.dateOptValidator
 import de.uapcore.lightpit.entities.*
 import de.uapcore.lightpit.types.IssueCategory
 import de.uapcore.lightpit.types.IssueStatus
@@ -524,10 +521,20 @@
             val commentId = http.param("commentid")?.toIntOrNull() ?: -1
             if (commentId > 0) {
                 val comment = dao.findComment(commentId)
-                val originalAuthor = comment?.author?.username
+                if (comment == null) {
+                    http.response.sendError(404)
+                    return
+                }
+                val originalAuthor = comment.author?.username
                 if (originalAuthor != null && originalAuthor == http.remoteUser) {
-                    comment.comment = http.param("comment") ?: ""
-                    dao.updateComment(comment)
+                    val newComment = http.param("comment")
+                    if (!newComment.isNullOrBlank()) {
+                        comment.comment = newComment
+                        dao.updateComment(comment)
+                        dao.insertHistoryEvent(comment)
+                    } else {
+                        logger().debug("Not updating comment ${comment.id} because nothing changed.")
+                    }
                 } else {
                     http.response.sendError(403)
                     return
@@ -537,7 +544,8 @@
                     author = http.remoteUser?.let { dao.findUserByName(it) }
                     comment = http.param("comment") ?: ""
                 }
-                dao.insertComment(comment)
+                val newId = dao.insertComment(comment)
+                dao.insertHistoryEvent(comment, newId)
             }
 
             http.renderCommit("${issuesHref}${issue.id}")
@@ -570,15 +578,31 @@
             }
 
             val openId = if (issue.id < 0) {
-                dao.insertIssue(issue)
+                val id = dao.insertIssue(issue)
+                dao.insertHistoryEvent(issue, id)
+                id
             } else {
-                dao.updateIssue(issue)
+                val reference = dao.findIssue(issue.id)
+                if (reference == null) {
+                    http.response.sendError(404)
+                    return
+                }
+
+                if (issue.hasChanged(reference)) {
+                    dao.updateIssue(issue)
+                    dao.insertHistoryEvent(issue)
+                } else {
+                    logger().debug("Not updating issue ${issue.id} because nothing changed.")
+                }
+
                 val newComment = http.param("comment")
                 if (!newComment.isNullOrBlank()) {
-                    dao.insertComment(IssueComment(-1, issue.id).apply {
+                    val comment = IssueComment(-1, issue.id).apply {
                         author = http.remoteUser?.let { dao.findUserByName(it) }
                         comment = newComment
-                    })
+                    }
+                    val commentid = dao.insertComment(comment)
+                    dao.insertHistoryEvent(comment, commentid)
                 }
                 issue.id
             }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/kotlin/de/uapcore/lightpit/types/IssueHistoryType.kt	Thu Aug 19 17:20:43 2021 +0200
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2021 Mike Becker. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package de.uapcore.lightpit.types
+
+enum class IssueHistoryType {
+    New,
+    Update,
+    NewComment,
+    UpdateComment
+}

mercurial