migrate entities package

Fri, 23 Oct 2020 20:34:57 +0200

author
Mike Becker <universe@uap-core.de>
date
Fri, 23 Oct 2020 20:34:57 +0200
changeset 150
822b7e3d064d
parent 149
30b840ed8c0e
child 151
b3f14cd4f3ab

migrate entities package

src/main/java/de/uapcore/lightpit/dao/Functions.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/dao/IssueDao.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/dao/postgres/PGIssueDao.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/dao/postgres/PGUserDao.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/entities/Component.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/entities/Issue.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/entities/IssueCategory.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/entities/IssueComment.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/entities/IssueStatus.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/entities/IssueSummary.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/entities/Project.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/entities/User.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/entities/Version.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/entities/VersionStatistics.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/entities/VersionStatus.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/modules/ProjectsModule.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/viewmodel/util/IssueSorter.java file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/entities/Component.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/IssueComment.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/entities/Project.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/entities/Version.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/types/WebColor.kt file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/jsp/issue-view.jsp file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/jspf/issue-list.jspf file | annotate | diff | comparison | revisions
--- a/src/main/java/de/uapcore/lightpit/dao/Functions.java	Fri Oct 23 18:40:50 2020 +0200
+++ b/src/main/java/de/uapcore/lightpit/dao/Functions.java	Fri Oct 23 20:34:57 2020 +0200
@@ -39,6 +39,10 @@
  */
 public final class Functions {
 
+    public static String getSafeString(ResultSet rs, String column) throws SQLException {
+        return Optional.ofNullable(rs.getString(column)).orElse("");
+    }
+
     public static void setStringOrNull(PreparedStatement stmt, int index, String str) throws SQLException {
         if (str == null || str.isBlank()) {
             stmt.setNull(index, Types.VARCHAR);
--- a/src/main/java/de/uapcore/lightpit/dao/IssueDao.java	Fri Oct 23 18:40:50 2020 +0200
+++ b/src/main/java/de/uapcore/lightpit/dao/IssueDao.java	Fri Oct 23 20:34:57 2020 +0200
@@ -83,10 +83,11 @@
      * Stores the specified comment in database.
      * This is an update-or-insert operation.
      *
+     * @param issue the issue to save the comment for
      * @param comment the comment to save
      * @throws SQLException on any kind of SQL error
      */
-    void saveComment(IssueComment comment) throws SQLException;
+    void saveComment(Issue issue, IssueComment comment) throws SQLException;
 
     /**
      * Saves an instances to the database.
--- a/src/main/java/de/uapcore/lightpit/dao/postgres/PGIssueDao.java	Fri Oct 23 18:40:50 2020 +0200
+++ b/src/main/java/de/uapcore/lightpit/dao/postgres/PGIssueDao.java	Fri Oct 23 20:34:57 2020 +0200
@@ -275,7 +275,7 @@
         List<IssueComment> comments = new ArrayList<>();
         try (var result = listComments.executeQuery()) {
             while (result.next()) {
-                final var comment = new IssueComment(result.getInt("commentid"), issue);
+                final var comment = new IssueComment(result.getInt("commentid"));
                 comment.setCreated(result.getTimestamp("created"));
                 comment.setUpdated(result.getTimestamp("updated"));
                 comment.setUpdateCount(result.getInt("updatecount"));
@@ -288,15 +288,13 @@
     }
 
     @Override
-    public void saveComment(IssueComment comment) throws SQLException {
-        Objects.requireNonNull(comment.getComment());
-        Objects.requireNonNull(comment.getIssue());
+    public void saveComment(Issue issue, IssueComment comment) throws SQLException {
         if (comment.getId() >= 0) {
             updateComment.setString(1, comment.getComment());
             updateComment.setInt(2, comment.getId());
             updateComment.execute();
         } else {
-            insertComment.setInt(1, comment.getIssue().getId());
+            insertComment.setInt(1, issue.getId());
             insertComment.setString(2, comment.getComment());
             setForeignKeyOrNull(insertComment, 3, comment.getAuthor(), User::getId);
             insertComment.execute();
--- a/src/main/java/de/uapcore/lightpit/dao/postgres/PGUserDao.java	Fri Oct 23 18:40:50 2020 +0200
+++ b/src/main/java/de/uapcore/lightpit/dao/postgres/PGUserDao.java	Fri Oct 23 20:34:57 2020 +0200
@@ -40,6 +40,7 @@
 import java.util.Objects;
 import java.util.Optional;
 
+import static de.uapcore.lightpit.dao.Functions.getSafeString;
 import static de.uapcore.lightpit.dao.Functions.setStringOrNull;
 
 public final class PGUserDao implements UserDao {
@@ -68,9 +69,9 @@
         if (id == 0) return null;
         final var user = new User(id);
         user.setUsername(result.getString("username"));
-        user.setGivenname(result.getString("givenname"));
-        user.setLastname(result.getString("lastname"));
-        user.setMail(result.getString("mail"));
+        user.setGivenname(getSafeString(result, "givenname"));
+        user.setLastname(getSafeString(result, "lastname"));
+        user.setMail(getSafeString(result, "mail"));
         return user;
     }
 
--- a/src/main/java/de/uapcore/lightpit/entities/Component.java	Fri Oct 23 18:40:50 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,123 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2020 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.entities;
-
-import de.uapcore.lightpit.types.WebColor;
-
-import java.util.Objects;
-
-public final class Component {
-
-    private final int id;
-
-    private String name;
-
-    private String node;
-
-    private WebColor color = new WebColor("000000");
-
-    private int ordinal = 0;
-
-    private String description = null;
-
-    private User lead = null;
-
-    /**
-     * Sole constructor.
-     * @param id the ID of the component
-     */
-    public Component(int id) {
-        this.id = id;
-    }
-
-    public int getId() {
-        return id;
-    }
-
-    public String getName() {
-        return name;
-    }
-
-    public void setName(String name) {
-        this.name = name;
-    }
-
-    public String getNode() {
-        return node;
-    }
-
-    public void setNode(String node) {
-        this.node = node;
-    }
-
-    public WebColor getColor() {
-        return color;
-    }
-
-    public void setColor(WebColor color) {
-        this.color = color;
-    }
-
-    public int getOrdinal() {
-        return ordinal;
-    }
-
-    public void setOrdinal(int ordinal) {
-        this.ordinal = ordinal;
-    }
-
-    public String getDescription() {
-        return description;
-    }
-
-    public void setDescription(String description) {
-        this.description = description;
-    }
-
-    public User getLead() {
-        return lead;
-    }
-
-    public void setLead(User lead) {
-        this.lead = lead;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-        Component component = (Component) o;
-        return id == component.id;
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(id);
-    }
-}
--- a/src/main/java/de/uapcore/lightpit/entities/Issue.java	Fri Oct 23 18:40:50 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,195 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2018 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.entities;
-
-import java.sql.Date;
-import java.sql.Timestamp;
-import java.time.Instant;
-import java.util.Collections;
-import java.util.List;
-import java.util.Objects;
-
-public final class Issue {
-
-    private int id;
-    private Project project;
-    private Component component;
-
-    private IssueStatus status;
-    private IssueCategory category;
-
-    private String subject;
-    private String description;
-    private User assignee;
-
-    private List<Version> affectedVersions = Collections.emptyList();
-    private List<Version> resolvedVersions = Collections.emptyList();
-
-    private Timestamp created = Timestamp.from(Instant.now());
-    private Timestamp updated = Timestamp.from(Instant.now());
-    private Date eta;
-
-    public Issue(int id) {
-        this.id = id;
-    }
-
-    public int getId() {
-        return id;
-    }
-
-    /**
-     * Should only be used by a DAO to store the generated ID.
-     * @param id the freshly generated ID returned from the database after insert
-     */
-    public void setId(int id) {
-        this.id = id;
-    }
-
-    public void setProject(Project project) {
-        this.project = project;
-    }
-
-    public Project getProject() {
-        return project;
-    }
-
-    public Component getComponent() {
-        return component;
-    }
-
-    public void setComponent(Component component) {
-        this.component = component;
-    }
-
-    public IssueStatus getStatus() {
-        return status;
-    }
-
-    public void setStatus(IssueStatus status) {
-        this.status = status;
-    }
-
-    public int getPhase() {
-        return this.status.getPhase();
-    }
-
-    public IssueCategory getCategory() {
-        return category;
-    }
-
-    public void setCategory(IssueCategory category) {
-        this.category = category;
-    }
-
-    public String getSubject() {
-        return subject;
-    }
-
-    public void setSubject(String subject) {
-        this.subject = subject;
-    }
-
-    public String getDescription() {
-        return description;
-    }
-
-    public void setDescription(String description) {
-        this.description = description;
-    }
-
-    public User getAssignee() {
-        return assignee;
-    }
-
-    public void setAssignee(User assignee) {
-        this.assignee = assignee;
-    }
-
-    public List<Version> getAffectedVersions() {
-        return affectedVersions;
-    }
-
-    public void setAffectedVersions(List<Version> affectedVersions) {
-        this.affectedVersions = affectedVersions;
-    }
-
-    public List<Version> getResolvedVersions() {
-        return resolvedVersions;
-    }
-
-    public void setResolvedVersions(List<Version> resolvedVersions) {
-        this.resolvedVersions = resolvedVersions;
-    }
-
-    public Timestamp getCreated() {
-        return created;
-    }
-
-    public void setCreated(Timestamp created) {
-        this.created = created;
-    }
-
-    public Timestamp getUpdated() {
-        return updated;
-    }
-
-    public void setUpdated(Timestamp updated) {
-        this.updated = updated;
-    }
-
-    public Date getEta() {
-        return eta;
-    }
-
-    public void setEta(Date eta) {
-        this.eta = eta;
-    }
-
-    /**
-     * An issue is overdue, if it is not done and the ETA is before the current time.
-     * @return true if this issue is overdue, false otherwise
-     */
-    public boolean isOverdue() {
-        return eta != null && status.getPhase() != IssueStatus.PHASE_DONE
-                && eta.before(new Date(System.currentTimeMillis()));
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-        Issue issue = (Issue) o;
-        return id == issue.id;
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(id);
-    }
-}
--- a/src/main/java/de/uapcore/lightpit/entities/IssueCategory.java	Fri Oct 23 18:40:50 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,37 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2018 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.entities;
-
-public enum IssueCategory {
-    Feature,
-    Improvement,
-    Bug,
-    Task,
-    Test
-}
--- a/src/main/java/de/uapcore/lightpit/entities/IssueComment.java	Fri Oct 23 18:40:50 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,85 +0,0 @@
-package de.uapcore.lightpit.entities;
-
-import java.sql.Timestamp;
-import java.time.Instant;
-import java.util.Objects;
-
-public class IssueComment {
-
-    private final Issue issue;
-    private final int commentid;
-
-    private User author;
-    private String comment;
-
-    private Timestamp created = Timestamp.from(Instant.now());
-    private Timestamp updated = Timestamp.from(Instant.now());
-    private int updatecount = 0;
-
-
-    public IssueComment(int id, Issue issue) {
-        this.commentid = id;
-        this.issue = issue;
-    }
-
-    public Issue getIssue() {
-        return issue;
-    }
-
-    public int getId() {
-        return commentid;
-    }
-
-    public User getAuthor() {
-        return author;
-    }
-
-    public void setAuthor(User author) {
-        this.author = author;
-    }
-
-    public String getComment() {
-        return comment;
-    }
-
-    public void setComment(String comment) {
-        this.comment = comment;
-    }
-
-    public Timestamp getCreated() {
-        return created;
-    }
-
-    public void setCreated(Timestamp created) {
-        this.created = created;
-    }
-
-    public Timestamp getUpdated() {
-        return updated;
-    }
-
-    public void setUpdated(Timestamp updated) {
-        this.updated = updated;
-    }
-
-    public int getUpdateCount() {
-        return updatecount;
-    }
-
-    public void setUpdateCount(int updatecount) {
-        this.updatecount = updatecount;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-        IssueComment that = (IssueComment) o;
-        return commentid == that.commentid;
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(commentid);
-    }
-}
--- a/src/main/java/de/uapcore/lightpit/entities/IssueStatus.java	Fri Oct 23 18:40:50 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,59 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2018 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.entities;
-
-public enum IssueStatus {
-    InSpecification(0),
-    ToDo(0),
-    Scheduled(0),
-    InProgress(1),
-    InReview(1),
-    Done(2),
-    Rejected(2),
-    Withdrawn(2),
-    Duplicate(2);
-
-    public static final int PHASE_OPEN = 0;
-    public static final int PHASE_WIP = 1;
-    public static final int PHASE_DONE = 2;
-
-    private int phase;
-
-    IssueStatus(int phase) {
-        this.phase = phase;
-    }
-
-    public int getPhase() {
-        return phase;
-    }
-
-    public static int phaseCount() {
-        return 3;
-    }
-}
--- a/src/main/java/de/uapcore/lightpit/entities/IssueSummary.java	Fri Oct 23 18:40:50 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,67 +0,0 @@
-package de.uapcore.lightpit.entities;
-
-public class IssueSummary {
-    private int open = 0;
-    private int active = 0;
-    private int done = 0;
-
-    public int getOpen() {
-        return open;
-    }
-
-    public void setOpen(int open) {
-        this.open = open;
-    }
-
-    public int getActive() {
-        return active;
-    }
-
-    public void setActive(int active) {
-        this.active = active;
-    }
-
-    public int getDone() {
-        return done;
-    }
-
-    public void setDone(int done) {
-        this.done = done;
-    }
-
-    public int getTotal() {
-        return open+active+done;
-    }
-
-    public int getOpenPercent() {
-        return 100-getActivePercent()-getDonePercent();
-    }
-
-    public int getActivePercent() {
-        int total = getTotal();
-        return total > 0 ? Math.round(100.f*active/total) : 0;
-    }
-
-    public int getDonePercent() {
-        int total = getTotal();
-        return total > 0 ? Math.round(100.f*done/total) : 100;
-    }
-
-    /**
-     * Adds the specified issue to the summary by increming the respective counter.
-     * @param issue the issue
-     */
-    public void add(Issue issue) {
-        switch (issue.getStatus().getPhase()) {
-            case 0:
-                open++;
-                break;
-            case 1:
-                active++;
-                break;
-            case 2:
-                done++;
-                break;
-        }
-    }
-}
--- a/src/main/java/de/uapcore/lightpit/entities/Project.java	Fri Oct 23 18:40:50 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,102 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2018 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.entities;
-
-import java.util.Objects;
-
-public class Project {
-
-    private final int id;
-    private String name;
-    private String node;
-    private String description;
-    private String repoUrl;
-    private User owner;
-
-    public Project(int id) {
-        this.id = id;
-    }
-
-    public int getId() {
-        return id;
-    }
-
-    public String getName() {
-        return name;
-    }
-
-    public void setName(String name) {
-        this.name = name;
-    }
-
-    public String getNode() {
-        return node;
-    }
-
-    public void setNode(String node) {
-        this.node = node;
-    }
-
-    public String getDescription() {
-        return description;
-    }
-
-    public void setDescription(String description) {
-        this.description = description;
-    }
-
-    public String getRepoUrl() {
-        return repoUrl;
-    }
-
-    public void setRepoUrl(String repoUrl) {
-        this.repoUrl = repoUrl;
-    }
-
-    public User getOwner() {
-        return owner;
-    }
-
-    public void setOwner(User owner) {
-        this.owner = owner;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-        Project project = (Project) o;
-        return id == project.id;
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(id);
-    }
-}
--- a/src/main/java/de/uapcore/lightpit/entities/User.java	Fri Oct 23 18:40:50 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,115 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2018 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.entities;
-
-import java.util.Objects;
-
-public final class User {
-
-    public static final int ANONYMOUS_USERID = -1;
-
-    private final int id;
-    private String username;
-    private String mail;
-    private String givenname;
-    private String lastname;
-
-    public User(int id) {
-        this.id = id;
-    }
-
-    public int getId() {
-        return id;
-    }
-
-    public String getUsername() {
-        return username;
-    }
-
-    public void setUsername(String username) {
-        this.username = username;
-    }
-
-    public String getMail() {
-        return mail;
-    }
-
-    public void setMail(String mail) {
-        this.mail = mail;
-    }
-
-    public String getGivenname() {
-        return givenname;
-    }
-
-    public void setGivenname(String givenname) {
-        this.givenname = givenname;
-    }
-
-    public String getLastname() {
-        return lastname;
-    }
-
-    public void setLastname(String lastname) {
-        this.lastname = lastname;
-    }
-
-    public String getShortDisplayname() {
-        StringBuilder dn = new StringBuilder();
-        if (givenname != null)
-            dn.append(givenname);
-        dn.append(' ');
-        if (lastname != null)
-            dn.append(lastname);
-        final var str = dn.toString().trim();
-        return str.isBlank() ? username : str;
-    }
-
-    public String getDisplayname() {
-        final String sdn = getShortDisplayname();
-        if (mail != null && !mail.isBlank()) {
-            return sdn + " <" + mail + ">";
-        } else {
-            return sdn;
-        }
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-        User user = (User) o;
-        return id == user.id;
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(id);
-    }
-}
--- a/src/main/java/de/uapcore/lightpit/entities/Version.java	Fri Oct 23 18:40:50 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,106 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2018 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.entities;
-
-import java.util.Objects;
-
-public final class Version implements Comparable<Version> {
-
-    private final int id;
-    private String name;
-    private String node;
-    /**
-     * If we do not want versions to be ordered lexicographically we may specify an order.
-     */
-    private int ordinal = 0;
-    private VersionStatus status = VersionStatus.Future;
-
-    public Version(int id) {
-        this.id = id;
-    }
-
-    public int getId() {
-        return id;
-    }
-
-    public String getName() {
-        return name;
-    }
-
-    public void setName(String name) {
-        this.name = name;
-    }
-
-    public String getNode() {
-        return node;
-    }
-
-    public void setNode(String node) {
-        this.node = node;
-    }
-
-    public int getOrdinal() {
-        return ordinal;
-    }
-
-    public void setOrdinal(int ordinal) {
-        this.ordinal = ordinal;
-    }
-
-    public VersionStatus getStatus() {
-        return status;
-    }
-
-    public void setStatus(VersionStatus status) {
-        this.status = status;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-        Version version = (Version) o;
-        return id == version.id;
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(id);
-    }
-
-    @Override
-    public int compareTo(Version version) {
-        int ord = Integer.compare(this.ordinal, version.ordinal);
-        if (ord == 0) {
-            return this.name.compareToIgnoreCase(version.name);
-        } else {
-            return ord;
-        }
-    }
-}
--- a/src/main/java/de/uapcore/lightpit/entities/VersionStatistics.java	Fri Oct 23 18:40:50 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,101 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2018 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.entities;
-
-public class VersionStatistics {
-
-    private final Version version;
-    private int[][] issueCount;
-
-    private int[] rowTotals = null;
-    private int[] columnTotals = null;
-    private int total = -1;
-
-    public VersionStatistics(Version version) {
-        this.version = version;
-        issueCount = new int[IssueCategory.values().length][IssueStatus.values().length];
-    }
-
-    public Version getVersion() {
-        return version;
-    }
-
-    public void setIssueCount(IssueCategory category, IssueStatus status, int count) {
-        issueCount[category.ordinal()][status.ordinal()] = count;
-        total = -1;
-        rowTotals = columnTotals = null;
-    }
-
-    public int[][] getIssueCount() {
-        return issueCount;
-    }
-
-    public int[] getRowTotals() {
-        if (rowTotals != null) return rowTotals;
-        final int cn = IssueCategory.values().length;
-        final int sn = IssueStatus.values().length;
-        final var totals = new int[cn];
-        for (int i = 0 ; i < cn ; i++) {
-            totals[i] = 0;
-            for (int j = 0 ; j < sn ; j++) {
-                totals[i] += issueCount[i][j];
-            }
-        }
-        return rowTotals = totals;
-    }
-
-    public int[] getColumnTotals() {
-        if (columnTotals != null) return columnTotals;
-        final int cn = IssueCategory.values().length;
-        final int sn = IssueStatus.values().length;
-        final var totals = new int[sn];
-        for (int i = 0 ; i < sn ; i++) {
-            totals[i] = 0;
-            for (int j = 0 ; j < cn ; j++) {
-                totals[i] += issueCount[j][i];
-            }
-        }
-        return columnTotals = totals;
-    }
-
-    public int getTotal() {
-        if (this.total >= 0) {
-            return this.total;
-        }
-        int total = 0;
-        final int cn = IssueCategory.values().length;
-        final int sn = IssueStatus.values().length;
-        for (int i = 0 ; i < sn ; i++) {
-            for (int j = 0 ; j < cn ; j++) {
-                total += issueCount[j][i];
-            }
-        }
-        return this.total = total;
-    }
-}
--- a/src/main/java/de/uapcore/lightpit/entities/VersionStatus.java	Fri Oct 23 18:40:50 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,41 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2018 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.entities;
-
-public enum VersionStatus {
-    Future,
-    Unreleased,
-    Released,
-    LTS,
-    Deprecated;
-
-    public boolean isReleased() {
-        return ordinal() >= VersionStatus.Released.ordinal();
-    }
-}
--- a/src/main/java/de/uapcore/lightpit/modules/ProjectsModule.java	Fri Oct 23 18:40:50 2020 +0200
+++ b/src/main/java/de/uapcore/lightpit/modules/ProjectsModule.java	Fri Oct 23 20:34:57 2020 +0200
@@ -570,7 +570,7 @@
             return ResponseType.NONE;
         }
         try {
-            final var issueComment = new IssueComment(getParameter(req, Integer.class, "commentid").orElse(-1), issue);
+            final var issueComment = new IssueComment(getParameter(req, Integer.class, "commentid").orElse(-1));
             issueComment.setComment(getParameter(req, String.class, "comment").orElse(""));
 
             if (issueComment.getComment().isBlank()) {
@@ -582,7 +582,7 @@
                 dao.getUserDao().findByUsername(req.getRemoteUser()).ifPresent(issueComment::setAuthor);
             }
 
-            dao.getIssueDao().saveComment(issueComment);
+            dao.getIssueDao().saveComment(issue, issueComment);
 
             // TODO: fix redirect location
             setRedirectLocation(req, "./projects/" + issue.getProject().getNode()+"/issues/"+issue.getId()+"/view");
--- a/src/main/java/de/uapcore/lightpit/viewmodel/util/IssueSorter.java	Fri Oct 23 18:40:50 2020 +0200
+++ b/src/main/java/de/uapcore/lightpit/viewmodel/util/IssueSorter.java	Fri Oct 23 20:34:57 2020 +0200
@@ -1,7 +1,7 @@
 package de.uapcore.lightpit.viewmodel.util;
 
 import de.uapcore.lightpit.entities.Issue;
-import de.uapcore.lightpit.entities.IssueStatus;
+import de.uapcore.lightpit.entities.IssueStatusPhase;
 
 import java.util.Arrays;
 import java.util.Comparator;
@@ -41,8 +41,8 @@
         switch (criteria.field) {
             case DONE:
                 result = Boolean.compare(
-                        left.getPhase() == IssueStatus.PHASE_DONE,
-                        right.getPhase() == IssueStatus.PHASE_DONE);
+                        left.getStatus().getPhase().equals(IssueStatusPhase.Companion.getDone()),
+                        right.getStatus().getPhase().equals(IssueStatusPhase.Companion.getDone()));
                 break;
             case ETA:
                 if (left.getEta() != null && right.getEta() != null)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/kotlin/de/uapcore/lightpit/entities/Component.kt	Fri Oct 23 20:34:57 2020 +0200
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2020 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.entities
+
+import de.uapcore.lightpit.types.WebColor
+
+data class Component(val id: Int) {
+    var name: String? = null
+    var node: String? = null
+    var color = WebColor("000000")
+    var ordinal = 0
+    var description: String? = null
+    var lead: User? = null
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/kotlin/de/uapcore/lightpit/entities/Issue.kt	Fri Oct 23 20:34:57 2020 +0200
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2020 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.entities
+
+import java.sql.Date
+import java.sql.Timestamp
+import java.time.Instant
+import kotlin.math.roundToInt
+
+data class IssueStatusPhase(val number: Int) {
+    companion object {
+        val Open = IssueStatusPhase(0)
+        val WorkInProgress = IssueStatusPhase(1)
+        val Done = IssueStatusPhase(2)
+    }
+}
+
+enum class IssueStatus(val phase: IssueStatusPhase) {
+    InSpecification(IssueStatusPhase.Open),
+    ToDo(IssueStatusPhase.Open),
+    Scheduled(IssueStatusPhase.Open),
+    InProgress(IssueStatusPhase.WorkInProgress),
+    InReview(IssueStatusPhase.WorkInProgress),
+    Done(IssueStatusPhase.Done),
+    Rejected(IssueStatusPhase.Done),
+    Withdrawn(IssueStatusPhase.Done),
+    Duplicate(IssueStatusPhase.Done);
+}
+
+enum class IssueCategory {
+    Feature, Improvement, Bug, Task, Test
+}
+
+data class Issue(var id: Int) {
+
+    var project: Project? = null
+    var component: Component? = null
+
+    var status = IssueStatus.InSpecification
+    var category = IssueCategory.Feature
+
+    var subject: String? = null
+    var description: String? = null
+    var assignee: User? = null
+
+    var affectedVersions = emptyList<Version>()
+    var resolvedVersions = emptyList<Version>()
+
+    var created: Timestamp = Timestamp.from(Instant.now())
+    var updated: Timestamp = Timestamp.from(Instant.now())
+    var eta: Date? = null
+
+    /**
+     * 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
+}
+
+class IssueSummary {
+    var open = 0
+    var active = 0
+    var done = 0
+
+    val total get() = open + active + done
+
+    val openPercent get() = 100 - activePercent - donePercent
+    val activePercent get() = if (total > 0) (100f * active / total).roundToInt() else 0
+    val donePercent get() = if (total > 0) (100f * done / total).roundToInt() else 100
+
+    /**
+     * Adds the specified issue to the summary by incrementing the respective counter.
+     * @param issue the issue
+     */
+    fun add(issue: Issue) {
+        when (issue.status.phase) {
+            IssueStatusPhase.Open -> open++
+            IssueStatusPhase.WorkInProgress -> active++
+            IssueStatusPhase.Done -> done++
+        }
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/kotlin/de/uapcore/lightpit/entities/IssueComment.kt	Fri Oct 23 20:34:57 2020 +0200
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2020 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.entities
+
+import java.sql.Timestamp
+import java.time.Instant
+
+data class IssueComment(val id: Int) {
+    var author: User? = null
+    var comment: String? = null
+    var created: Timestamp = Timestamp.from(Instant.now())
+    var updated: Timestamp = Timestamp.from(Instant.now())
+    var updateCount = 0
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/kotlin/de/uapcore/lightpit/entities/Project.kt	Fri Oct 23 20:34:57 2020 +0200
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2020 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.entities
+
+data class Project(val id: Int) {
+    var name: String? = null
+    var node: String? = null
+    var description: String? = null
+    var repoUrl: String? = null
+    var owner: User? = null
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/kotlin/de/uapcore/lightpit/entities/User.kt	Fri Oct 23 20:34:57 2020 +0200
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2020 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.entities
+
+data class User(val id: Int) {
+    var username = "anonymous"
+    var mail = ""
+    var givenname = ""
+    var lastname = ""
+
+    val shortDisplayname: String get() {
+        val str = "$givenname $lastname"
+        return if (str.isBlank()) username else str.trim()
+    }
+
+    val displayname: String get() = if (mail.isBlank()) shortDisplayname else "$shortDisplayname <$mail>"
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/kotlin/de/uapcore/lightpit/entities/Version.kt	Fri Oct 23 20:34:57 2020 +0200
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2020 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.entities
+
+enum class VersionStatus {
+    Future, Unreleased, Released, LTS, Deprecated;
+    val isReleased get() = this.ordinal >= Released.ordinal
+}
+
+data class Version(val id: Int) : Comparable<Version> {
+    var name: String = "unspecified"
+    var node = name
+    var ordinal = 0
+    var status = VersionStatus.Future
+
+    override fun compareTo(other: Version): Int {
+        val ord = Integer.compare(ordinal, other.ordinal)
+        return if (ord == 0) {
+            name.compareTo(other.name, ignoreCase = true)
+        } else {
+            ord
+        }
+    }
+}
--- a/src/main/kotlin/de/uapcore/lightpit/types/WebColor.kt	Fri Oct 23 18:40:50 2020 +0200
+++ b/src/main/kotlin/de/uapcore/lightpit/types/WebColor.kt	Fri Oct 23 20:34:57 2020 +0200
@@ -1,3 +1,28 @@
+/*
+ * Copyright 2020 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
 
 
--- a/src/main/webapp/WEB-INF/jsp/issue-view.jsp	Fri Oct 23 18:40:50 2020 +0200
+++ b/src/main/webapp/WEB-INF/jsp/issue-view.jsp	Fri Oct 23 20:34:57 2020 +0200
@@ -79,7 +79,7 @@
     <tr>
         <th><fmt:message key="issue.status"/></th>
         <td>
-            <div class="issue-tag phase-${issue.status.phase}" style="width: auto">
+            <div class="issue-tag phase-${issue.status.phase.number}" style="width: auto">
                 <fmt:message key="issue.status.${issue.status}" />
             </div>
         </td>
--- a/src/main/webapp/WEB-INF/jspf/issue-list.jspf	Fri Oct 23 18:40:50 2020 +0200
+++ b/src/main/webapp/WEB-INF/jspf/issue-list.jspf	Fri Oct 23 20:34:57 2020 +0200
@@ -16,7 +16,7 @@
     <c:forEach var="issue" items="${issues}">
         <tr>
             <td>
-                <span class="phase-${issue.status.phase}">
+                <span class="phase-${issue.status.phase.number}">
                     <a href="./projects/${issue.project.node}/issues/${issue.id}/view">
                         #${issue.id}&nbsp;-&nbsp;<c:out value="${issue.subject}" />
                     </a>
@@ -25,13 +25,13 @@
                 <div class="issue-tag ${issue.category}">
                     <fmt:message key="issue.category.${issue.category}" />
                 </div>
-                <div class="issue-tag phase-${issue.status.phase}">
+                <div class="issue-tag phase-${issue.status.phase.number}">
                     <fmt:message key="issue.status.${issue.status}" />
                 </div>
             </td>
             <td>
                 <span class="<c:if test="${issue.overdue}">eta-overdue</c:if> ">
-                        <fmt:formatDate value="${issue.eta}" />
+                    <fmt:formatDate value="${issue.eta}" />
                 </span>
             </td>
         </tr>

mercurial