2020-12-29 02:04:20 +01:00
|
|
|
// Vikunja is a to-do list application to facilitate your life.
|
2021-02-02 20:19:13 +01:00
|
|
|
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
|
2020-02-19 22:57:56 +01:00
|
|
|
//
|
2020-12-29 02:04:20 +01:00
|
|
|
// This program is free software: you can redistribute it and/or modify
|
2020-12-23 16:41:52 +01:00
|
|
|
// it under the terms of the GNU Affero General Public Licensee as published by
|
2020-02-19 22:57:56 +01:00
|
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
|
|
// (at your option) any later version.
|
|
|
|
//
|
2020-12-29 02:04:20 +01:00
|
|
|
// This program is distributed in the hope that it will be useful,
|
2020-02-19 22:57:56 +01:00
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
2020-12-23 16:41:52 +01:00
|
|
|
// GNU Affero General Public Licensee for more details.
|
2020-02-19 22:57:56 +01:00
|
|
|
//
|
2020-12-23 16:41:52 +01:00
|
|
|
// You should have received a copy of the GNU Affero General Public Licensee
|
2020-12-29 02:04:20 +01:00
|
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
2020-02-19 22:57:56 +01:00
|
|
|
|
|
|
|
package models
|
|
|
|
|
|
|
|
import (
|
2020-10-11 22:10:03 +02:00
|
|
|
"time"
|
|
|
|
|
2021-08-01 23:40:25 +02:00
|
|
|
"code.vikunja.io/api/pkg/db"
|
2020-12-23 16:32:28 +01:00
|
|
|
|
2021-08-01 23:40:25 +02:00
|
|
|
"code.vikunja.io/api/pkg/events"
|
2020-02-19 22:57:56 +01:00
|
|
|
"code.vikunja.io/api/pkg/user"
|
|
|
|
"code.vikunja.io/web"
|
2021-08-01 23:40:25 +02:00
|
|
|
|
|
|
|
"xorm.io/builder"
|
|
|
|
"xorm.io/xorm"
|
2020-02-19 22:57:56 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
// TaskComment represents a task comment
|
|
|
|
type TaskComment struct {
|
|
|
|
ID int64 `xorm:"autoincr pk unique not null" json:"id" param:"commentid"`
|
|
|
|
Comment string `xorm:"text not null" json:"comment"`
|
|
|
|
AuthorID int64 `xorm:"not null" json:"-"`
|
|
|
|
Author *user.User `xorm:"-" json:"author"`
|
|
|
|
TaskID int64 `xorm:"not null" json:"-" param:"task"`
|
|
|
|
|
2020-06-27 19:04:01 +02:00
|
|
|
Created time.Time `xorm:"created" json:"created"`
|
|
|
|
Updated time.Time `xorm:"updated" json:"updated"`
|
2020-02-19 22:57:56 +01:00
|
|
|
|
|
|
|
web.CRUDable `xorm:"-" json:"-"`
|
|
|
|
web.Rights `xorm:"-" json:"-"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// TableName holds the table name for the task comments table
|
|
|
|
func (tc *TaskComment) TableName() string {
|
|
|
|
return "task_comments"
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create creates a new task comment
|
|
|
|
// @Summary Create a new task comment
|
|
|
|
// @Description Create a new task comment. The user doing this need to have at least write access to the task this comment should belong to.
|
|
|
|
// @tags task
|
|
|
|
// @Accept json
|
|
|
|
// @Produce json
|
|
|
|
// @Security JWTKeyAuth
|
|
|
|
// @Param relation body models.TaskComment true "The task comment object"
|
|
|
|
// @Param taskID path int true "Task ID"
|
2021-05-26 21:56:31 +02:00
|
|
|
// @Success 201 {object} models.TaskComment "The created task comment object."
|
2020-06-28 16:25:46 +02:00
|
|
|
// @Failure 400 {object} web.HTTPError "Invalid task comment object provided."
|
2020-02-19 22:57:56 +01:00
|
|
|
// @Failure 500 {object} models.Message "Internal error"
|
|
|
|
// @Router /tasks/{taskID}/comments [put]
|
2020-12-23 16:32:28 +01:00
|
|
|
func (tc *TaskComment) Create(s *xorm.Session, a web.Auth) (err error) {
|
2020-02-19 22:57:56 +01:00
|
|
|
// Check if the task exists
|
2021-02-02 23:48:37 +01:00
|
|
|
task, err := GetTaskSimple(s, &Task{ID: tc.TaskID})
|
2020-02-19 22:57:56 +01:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2021-04-07 15:02:57 +02:00
|
|
|
tc.Author, err = GetUserOrLinkShareUser(s, a)
|
2021-04-07 14:44:39 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
tc.AuthorID = tc.Author.ID
|
|
|
|
|
2020-12-23 16:32:28 +01:00
|
|
|
_, err = s.Insert(tc)
|
2020-02-25 21:18:42 +01:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
2021-02-02 23:48:37 +01:00
|
|
|
|
2021-04-07 14:44:39 +02:00
|
|
|
return events.Dispatch(&TaskCommentCreatedEvent{
|
2021-02-02 23:48:37 +01:00
|
|
|
Task: &task,
|
|
|
|
Comment: tc,
|
2021-04-07 14:44:39 +02:00
|
|
|
Doer: tc.Author,
|
2021-02-02 23:48:37 +01:00
|
|
|
})
|
2020-02-19 22:57:56 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Delete removes a task comment
|
|
|
|
// @Summary Remove a task comment
|
|
|
|
// @Description Remove a task comment. The user doing this need to have at least write access to the task this comment belongs to.
|
|
|
|
// @tags task
|
|
|
|
// @Accept json
|
|
|
|
// @Produce json
|
|
|
|
// @Security JWTKeyAuth
|
|
|
|
// @Param taskID path int true "Task ID"
|
|
|
|
// @Param commentID path int true "Comment ID"
|
|
|
|
// @Success 200 {object} models.Message "The task comment was successfully deleted."
|
2020-06-28 16:25:46 +02:00
|
|
|
// @Failure 400 {object} web.HTTPError "Invalid task comment object provided."
|
|
|
|
// @Failure 404 {object} web.HTTPError "The task comment was not found."
|
2020-02-19 22:57:56 +01:00
|
|
|
// @Failure 500 {object} models.Message "Internal error"
|
|
|
|
// @Router /tasks/{taskID}/comments/{commentID} [delete]
|
2021-02-02 23:48:37 +01:00
|
|
|
func (tc *TaskComment) Delete(s *xorm.Session, a web.Auth) error {
|
2020-12-23 16:32:28 +01:00
|
|
|
deleted, err := s.
|
|
|
|
ID(tc.ID).
|
|
|
|
NoAutoCondition().
|
|
|
|
Delete(tc)
|
2020-02-19 22:57:56 +01:00
|
|
|
if deleted == 0 {
|
|
|
|
return ErrTaskCommentDoesNotExist{ID: tc.ID}
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update updates a task text by its ID
|
|
|
|
// @Summary Update an existing task comment
|
|
|
|
// @Description Update an existing task comment. The user doing this need to have at least write access to the task this comment belongs to.
|
|
|
|
// @tags task
|
|
|
|
// @Accept json
|
|
|
|
// @Produce json
|
|
|
|
// @Security JWTKeyAuth
|
|
|
|
// @Param taskID path int true "Task ID"
|
|
|
|
// @Param commentID path int true "Comment ID"
|
|
|
|
// @Success 200 {object} models.TaskComment "The updated task comment object."
|
2020-06-28 16:25:46 +02:00
|
|
|
// @Failure 400 {object} web.HTTPError "Invalid task comment object provided."
|
|
|
|
// @Failure 404 {object} web.HTTPError "The task comment was not found."
|
2020-02-19 22:57:56 +01:00
|
|
|
// @Failure 500 {object} models.Message "Internal error"
|
|
|
|
// @Router /tasks/{taskID}/comments/{commentID} [post]
|
2021-02-02 23:48:37 +01:00
|
|
|
func (tc *TaskComment) Update(s *xorm.Session, a web.Auth) error {
|
2020-12-23 16:32:28 +01:00
|
|
|
updated, err := s.
|
|
|
|
ID(tc.ID).
|
|
|
|
Cols("comment").
|
|
|
|
Update(tc)
|
2020-02-19 22:57:56 +01:00
|
|
|
if updated == 0 {
|
|
|
|
return ErrTaskCommentDoesNotExist{ID: tc.ID}
|
|
|
|
}
|
2021-07-29 17:42:49 +02:00
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
task, err := GetTaskSimple(s, &Task{ID: tc.TaskID})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return events.Dispatch(&TaskCommentUpdatedEvent{
|
|
|
|
Task: &task,
|
|
|
|
Comment: tc,
|
|
|
|
Doer: tc.Author,
|
|
|
|
})
|
2020-02-19 22:57:56 +01:00
|
|
|
}
|
|
|
|
|
2022-06-16 17:38:27 +02:00
|
|
|
func getTaskCommentSimple(s *xorm.Session, tc *TaskComment) error {
|
|
|
|
exists, err := s.
|
|
|
|
Where("id = ? and task_id = ?", tc.ID, tc.TaskID).
|
|
|
|
NoAutoCondition().
|
|
|
|
Get(tc)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if !exists {
|
|
|
|
return ErrTaskCommentDoesNotExist{
|
|
|
|
ID: tc.ID,
|
|
|
|
TaskID: tc.TaskID,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-02-19 22:57:56 +01:00
|
|
|
// ReadOne handles getting a single comment
|
|
|
|
// @Summary Remove a task comment
|
|
|
|
// @Description Remove a task comment. The user doing this need to have at least read access to the task this comment belongs to.
|
|
|
|
// @tags task
|
|
|
|
// @Accept json
|
|
|
|
// @Produce json
|
|
|
|
// @Security JWTKeyAuth
|
|
|
|
// @Param taskID path int true "Task ID"
|
|
|
|
// @Param commentID path int true "Comment ID"
|
|
|
|
// @Success 200 {object} models.TaskComment "The task comment object."
|
2020-06-28 16:25:46 +02:00
|
|
|
// @Failure 400 {object} web.HTTPError "Invalid task comment object provided."
|
|
|
|
// @Failure 404 {object} web.HTTPError "The task comment was not found."
|
2020-02-19 22:57:56 +01:00
|
|
|
// @Failure 500 {object} models.Message "Internal error"
|
|
|
|
// @Router /tasks/{taskID}/comments/{commentID} [get]
|
2021-02-02 23:48:37 +01:00
|
|
|
func (tc *TaskComment) ReadOne(s *xorm.Session, a web.Auth) (err error) {
|
2022-06-16 17:38:27 +02:00
|
|
|
err = getTaskCommentSimple(s, tc)
|
2020-02-19 22:57:56 +01:00
|
|
|
if err != nil {
|
2022-06-16 17:38:27 +02:00
|
|
|
return err
|
2020-02-19 22:57:56 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Get the author
|
|
|
|
author := &user.User{}
|
2020-12-23 16:32:28 +01:00
|
|
|
_, err = s.
|
2020-02-19 22:57:56 +01:00
|
|
|
Where("id = ?", tc.AuthorID).
|
|
|
|
Get(author)
|
|
|
|
tc.Author = author
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// ReadAll returns all comments for a task
|
|
|
|
// @Summary Get all task comments
|
|
|
|
// @Description Get all task comments. The user doing this need to have at least read access to the task.
|
|
|
|
// @tags task
|
|
|
|
// @Accept json
|
|
|
|
// @Produce json
|
|
|
|
// @Security JWTKeyAuth
|
|
|
|
// @Param taskID path int true "Task ID"
|
|
|
|
// @Success 200 {array} models.TaskComment "The array with all task comments"
|
|
|
|
// @Failure 500 {object} models.Message "Internal error"
|
|
|
|
// @Router /tasks/{taskID}/comments [get]
|
2020-12-23 16:32:28 +01:00
|
|
|
func (tc *TaskComment) ReadAll(s *xorm.Session, auth web.Auth, search string, page int, perPage int) (result interface{}, resultCount int, numberOfTotalItems int64, err error) {
|
2020-02-19 22:57:56 +01:00
|
|
|
|
|
|
|
// Check if the user has access to the task
|
2020-12-23 16:32:28 +01:00
|
|
|
canRead, _, err := tc.CanRead(s, auth)
|
2020-02-19 22:57:56 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, 0, 0, err
|
|
|
|
}
|
|
|
|
if !canRead {
|
|
|
|
return nil, 0, 0, ErrGenericForbidden{}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Because we can't extend the type in general, we need to do this here.
|
|
|
|
// Not a good solution, but saves performance.
|
|
|
|
type TaskCommentWithAuthor struct {
|
|
|
|
TaskComment
|
|
|
|
AuthorFromDB *user.User `xorm:"extends" json:"-"`
|
|
|
|
}
|
|
|
|
|
2020-04-12 19:29:24 +02:00
|
|
|
limit, start := getLimitFromPageIndex(page, perPage)
|
2020-02-19 22:57:56 +01:00
|
|
|
comments := []*TaskComment{}
|
2020-12-23 16:32:28 +01:00
|
|
|
query := s.
|
2021-08-01 23:40:25 +02:00
|
|
|
Where(builder.And(
|
|
|
|
builder.Eq{"task_id": tc.TaskID},
|
|
|
|
db.ILIKE("comment", search),
|
|
|
|
)).
|
2020-04-12 19:29:24 +02:00
|
|
|
Join("LEFT", "users", "users.id = task_comments.author_id")
|
|
|
|
if limit > 0 {
|
|
|
|
query = query.Limit(limit, start)
|
|
|
|
}
|
|
|
|
err = query.Find(&comments)
|
2020-02-19 22:57:56 +01:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-04-07 14:44:39 +02:00
|
|
|
var authorIDs []int64
|
|
|
|
for _, comment := range comments {
|
|
|
|
authorIDs = append(authorIDs, comment.AuthorID)
|
|
|
|
}
|
|
|
|
|
|
|
|
authors, err := getUsersOrLinkSharesFromIDs(s, authorIDs)
|
2020-02-19 22:57:56 +01:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, comment := range comments {
|
|
|
|
comment.Author = authors[comment.AuthorID]
|
|
|
|
}
|
|
|
|
|
2020-12-23 16:32:28 +01:00
|
|
|
numberOfTotalItems, err = s.
|
2020-02-19 22:57:56 +01:00
|
|
|
Where("task_id = ? AND comment like ?", tc.TaskID, "%"+search+"%").
|
|
|
|
Count(&TaskCommentWithAuthor{})
|
|
|
|
return comments, len(comments), numberOfTotalItems, err
|
|
|
|
}
|