#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
     1.1 --- a/setup/postgres/psql_create_tables.sql	Thu Aug 19 14:51:04 2021 +0200
     1.2 +++ b/setup/postgres/psql_create_tables.sql	Thu Aug 19 17:20:43 2021 +0200
     1.3 @@ -101,6 +101,35 @@
     1.4      resolved    integer references lpit_version (versionid)
     1.5  );
     1.6  
     1.7 +create type issue_history_event as enum (
     1.8 +    'New',
     1.9 +    'Update',
    1.10 +    'NewComment',
    1.11 +    'UpdateComment'
    1.12 +    );
    1.13 +
    1.14 +create table lpit_issue_history_event
    1.15 +(
    1.16 +    eventid serial primary key,
    1.17 +    issueid integer                  not null references lpit_issue (issueid) on delete cascade,
    1.18 +    time    timestamp with time zone not null default now(),
    1.19 +    type    issue_history_event      not null
    1.20 +);
    1.21 +
    1.22 +create table lpit_issue_history_data
    1.23 +(
    1.24 +    eventid     integer        not null references lpit_issue_history_event (eventid) on delete cascade,
    1.25 +    component   text,
    1.26 +    status      issue_status   not null,
    1.27 +    category    issue_category not null,
    1.28 +    subject     text           not null,
    1.29 +    description text,
    1.30 +    assignee    text,
    1.31 +    eta         date,
    1.32 +    affected    text,
    1.33 +    resolved    text
    1.34 +);
    1.35 +
    1.36  create table lpit_issue_comment
    1.37  (
    1.38      commentid   serial primary key,
    1.39 @@ -111,3 +140,11 @@
    1.40      updatecount integer                  not null default 0,
    1.41      comment     text                     not null
    1.42  );
    1.43 +
    1.44 +create table lpit_issue_comment_history
    1.45 +(
    1.46 +    commentid integer not null references lpit_issue_comment (commentid) on delete cascade,
    1.47 +    eventid   integer not null references lpit_issue_history_event (eventid) on delete cascade,
    1.48 +    comment   text    not null
    1.49 +);
    1.50 +
     2.1 --- a/src/main/kotlin/de/uapcore/lightpit/dao/DataAccessObject.kt	Thu Aug 19 14:51:04 2021 +0200
     2.2 +++ b/src/main/kotlin/de/uapcore/lightpit/dao/DataAccessObject.kt	Thu Aug 19 17:20:43 2021 +0200
     2.3 @@ -76,6 +76,9 @@
     2.4  
     2.5      fun listComments(issue: Issue): List<IssueComment>
     2.6      fun findComment(id: Int): IssueComment?
     2.7 -    fun insertComment(issueComment: IssueComment)
     2.8 +    fun insertComment(issueComment: IssueComment): Int
     2.9      fun updateComment(issueComment: IssueComment)
    2.10 -}
    2.11 \ No newline at end of file
    2.12 +
    2.13 +    fun insertHistoryEvent(issue: Issue, newId: Int = 0)
    2.14 +    fun insertHistoryEvent(issueComment: IssueComment, newId: Int = 0)
    2.15 +}
     3.1 --- a/src/main/kotlin/de/uapcore/lightpit/dao/PostgresDataAccessObject.kt	Thu Aug 19 14:51:04 2021 +0200
     3.2 +++ b/src/main/kotlin/de/uapcore/lightpit/dao/PostgresDataAccessObject.kt	Thu Aug 19 17:20:43 2021 +0200
     3.3 @@ -26,6 +26,7 @@
     3.4  package de.uapcore.lightpit.dao
     3.5  
     3.6  import de.uapcore.lightpit.entities.*
     3.7 +import de.uapcore.lightpit.types.IssueHistoryType
     3.8  import de.uapcore.lightpit.types.WebColor
     3.9  import de.uapcore.lightpit.util.*
    3.10  import de.uapcore.lightpit.viewmodel.ComponentSummary
    3.11 @@ -565,7 +566,7 @@
    3.12          val id = withStatement(
    3.13              """
    3.14              insert into lpit_issue (component, status, category, subject, description, assignee, eta, affected, resolved, project)
    3.15 -            values (?, ?::issue_status, ?::issue_category, ?, ?, ?, ?, ?)
    3.16 +            values (?, ?::issue_status, ?::issue_category, ?, ?, ?, ?, ?, ?, ?)
    3.17              returning issueid
    3.18              """.trimIndent()
    3.19          ) {
    3.20 @@ -591,6 +592,36 @@
    3.21          }
    3.22      }
    3.23  
    3.24 +    override fun insertHistoryEvent(issue: Issue, newId: Int) {
    3.25 +        val type = if (newId > 0) IssueHistoryType.New else IssueHistoryType.Update
    3.26 +        val issueid = if (newId > 0) newId else issue.id
    3.27 +
    3.28 +        val eventid =
    3.29 +            withStatement("insert into lpit_issue_history_event(issueid, type) values (?,?::issue_history_event) returning eventid") {
    3.30 +                setInt(1, issueid)
    3.31 +                setEnum(2, type)
    3.32 +                querySingle { it.getInt(1) }!!
    3.33 +            }
    3.34 +        withStatement(
    3.35 +            """
    3.36 +            insert into lpit_issue_history_data (component, status, category, subject, description, assignee, eta, affected, resolved, eventid)
    3.37 +            values (?, ?::issue_status, ?::issue_category, ?, ?, ?, ?, ?, ?, ?)
    3.38 +            """.trimIndent()
    3.39 +        ) {
    3.40 +            setStringOrNull(1, issue.component?.name)
    3.41 +            setEnum(2, issue.status)
    3.42 +            setEnum(3, issue.category)
    3.43 +            setString(4, issue.subject)
    3.44 +            setStringOrNull(5, issue.description)
    3.45 +            setStringOrNull(6, issue.assignee?.shortDisplayname)
    3.46 +            setDateOrNull(7, issue.eta)
    3.47 +            setStringOrNull(8, issue.affected?.name)
    3.48 +            setStringOrNull(9, issue.resolved?.name)
    3.49 +            setInt(10, eventid)
    3.50 +            executeUpdate()
    3.51 +        }
    3.52 +    }
    3.53 +
    3.54      //</editor-fold>
    3.55  
    3.56      //<editor-fold desc="IssueComment">
    3.57 @@ -616,20 +647,20 @@
    3.58              querySingle { it.extractIssueComment() }
    3.59          }
    3.60  
    3.61 -    override fun insertComment(issueComment: IssueComment) {
    3.62 +    override fun insertComment(issueComment: IssueComment): Int =
    3.63          useStatement("update lpit_issue set updated = now() where issueid = ?") { updateIssueDate ->
    3.64 -            withStatement("insert into lpit_issue_comment (issueid, comment, userid) values (?, ? ,?)") {
    3.65 +            withStatement("insert into lpit_issue_comment (issueid, comment, userid) values (?, ? ,?) returning commentid") {
    3.66                  with(issueComment) {
    3.67                      updateIssueDate.setInt(1, issueid)
    3.68                      setInt(1, issueid)
    3.69                      setStringSafe(2, comment)
    3.70                      setIntOrNull(3, author?.id)
    3.71                  }
    3.72 -                executeUpdate()
    3.73 +                val commentid = querySingle { it.getInt(1) }!!
    3.74                  updateIssueDate.executeUpdate()
    3.75 +                commentid
    3.76              }
    3.77          }
    3.78 -    }
    3.79  
    3.80      override fun updateComment(issueComment: IssueComment) {
    3.81          useStatement("update lpit_issue set updated = now() where issueid = ?") { updateIssueDate ->
    3.82 @@ -644,5 +675,25 @@
    3.83              }
    3.84          }
    3.85      }
    3.86 +
    3.87 +
    3.88 +    override fun insertHistoryEvent(issueComment: IssueComment, newId: Int) {
    3.89 +        val type = if (newId > 0) IssueHistoryType.NewComment else IssueHistoryType.UpdateComment
    3.90 +        val commentid = if (newId > 0) newId else issueComment.id
    3.91 +
    3.92 +        val eventid =
    3.93 +            withStatement("insert into lpit_issue_history_event(issueid, type) values (?,?::issue_history_event) returning eventid") {
    3.94 +                setInt(1, issueComment.issueid)
    3.95 +                setEnum(2, type)
    3.96 +                querySingle { it.getInt(1) }!!
    3.97 +            }
    3.98 +        withStatement("insert into lpit_issue_comment_history (commentid, eventid, comment) values (?,?,?)") {
    3.99 +            setInt(1, commentid)
   3.100 +            setInt(2, eventid)
   3.101 +            setString(3, issueComment.comment)
   3.102 +            executeUpdate()
   3.103 +        }
   3.104 +    }
   3.105 +
   3.106      //</editor-fold>
   3.107  }
   3.108 \ No newline at end of file
     4.1 --- a/src/main/kotlin/de/uapcore/lightpit/entities/Issue.kt	Thu Aug 19 14:51:04 2021 +0200
     4.2 +++ b/src/main/kotlin/de/uapcore/lightpit/entities/Issue.kt	Thu Aug 19 17:20:43 2021 +0200
     4.3 @@ -52,5 +52,14 @@
     4.4       * An issue is overdue, if it is not done and the ETA is before the current time.
     4.5       */
     4.6      val overdue get() = status.phase != IssueStatusPhase.Done && eta?.before(Date(System.currentTimeMillis())) ?: false
     4.7 +
     4.8 +    fun hasChanged(reference: Issue) = !(component == reference.component &&
     4.9 +            status == reference.status &&
    4.10 +            category == reference.category &&
    4.11 +            subject == reference.subject &&
    4.12 +            description == reference.description &&
    4.13 +            eta == reference.eta &&
    4.14 +            affected == reference.affected &&
    4.15 +            resolved == reference.resolved)
    4.16  }
    4.17  
     5.1 --- a/src/main/kotlin/de/uapcore/lightpit/entities/User.kt	Thu Aug 19 14:51:04 2021 +0200
     5.2 +++ b/src/main/kotlin/de/uapcore/lightpit/entities/User.kt	Thu Aug 19 17:20:43 2021 +0200
     5.3 @@ -31,11 +31,18 @@
     5.4      var givenname: String? = null
     5.5      var lastname: String? = null
     5.6  
     5.7 +    /**
     5.8 +     * The display name without mail address.
     5.9 +     */
    5.10      val shortDisplayname: String
    5.11          get() {
    5.12              val str = "${givenname ?: ""} ${lastname ?: ""}"
    5.13              return if (str.isBlank()) username else str.trim()
    5.14          }
    5.15  
    5.16 +    /**
    5.17 +     * Shows the full name plus mail address.
    5.18 +     * If neither given name nor lastname are provided, the username is used instead.
    5.19 +     */
    5.20      val displayname: String get() = if (mail.isNullOrBlank()) shortDisplayname else "$shortDisplayname <$mail>"
    5.21  }
    5.22 \ No newline at end of file
     6.1 --- a/src/main/kotlin/de/uapcore/lightpit/servlet/ProjectServlet.kt	Thu Aug 19 14:51:04 2021 +0200
     6.2 +++ b/src/main/kotlin/de/uapcore/lightpit/servlet/ProjectServlet.kt	Thu Aug 19 17:20:43 2021 +0200
     6.3 @@ -25,11 +25,8 @@
     6.4  
     6.5  package de.uapcore.lightpit.servlet
     6.6  
     6.7 -import de.uapcore.lightpit.AbstractServlet
     6.8 -import de.uapcore.lightpit.HttpRequest
     6.9 -import de.uapcore.lightpit.boolValidator
    6.10 +import de.uapcore.lightpit.*
    6.11  import de.uapcore.lightpit.dao.DataAccessObject
    6.12 -import de.uapcore.lightpit.dateOptValidator
    6.13  import de.uapcore.lightpit.entities.*
    6.14  import de.uapcore.lightpit.types.IssueCategory
    6.15  import de.uapcore.lightpit.types.IssueStatus
    6.16 @@ -524,10 +521,20 @@
    6.17              val commentId = http.param("commentid")?.toIntOrNull() ?: -1
    6.18              if (commentId > 0) {
    6.19                  val comment = dao.findComment(commentId)
    6.20 -                val originalAuthor = comment?.author?.username
    6.21 +                if (comment == null) {
    6.22 +                    http.response.sendError(404)
    6.23 +                    return
    6.24 +                }
    6.25 +                val originalAuthor = comment.author?.username
    6.26                  if (originalAuthor != null && originalAuthor == http.remoteUser) {
    6.27 -                    comment.comment = http.param("comment") ?: ""
    6.28 -                    dao.updateComment(comment)
    6.29 +                    val newComment = http.param("comment")
    6.30 +                    if (!newComment.isNullOrBlank()) {
    6.31 +                        comment.comment = newComment
    6.32 +                        dao.updateComment(comment)
    6.33 +                        dao.insertHistoryEvent(comment)
    6.34 +                    } else {
    6.35 +                        logger().debug("Not updating comment ${comment.id} because nothing changed.")
    6.36 +                    }
    6.37                  } else {
    6.38                      http.response.sendError(403)
    6.39                      return
    6.40 @@ -537,7 +544,8 @@
    6.41                      author = http.remoteUser?.let { dao.findUserByName(it) }
    6.42                      comment = http.param("comment") ?: ""
    6.43                  }
    6.44 -                dao.insertComment(comment)
    6.45 +                val newId = dao.insertComment(comment)
    6.46 +                dao.insertHistoryEvent(comment, newId)
    6.47              }
    6.48  
    6.49              http.renderCommit("${issuesHref}${issue.id}")
    6.50 @@ -570,15 +578,31 @@
    6.51              }
    6.52  
    6.53              val openId = if (issue.id < 0) {
    6.54 -                dao.insertIssue(issue)
    6.55 +                val id = dao.insertIssue(issue)
    6.56 +                dao.insertHistoryEvent(issue, id)
    6.57 +                id
    6.58              } else {
    6.59 -                dao.updateIssue(issue)
    6.60 +                val reference = dao.findIssue(issue.id)
    6.61 +                if (reference == null) {
    6.62 +                    http.response.sendError(404)
    6.63 +                    return
    6.64 +                }
    6.65 +
    6.66 +                if (issue.hasChanged(reference)) {
    6.67 +                    dao.updateIssue(issue)
    6.68 +                    dao.insertHistoryEvent(issue)
    6.69 +                } else {
    6.70 +                    logger().debug("Not updating issue ${issue.id} because nothing changed.")
    6.71 +                }
    6.72 +
    6.73                  val newComment = http.param("comment")
    6.74                  if (!newComment.isNullOrBlank()) {
    6.75 -                    dao.insertComment(IssueComment(-1, issue.id).apply {
    6.76 +                    val comment = IssueComment(-1, issue.id).apply {
    6.77                          author = http.remoteUser?.let { dao.findUserByName(it) }
    6.78                          comment = newComment
    6.79 -                    })
    6.80 +                    }
    6.81 +                    val commentid = dao.insertComment(comment)
    6.82 +                    dao.insertHistoryEvent(comment, commentid)
    6.83                  }
    6.84                  issue.id
    6.85              }
     7.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     7.2 +++ b/src/main/kotlin/de/uapcore/lightpit/types/IssueHistoryType.kt	Thu Aug 19 17:20:43 2021 +0200
     7.3 @@ -0,0 +1,33 @@
     7.4 +/*
     7.5 + * Copyright 2021 Mike Becker. All rights reserved.
     7.6 + *
     7.7 + * Redistribution and use in source and binary forms, with or without
     7.8 + * modification, are permitted provided that the following conditions are met:
     7.9 + *
    7.10 + * 1. Redistributions of source code must retain the above copyright
    7.11 + * notice, this list of conditions and the following disclaimer.
    7.12 + *
    7.13 + * 2. Redistributions in binary form must reproduce the above copyright
    7.14 + * notice, this list of conditions and the following disclaimer in the
    7.15 + * documentation and/or other materials provided with the distribution.
    7.16 + *
    7.17 + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    7.18 + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    7.19 + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
    7.20 + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
    7.21 + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
    7.22 + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
    7.23 + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
    7.24 + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
    7.25 + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
    7.26 + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    7.27 + */
    7.28 +
    7.29 +package de.uapcore.lightpit.types
    7.30 +
    7.31 +enum class IssueHistoryType {
    7.32 +    New,
    7.33 +    Update,
    7.34 +    NewComment,
    7.35 +    UpdateComment
    7.36 +}

mercurial