Can now comment on events
This commit is contained in:
parent
ac06033c7d
commit
bf5b800f7d
@ -2,7 +2,6 @@ package uk.co.neviyn.projectplanner
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||
import java.time.Instant
|
||||
import java.time.LocalDateTime
|
||||
import javax.persistence.CascadeType
|
||||
import javax.persistence.Column
|
||||
import javax.persistence.Entity
|
||||
@ -69,6 +68,8 @@ class Event(
|
||||
@ManyToMany(mappedBy = "events")
|
||||
@JsonIgnore
|
||||
var tags: MutableSet<Tag> = mutableSetOf(),
|
||||
@OneToMany(mappedBy = "event")
|
||||
var comments: MutableList<Comment> = mutableListOf(),
|
||||
@Id @GeneratedValue(strategy = GenerationType.IDENTITY) var id: Long? = null
|
||||
)
|
||||
|
||||
@ -82,12 +83,13 @@ class Comment(
|
||||
@JsonIgnore
|
||||
var event: Event? = null,
|
||||
@ManyToMany(mappedBy = "comments")
|
||||
@JsonIgnore
|
||||
var tags: MutableSet<Tag> = mutableSetOf(),
|
||||
var created: LocalDateTime = LocalDateTime.MIN,
|
||||
var created: Instant = Instant.MIN,
|
||||
var comment: String = "INVALID",
|
||||
@Id @GeneratedValue(strategy = GenerationType.IDENTITY) var id: Long? = null
|
||||
)
|
||||
) {
|
||||
fun toFlatComment(): FlatComment = FlatComment(created, comment, user!!.username)
|
||||
}
|
||||
|
||||
@Entity
|
||||
class Tag(
|
||||
|
@ -1,6 +1,7 @@
|
||||
package uk.co.neviyn.projectplanner
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.security.access.prepost.PreAuthorize
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
||||
import org.springframework.stereotype.Controller
|
||||
@ -13,6 +14,7 @@ import org.springframework.web.bind.annotation.RequestBody
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RequestParam
|
||||
import org.springframework.web.bind.annotation.ResponseBody
|
||||
import org.springframework.web.server.ResponseStatusException
|
||||
import java.time.Instant
|
||||
import javax.persistence.EntityManager
|
||||
import javax.transaction.Transactional
|
||||
@ -95,7 +97,7 @@ class HtmlController @Autowired constructor(val userRepository: UserRepository,
|
||||
|
||||
@Controller
|
||||
@RequestMapping("/project/{id}")
|
||||
class ProjectController @Autowired constructor(val projectRepository: ProjectRepository, val userRepository: UserRepository, val eventRepository: EventRepository) {
|
||||
class ProjectController @Autowired constructor(val projectRepository: ProjectRepository, val userRepository: UserRepository, val eventRepository: EventRepository, val commentRepository: CommentRepository) {
|
||||
|
||||
@GetMapping("")
|
||||
@PreAuthorize("hasPermission(#id, 'Long', '')")
|
||||
@ -169,5 +171,23 @@ class ProjectController @Autowired constructor(val projectRepository: ProjectRep
|
||||
eventRepository.deleteById(e.id)
|
||||
}
|
||||
|
||||
|
||||
@GetMapping("/eventcomments/{eventID}")
|
||||
@PreAuthorize("hasPermission(#id, 'Long', '')")
|
||||
@ResponseBody
|
||||
fun getCommentsForEvent(@PathVariable id: Long, @PathVariable eventID: Long): List<FlatComment> {
|
||||
val project = projectRepository.findById(id).get()
|
||||
val event = eventRepository.findById(eventID).get()
|
||||
if (event.project != project) throw ResponseStatusException(HttpStatus.FORBIDDEN, "This comment does not belong to this project")
|
||||
return event.comments.map { it.toFlatComment() }
|
||||
}
|
||||
|
||||
@PostMapping("/addcomment/{eventID}")
|
||||
@PreAuthorize("hasPermission(#id, 'Long', '')")
|
||||
@ResponseBody
|
||||
fun addCommentToEvent(@PathVariable id: Long, @PathVariable eventID: Long, @RequestBody c: NewComment, @AuthenticationPrincipal userDetails: CustomUserDetails) {
|
||||
val event = eventRepository.findById(eventID).get()
|
||||
val comment = Comment(user = userDetails.user, event = event, created = Instant.now(), comment = c.comment)
|
||||
event.comments.add(comment)
|
||||
commentRepository.save(comment)
|
||||
}
|
||||
}
|
@ -11,3 +11,7 @@ data class NewEvent(val title: String, val description: String, val start: Insta
|
||||
data class EditedEvent(val id: Long, val title: String, val description: String, val start: Instant, val end: Instant)
|
||||
|
||||
data class EventID(val id: Long)
|
||||
|
||||
data class FlatComment(val created: Instant, val comment: String, val username: String)
|
||||
|
||||
data class NewComment(val comment: String)
|
@ -55,13 +55,13 @@ create table if not exists projectplanner.comment
|
||||
(
|
||||
id bigserial not null
|
||||
constraint comment_pk
|
||||
primary key
|
||||
primary key,
|
||||
event_id bigserial not null
|
||||
constraint comment_event_fk
|
||||
references projectplanner.event
|
||||
references projectplanner.event,
|
||||
user_id bigserial not null
|
||||
constraint comment_user_fk
|
||||
references projectplanner."user",
|
||||
event_id bigserial not null,
|
||||
user_id bigserial not null,
|
||||
created timestamp not null,
|
||||
comment text not null
|
||||
);
|
||||
|
@ -1,6 +1,9 @@
|
||||
let DateTime = luxon.DateTime;
|
||||
let calendar;
|
||||
let addModal;
|
||||
let editModal;
|
||||
let memberList;
|
||||
let commentList;
|
||||
window.onload = function () {
|
||||
flatpickr("#startTimeInput", {enableTime: true})
|
||||
flatpickr("#endTimeInput", {enableTime: true})
|
||||
@ -71,15 +74,24 @@ window.onload = function () {
|
||||
document.getElementById('descriptionEdit').value = eventInfo.event.extendedProps.description
|
||||
startTimeEdit.setDate(eventInfo.event.start)
|
||||
endTimeEdit.setDate(eventInfo.event.end)
|
||||
setComments(eventInfo.event.id)
|
||||
editModal.show()
|
||||
},
|
||||
events: window.location.origin + window.location.pathname + "/events"
|
||||
});
|
||||
calendar.render();
|
||||
try {
|
||||
const options = {
|
||||
valueNames: ['username'],
|
||||
};
|
||||
new List('user-list', options);
|
||||
memberList = new List('user-list', options);
|
||||
} catch (e) {
|
||||
}
|
||||
const commentOptions = {
|
||||
valueNames: ['comment', 'created', 'username'],
|
||||
item: '<blockquote class="blockquote mb-0"><p class="comment"></p><footer class="blockquote-footer"><span class="username"></span> | <span class="created"></span></footer></blockquote>'
|
||||
}
|
||||
commentList = new List('comment-list', commentOptions)
|
||||
};
|
||||
|
||||
function addEvent() {
|
||||
@ -102,3 +114,37 @@ function editEvent() {
|
||||
if (myEvent.description !== description) myEvent.setExtendedProp('description', description)
|
||||
myEvent.setDates(start, end)
|
||||
}
|
||||
|
||||
async function setComments(eventID) {
|
||||
let data = await fetch(window.location.origin + window.location.pathname + "/eventcomments/" + eventID, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json;charset=utf-8',
|
||||
'X-CSRF-TOKEN': csrf_token
|
||||
}
|
||||
}).then(x => {
|
||||
return x.json()
|
||||
})
|
||||
commentList.clear()
|
||||
data.forEach(function (x) {
|
||||
x.created = DateTime.fromISO(x.created).toLocaleString(DateTime.DATETIME_FULL)
|
||||
commentList.add(x)
|
||||
})
|
||||
}
|
||||
|
||||
function saveComment() {
|
||||
let id = document.getElementById('idEdit').value
|
||||
let newComment = {
|
||||
comment: document.getElementById('addComment').value
|
||||
}
|
||||
fetch(window.location.origin + window.location.pathname + "/addcomment/" + id, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json;charset=utf-8',
|
||||
'X-CSRF-TOKEN': csrf_token
|
||||
},
|
||||
body: JSON.stringify(newComment)
|
||||
})
|
||||
// Refresh comments after adding one
|
||||
setComments(id)
|
||||
}
|
@ -4,15 +4,21 @@
|
||||
<title th:text="${project.title} + ' | Project Planner'">Project Planner</title>
|
||||
</head>
|
||||
<head>
|
||||
<style>
|
||||
.flatpickr {
|
||||
background-color: white !important;
|
||||
}
|
||||
</style>
|
||||
<link crossorigin="anonymous" href="https://cdn.jsdelivr.net/npm/fullcalendar@5.6.0/main.min.css" rel="stylesheet">
|
||||
<script crossorigin="anonymous" src="https://cdn.jsdelivr.net/npm/fullcalendar@5.6.0/main.min.js"></script>
|
||||
<script crossorigin="anonymous" src="https://cdnjs.cloudflare.com/ajax/libs/list.js/2.3.1/list.min.js"></script>
|
||||
<link crossorigin="anonymous" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css" rel="stylesheet">
|
||||
<script crossorigin="anonymous" src="https://cdn.jsdelivr.net/npm/flatpickr"></script>
|
||||
<script crossorigin="anonymous" src="https://cdn.jsdelivr.net/npm/luxon@1.26.0/build/global/luxon.min.js"></script>
|
||||
<script th:inline="javascript">
|
||||
let csrf_token = /*[[${_csrf.token}]]*/ 'csrf_token'
|
||||
</script>
|
||||
<script type="text/javascript" th:src="@{/js/project.js}"></script>
|
||||
<script th:src="@{/js/project.js}" type="text/javascript"></script>
|
||||
<title></title>
|
||||
</head>
|
||||
<body>
|
||||
@ -91,7 +97,7 @@
|
||||
</div>
|
||||
<!-- Modal for editing existing events -->
|
||||
<div class="modal" id="editEventModal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-dialog modal-xl">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Edit Event</h5>
|
||||
@ -99,14 +105,13 @@
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<input id="idEdit" type="hidden"/>
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-10">
|
||||
<div class="form-floating mb-3">
|
||||
<input class="form-control" id="titleEdit" type="text">
|
||||
<label for="titleEdit">Event Title</label>
|
||||
</div>
|
||||
<div class="form-floating mb-3">
|
||||
<textarea class="form-control h-100" id="descriptionEdit" rows="3"></textarea>
|
||||
<label for="descriptionEdit">Event Description</label>
|
||||
</div>
|
||||
<div class="form-floating mb-3">
|
||||
<input class="form-control flatpickr" id="startTimeEdit" type="text">
|
||||
<label for="startTimeEdit">Start Time</label>
|
||||
@ -116,16 +121,48 @@
|
||||
<label for="endTimeEdit">End Time</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer justify-content-between">
|
||||
<button class="btn btn-danger"
|
||||
<div class="col-2 d-flex flex-column">
|
||||
<button class="btn btn-danger mb-3"
|
||||
onclick="calendar.getEventById(document.getElementById('idEdit').value).remove();editModal.hide()"
|
||||
type="button">
|
||||
Delete Event
|
||||
</button>
|
||||
<div>
|
||||
<button class="btn btn-secondary" data-bs-dismiss="modal" type="button">Close</button>
|
||||
<button class="btn btn-primary" onclick="editEvent();editModal.hide()" type="button">Save Changes
|
||||
<button class="btn btn-primary mb-3" onclick="editEvent();editModal.hide()" type="button">
|
||||
Save
|
||||
Changes
|
||||
</button>
|
||||
<button class="btn btn-secondary" data-bs-dismiss="modal" type="button">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="form-floating mb-3">
|
||||
<textarea class="form-control h-100" id="descriptionEdit" rows="3"></textarea>
|
||||
<label for="descriptionEdit">Event Description</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Show comments -->
|
||||
<div class="row mb-3">
|
||||
<div class="col" id="comment-list">
|
||||
<h6>Comments</h6>
|
||||
<div class="list">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Add comment -->
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="form-floating mb-3">
|
||||
<textarea class="form-control h-100" id="addComment" rows="2"></textarea>
|
||||
<label for="addComment">Comment</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-1 d-inline-flex align-items-center justify-content-center">
|
||||
<button class="btn btn-primary" onclick="saveComment();" type="button">Save
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user