|
1 package de.uapcore.lightpit.logic |
|
2 |
|
3 import de.uapcore.lightpit.HttpRequest |
|
4 import de.uapcore.lightpit.dao.DataAccessObject |
|
5 import de.uapcore.lightpit.dateOptValidator |
|
6 import de.uapcore.lightpit.entities.Issue |
|
7 import de.uapcore.lightpit.entities.IssueComment |
|
8 import de.uapcore.lightpit.entities.IssueRelation |
|
9 import de.uapcore.lightpit.entities.Version |
|
10 import de.uapcore.lightpit.types.IssueCategory |
|
11 import de.uapcore.lightpit.types.IssueStatus |
|
12 import de.uapcore.lightpit.types.RelationType |
|
13 import de.uapcore.lightpit.viewmodel.IssueDetailView |
|
14 import de.uapcore.lightpit.viewmodel.PathInfos |
|
15 import de.uapcore.lightpit.viewmodel.PathInfosFull |
|
16 import de.uapcore.lightpit.viewmodel.projectNavMenu |
|
17 import java.sql.Date |
|
18 |
|
19 fun Issue.hasChanged(reference: Issue) = !(component == reference.component && |
|
20 status == reference.status && |
|
21 category == reference.category && |
|
22 subject == reference.subject && |
|
23 description == reference.description && |
|
24 assignee == reference.assignee && |
|
25 eta == reference.eta && |
|
26 affected == reference.affected && |
|
27 resolved == reference.resolved) |
|
28 |
|
29 fun Issue.compareEtaTo(date: Date?): Int { |
|
30 val eta = this.eta |
|
31 return if (eta == null && date == null) 0 |
|
32 else if (eta == null) 1 |
|
33 else if (date == null) -1 |
|
34 else eta.compareTo(date) |
|
35 } |
|
36 |
|
37 fun Issue.applyFormData(http: HttpRequest, dao: DataAccessObject): Issue = this.apply { |
|
38 component = dao.findComponent(http.param("component")?.toIntOrNull() ?: -1) |
|
39 category = IssueCategory.valueOf(http.param("category") ?: "") |
|
40 status = IssueStatus.valueOf(http.param("status") ?: "") |
|
41 subject = http.param("subject") ?: "" |
|
42 description = http.param("description") ?: "" |
|
43 assignee = http.param("assignee")?.toIntOrNull()?.let { |
|
44 when (it) { |
|
45 -1 -> null |
|
46 -2 -> (component?.lead ?: project.owner) |
|
47 else -> dao.findUser(it) |
|
48 } |
|
49 } |
|
50 // TODO: process error messages |
|
51 eta = http.param("eta", ::dateOptValidator, null, mutableListOf()) |
|
52 |
|
53 affected = http.param("affected")?.toIntOrNull()?.takeIf { it > 0 }?.let { Version(it, project.id) } |
|
54 resolved = http.param("resolved")?.toIntOrNull()?.takeIf { it > 0 }?.let { Version(it, project.id) } |
|
55 } |
|
56 |
|
57 fun processIssueForm(issue: Issue, reference: Issue, http: HttpRequest, dao: DataAccessObject) { |
|
58 if (issue.hasChanged(reference)) { |
|
59 dao.updateIssue(issue) |
|
60 dao.insertHistoryEvent(issue) |
|
61 } |
|
62 val newComment = http.param("comment") |
|
63 if (!newComment.isNullOrBlank()) { |
|
64 val comment = IssueComment(-1, issue.id).apply { |
|
65 author = http.remoteUser?.let { dao.findUserByName(it) } |
|
66 comment = newComment |
|
67 } |
|
68 val commentid = dao.insertComment(comment) |
|
69 dao.insertHistoryEvent(issue, comment, commentid) |
|
70 } |
|
71 } |
|
72 |
|
73 fun commitIssueComment(http: HttpRequest, dao: DataAccessObject, pathInfos: PathInfos) { |
|
74 val issue = http.pathParams["issue"]?.toIntOrNull()?.let(dao::findIssue) |
|
75 if (issue == null) { |
|
76 http.response.sendError(404) |
|
77 return |
|
78 } |
|
79 if (processIssueComment(issue, http, dao)) { |
|
80 http.renderCommit("${pathInfos.issuesHref}${issue.id}") |
|
81 } |
|
82 } |
|
83 |
|
84 fun processIssueComment(issue:Issue, http: HttpRequest, dao: DataAccessObject): Boolean { |
|
85 val commentId = http.param("commentid")?.toIntOrNull() ?: -1 |
|
86 if (commentId > 0) { |
|
87 val comment = dao.findComment(commentId) |
|
88 if (comment == null) { |
|
89 http.response.sendError(404) |
|
90 return false |
|
91 } |
|
92 val originalAuthor = comment.author?.username |
|
93 if (originalAuthor != null && originalAuthor == http.remoteUser) { |
|
94 val newComment = http.param("comment") |
|
95 if (!newComment.isNullOrBlank()) { |
|
96 comment.comment = newComment |
|
97 dao.updateComment(comment) |
|
98 dao.insertHistoryEvent(issue, comment) |
|
99 } |
|
100 } else { |
|
101 http.response.sendError(403) |
|
102 return false |
|
103 } |
|
104 } else { |
|
105 val comment = IssueComment(-1, issue.id).apply { |
|
106 author = http.remoteUser?.let { dao.findUserByName(it) } |
|
107 comment = http.param("comment") ?: "" |
|
108 } |
|
109 val newId = dao.insertComment(comment) |
|
110 dao.insertHistoryEvent(issue, comment, newId) |
|
111 } |
|
112 return true |
|
113 } |
|
114 |
|
115 fun renderIssueView( |
|
116 http: HttpRequest, |
|
117 dao: DataAccessObject, |
|
118 issue: Issue, |
|
119 pathInfos: PathInfos, |
|
120 relationError: String? = null |
|
121 ) { |
|
122 val comments = dao.listComments(issue) |
|
123 |
|
124 with(http) { |
|
125 pageTitle = "#${issue.id} ${issue.subject} (${issue.project.name})" |
|
126 view = IssueDetailView( |
|
127 issue, |
|
128 comments, |
|
129 dao.listIssues(issue.project, true), |
|
130 dao.listIssueRelations(issue), |
|
131 dao.listCommitRefs(issue), |
|
132 relationError, |
|
133 pathInfos |
|
134 ) |
|
135 if (pathInfos is PathInfosFull) { |
|
136 navigationMenu = projectNavMenu(dao.listProjects(), pathInfos) |
|
137 } |
|
138 styleSheets = listOf("projects") |
|
139 javascript = "issue-editor" |
|
140 render("issue-view") |
|
141 } |
|
142 } |
|
143 |
|
144 fun addIssueRelation(http: HttpRequest, dao: DataAccessObject, pathInfos: PathInfos) { |
|
145 val issue = http.pathParams["issue"]?.toIntOrNull()?.let(dao::findIssue) |
|
146 if (issue == null) { |
|
147 http.response.sendError(404) |
|
148 return |
|
149 } |
|
150 |
|
151 // determine the relation type |
|
152 val type: Pair<RelationType, Boolean>? = http.param("type")?.let { |
|
153 try { |
|
154 if (it.startsWith("!")) { |
|
155 Pair(RelationType.valueOf(it.substring(1)), true) |
|
156 } else { |
|
157 Pair(RelationType.valueOf(it), false) |
|
158 } |
|
159 } catch (_: IllegalArgumentException) { |
|
160 null |
|
161 } |
|
162 } |
|
163 |
|
164 // if the relation type was invalid, send HTTP 500 |
|
165 if (type == null) { |
|
166 http.response.sendError(500) |
|
167 return |
|
168 } |
|
169 |
|
170 // determine the target issue |
|
171 val targetIssue: Issue? = http.param("issue")?.let { |
|
172 if (it.startsWith("#") && it.length > 1) { |
|
173 it.substring(1).split(" ", limit = 2)[0].toIntOrNull() |
|
174 ?.let(dao::findIssue) |
|
175 ?.takeIf { target -> target.project.id == issue.project.id } |
|
176 } else { |
|
177 null |
|
178 } |
|
179 } |
|
180 |
|
181 // check if the target issue is valid |
|
182 if (targetIssue == null) { |
|
183 renderIssueView(http, dao, issue, pathInfos, "issue.relations.target.invalid") |
|
184 return |
|
185 } |
|
186 |
|
187 // commit the result |
|
188 dao.insertIssueRelation(IssueRelation(issue, targetIssue, type.first, type.second)) |
|
189 http.renderCommit("${pathInfos.issuesHref}${issue.id}") |
|
190 } |
|
191 |
|
192 fun removeIssueRelation(http: HttpRequest, dao: DataAccessObject, pathInfos: PathInfos) { |
|
193 val issue = http.pathParams["issue"]?.toIntOrNull()?.let(dao::findIssue) |
|
194 if (issue == null) { |
|
195 http.response.sendError(404) |
|
196 return |
|
197 } |
|
198 |
|
199 // determine relation |
|
200 val type = http.param("type")?.let { |
|
201 try { |
|
202 RelationType.valueOf(it)} |
|
203 catch (_:IllegalArgumentException) {null} |
|
204 } |
|
205 if (type == null) { |
|
206 http.response.sendError(500) |
|
207 return |
|
208 } |
|
209 val rel = http.param("to")?.toIntOrNull()?.let(dao::findIssue)?.let { |
|
210 IssueRelation( |
|
211 issue, |
|
212 it, |
|
213 type, |
|
214 http.param("reverse")?.toBoolean() ?: false |
|
215 ) |
|
216 } |
|
217 |
|
218 // execute removal, if there is something to remove |
|
219 rel?.run(dao::deleteIssueRelation) |
|
220 |
|
221 // always pretend that the operation was successful - if there was nothing to remove, it's okay |
|
222 http.renderCommit("${pathInfos.issuesHref}${issue.id}") |
|
223 } |