Merge branch 'main' into webhook-events
continuous-integration/drone/pr Build is failing
Details
continuous-integration/drone/pr Build is failing
Details
This commit is contained in:
commit
3713bcb374
2
go.mod
2
go.mod
|
@ -57,7 +57,7 @@ require (
|
|||
github.com/spf13/cobra v1.4.0
|
||||
github.com/spf13/viper v1.11.0
|
||||
github.com/stretchr/testify v1.7.2
|
||||
github.com/swaggo/swag v1.8.2
|
||||
github.com/swaggo/swag v1.8.3
|
||||
github.com/tkuchiki/go-timezone v0.2.2
|
||||
github.com/ulule/limiter/v3 v3.10.0
|
||||
github.com/vectordotdev/go-datemath v0.1.1-0.20211214182920-0a4ac8742b93
|
||||
|
|
2
go.sum
2
go.sum
|
@ -756,6 +756,8 @@ github.com/swaggo/swag v1.8.1 h1:JuARzFX1Z1njbCGz+ZytBR15TFJwF2Q7fu8puJHhQYI=
|
|||
github.com/swaggo/swag v1.8.1/go.mod h1:ugemnJsPZm/kRwFUnzBlbHRd0JY9zE1M4F+uy2pAaPQ=
|
||||
github.com/swaggo/swag v1.8.2 h1:D4aBiVS2a65zhyk3WFqOUz7Rz0sOaUcgeErcid5uGL4=
|
||||
github.com/swaggo/swag v1.8.2/go.mod h1:jMLeXOOmYyjk8PvHTsXBdrubsNd9gUJTTCzL5iBnseg=
|
||||
github.com/swaggo/swag v1.8.3 h1:3pZSSCQ//gAH88lfmxM3Cd1+JCsxV8Md6f36b9hrZ5s=
|
||||
github.com/swaggo/swag v1.8.3/go.mod h1:jMLeXOOmYyjk8PvHTsXBdrubsNd9gUJTTCzL5iBnseg=
|
||||
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
|
||||
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
|
||||
github.com/tkuchiki/go-timezone v0.2.2 h1:MdHR65KwgVTwWFQrota4SKzc4L5EfuH5SdZZGtk/P2Q=
|
||||
|
|
|
@ -63,6 +63,7 @@ func TestTaskComments(t *testing.T) {
|
|||
assertHandlerErrorCode(t, err, models.ErrCodeTaskDoesNotExist)
|
||||
})
|
||||
t.Run("Rights check", func(t *testing.T) {
|
||||
// Only the own comments can be updated
|
||||
t.Run("Forbidden", func(t *testing.T) {
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "14", "commentid": "2"}, `{"comment":"Lorem Ipsum"}`)
|
||||
assert.Error(t, err)
|
||||
|
@ -74,14 +75,14 @@ func TestTaskComments(t *testing.T) {
|
|||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via Team write", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "16", "commentid": "4"}, `{"comment":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"comment":"Lorem Ipsum"`)
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "16", "commentid": "4"}, `{"comment":"Lorem Ipsum"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via Team admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "17", "commentid": "5"}, `{"comment":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"comment":"Lorem Ipsum"`)
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "17", "commentid": "5"}, `{"comment":"Lorem Ipsum"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
|
||||
t.Run("Shared Via User readonly", func(t *testing.T) {
|
||||
|
@ -90,14 +91,14 @@ func TestTaskComments(t *testing.T) {
|
|||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via User write", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "19", "commentid": "7"}, `{"comment":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"comment":"Lorem Ipsum"`)
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "19", "commentid": "7"}, `{"comment":"Lorem Ipsum"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via User admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "20", "commentid": "8"}, `{"comment":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"comment":"Lorem Ipsum"`)
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "20", "commentid": "8"}, `{"comment":"Lorem Ipsum"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
|
||||
t.Run("Shared Via NamespaceTeam readonly", func(t *testing.T) {
|
||||
|
@ -106,14 +107,14 @@ func TestTaskComments(t *testing.T) {
|
|||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceTeam write", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "22", "commentid": "10"}, `{"comment":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"comment":"Lorem Ipsum"`)
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "22", "commentid": "10"}, `{"comment":"Lorem Ipsum"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceTeam admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "23", "commentid": "11"}, `{"comment":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"comment":"Lorem Ipsum"`)
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "23", "commentid": "11"}, `{"comment":"Lorem Ipsum"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
|
||||
t.Run("Shared Via NamespaceUser readonly", func(t *testing.T) {
|
||||
|
@ -122,14 +123,14 @@ func TestTaskComments(t *testing.T) {
|
|||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceUser write", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "25", "commentid": "13"}, `{"comment":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"comment":"Lorem Ipsum"`)
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "25", "commentid": "13"}, `{"comment":"Lorem Ipsum"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceUser admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "26", "commentid": "14"}, `{"comment":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"comment":"Lorem Ipsum"`)
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "26", "commentid": "14"}, `{"comment":"Lorem Ipsum"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -145,6 +146,7 @@ func TestTaskComments(t *testing.T) {
|
|||
assertHandlerErrorCode(t, err, models.ErrCodeTaskDoesNotExist)
|
||||
})
|
||||
t.Run("Rights check", func(t *testing.T) {
|
||||
// Only the own comments can be deleted
|
||||
t.Run("Forbidden", func(t *testing.T) {
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "14", "commentid": "2"})
|
||||
assert.Error(t, err)
|
||||
|
@ -156,14 +158,14 @@ func TestTaskComments(t *testing.T) {
|
|||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via Team write", func(t *testing.T) {
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "16", "commentid": "4"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "16", "commentid": "4"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via Team admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "17", "commentid": "5"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "17", "commentid": "5"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
|
||||
t.Run("Shared Via User readonly", func(t *testing.T) {
|
||||
|
@ -172,14 +174,14 @@ func TestTaskComments(t *testing.T) {
|
|||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via User write", func(t *testing.T) {
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "19", "commentid": "7"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "19", "commentid": "7"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via User admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "20", "commentid": "8"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "20", "commentid": "8"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
|
||||
t.Run("Shared Via NamespaceTeam readonly", func(t *testing.T) {
|
||||
|
@ -188,14 +190,14 @@ func TestTaskComments(t *testing.T) {
|
|||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceTeam write", func(t *testing.T) {
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "22", "commentid": "10"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "22", "commentid": "10"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceTeam admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "23", "commentid": "11"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "23", "commentid": "11"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
|
||||
t.Run("Shared Via NamespaceUser readonly", func(t *testing.T) {
|
||||
|
@ -204,14 +206,14 @@ func TestTaskComments(t *testing.T) {
|
|||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceUser write", func(t *testing.T) {
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "25", "commentid": "13"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "25", "commentid": "13"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceUser admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "26", "commentid": "14"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "26", "commentid": "14"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
// Vikunja is a to-do list application to facilitate your life.
|
||||
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public Licensee as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public Licensee for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public Licensee
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package migration
|
||||
|
||||
import (
|
||||
"src.techknowlogick.com/xormigrate"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
type users20220616145228 struct {
|
||||
OverdueTasksRemindersTime string `xorm:"varchar(5) not null default '09:00'" json:"-"`
|
||||
}
|
||||
|
||||
func (users20220616145228) TableName() string {
|
||||
return "users"
|
||||
}
|
||||
|
||||
func init() {
|
||||
migrations = append(migrations, &xormigrate.Migration{
|
||||
ID: "20220616145228",
|
||||
Description: "Add overdue task summary time field to users",
|
||||
Migrate: func(tx *xorm.Engine) error {
|
||||
return tx.Sync2(users20220616145228{})
|
||||
},
|
||||
Rollback: func(tx *xorm.Engine) error {
|
||||
return nil
|
||||
},
|
||||
})
|
||||
}
|
|
@ -43,6 +43,7 @@ func TestLabelTask_ReadAll(t *testing.T) {
|
|||
Issuer: "local",
|
||||
EmailRemindersEnabled: true,
|
||||
OverdueTasksRemindersEnabled: true,
|
||||
OverdueTasksRemindersTime: "09:00",
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
},
|
||||
|
|
|
@ -54,6 +54,7 @@ func TestLabel_ReadAll(t *testing.T) {
|
|||
Issuer: "local",
|
||||
EmailRemindersEnabled: true,
|
||||
OverdueTasksRemindersEnabled: true,
|
||||
OverdueTasksRemindersTime: "09:00",
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
}
|
||||
|
@ -104,6 +105,7 @@ func TestLabel_ReadAll(t *testing.T) {
|
|||
Issuer: "local",
|
||||
EmailRemindersEnabled: true,
|
||||
OverdueTasksRemindersEnabled: true,
|
||||
OverdueTasksRemindersTime: "09:00",
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
},
|
||||
|
@ -168,6 +170,7 @@ func TestLabel_ReadOne(t *testing.T) {
|
|||
Issuer: "local",
|
||||
EmailRemindersEnabled: true,
|
||||
OverdueTasksRemindersEnabled: true,
|
||||
OverdueTasksRemindersTime: "09:00",
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
}
|
||||
|
@ -229,6 +232,7 @@ func TestLabel_ReadOne(t *testing.T) {
|
|||
Issuer: "local",
|
||||
EmailRemindersEnabled: true,
|
||||
OverdueTasksRemindersEnabled: true,
|
||||
OverdueTasksRemindersTime: "09:00",
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
},
|
||||
|
|
|
@ -151,6 +151,7 @@ func TestListUser_ReadAll(t *testing.T) {
|
|||
Issuer: "local",
|
||||
EmailRemindersEnabled: true,
|
||||
OverdueTasksRemindersEnabled: true,
|
||||
OverdueTasksRemindersTime: "09:00",
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
},
|
||||
|
@ -164,6 +165,7 @@ func TestListUser_ReadAll(t *testing.T) {
|
|||
Issuer: "local",
|
||||
EmailRemindersEnabled: true,
|
||||
OverdueTasksRemindersEnabled: true,
|
||||
OverdueTasksRemindersTime: "09:00",
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
},
|
||||
|
|
|
@ -150,6 +150,7 @@ func TestNamespaceUser_ReadAll(t *testing.T) {
|
|||
Issuer: "local",
|
||||
EmailRemindersEnabled: true,
|
||||
OverdueTasksRemindersEnabled: true,
|
||||
OverdueTasksRemindersTime: "09:00",
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
},
|
||||
|
@ -163,6 +164,7 @@ func TestNamespaceUser_ReadAll(t *testing.T) {
|
|||
Issuer: "local",
|
||||
EmailRemindersEnabled: true,
|
||||
OverdueTasksRemindersEnabled: true,
|
||||
OverdueTasksRemindersTime: "09:00",
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
},
|
||||
|
|
|
@ -37,6 +37,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
|||
Issuer: "local",
|
||||
EmailRemindersEnabled: true,
|
||||
OverdueTasksRemindersEnabled: true,
|
||||
OverdueTasksRemindersTime: "09:00",
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
}
|
||||
|
@ -47,6 +48,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
|||
Issuer: "local",
|
||||
EmailRemindersEnabled: true,
|
||||
OverdueTasksRemindersEnabled: true,
|
||||
OverdueTasksRemindersTime: "09:00",
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
}
|
||||
|
@ -57,6 +59,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
|||
Issuer: "local",
|
||||
EmailRemindersEnabled: true,
|
||||
OverdueTasksRemindersEnabled: true,
|
||||
OverdueTasksRemindersTime: "09:00",
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
}
|
||||
|
|
|
@ -27,16 +27,36 @@ func (tc *TaskComment) CanRead(s *xorm.Session, a web.Auth) (bool, int, error) {
|
|||
return t.CanRead(s, a)
|
||||
}
|
||||
|
||||
func (tc *TaskComment) canUserModifyTaskComment(s *xorm.Session, a web.Auth) (bool, error) {
|
||||
t := Task{ID: tc.TaskID}
|
||||
canWriteTask, err := t.CanWrite(s, a)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !canWriteTask {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
savedComment := &TaskComment{
|
||||
ID: tc.ID,
|
||||
TaskID: tc.TaskID,
|
||||
}
|
||||
err = getTaskCommentSimple(s, savedComment)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return a.GetID() == savedComment.AuthorID, nil
|
||||
}
|
||||
|
||||
// CanDelete checks if a user can delete a comment
|
||||
func (tc *TaskComment) CanDelete(s *xorm.Session, a web.Auth) (bool, error) {
|
||||
t := Task{ID: tc.TaskID}
|
||||
return t.CanWrite(s, a)
|
||||
return tc.canUserModifyTaskComment(s, a)
|
||||
}
|
||||
|
||||
// CanUpdate checks if a user can update a comment
|
||||
func (tc *TaskComment) CanUpdate(s *xorm.Session, a web.Auth) (bool, error) {
|
||||
t := Task{ID: tc.TaskID}
|
||||
return t.CanWrite(s, a)
|
||||
return tc.canUserModifyTaskComment(s, a)
|
||||
}
|
||||
|
||||
// CanCreate checks if a user can create a new comment
|
||||
|
|
|
@ -151,6 +151,24 @@ func (tc *TaskComment) Update(s *xorm.Session, a web.Auth) error {
|
|||
})
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
@ -166,15 +184,9 @@ func (tc *TaskComment) Update(s *xorm.Session, a web.Auth) error {
|
|||
// @Failure 500 {object} models.Message "Internal error"
|
||||
// @Router /tasks/{taskID}/comments/{commentID} [get]
|
||||
func (tc *TaskComment) ReadOne(s *xorm.Session, a web.Auth) (err error) {
|
||||
exists, err := s.Get(tc)
|
||||
err = getTaskCommentSimple(s, tc)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !exists {
|
||||
return ErrTaskCommentDoesNotExist{
|
||||
ID: tc.ID,
|
||||
TaskID: tc.TaskID,
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Get the author
|
||||
|
|
|
@ -121,6 +121,16 @@ func TestTaskComment_Delete(t *testing.T) {
|
|||
assert.Error(t, err)
|
||||
assert.True(t, IsErrTaskCommentDoesNotExist(err))
|
||||
})
|
||||
t.Run("not the own comment", func(t *testing.T) {
|
||||
db.LoadAndAssertFixtures(t)
|
||||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
tc := &TaskComment{ID: 1, TaskID: 1}
|
||||
can, err := tc.CanDelete(s, &user.User{ID: 2})
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, can)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTaskComment_Update(t *testing.T) {
|
||||
|
@ -157,6 +167,16 @@ func TestTaskComment_Update(t *testing.T) {
|
|||
assert.Error(t, err)
|
||||
assert.True(t, IsErrTaskCommentDoesNotExist(err))
|
||||
})
|
||||
t.Run("not the own comment", func(t *testing.T) {
|
||||
db.LoadAndAssertFixtures(t)
|
||||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
tc := &TaskComment{ID: 1, TaskID: 1}
|
||||
can, err := tc.CanUpdate(s, &user.User{ID: 2})
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, can)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTaskComment_ReadOne(t *testing.T) {
|
||||
|
@ -167,7 +187,7 @@ func TestTaskComment_ReadOne(t *testing.T) {
|
|||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
tc := &TaskComment{ID: 1}
|
||||
tc := &TaskComment{ID: 1, TaskID: 1}
|
||||
err := tc.ReadOne(s, u)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "Lorem Ipsum Dolor Sit Amet", tc.Comment)
|
||||
|
|
|
@ -32,7 +32,7 @@ import (
|
|||
)
|
||||
|
||||
func getUndoneOverdueTasks(s *xorm.Session, now time.Time) (usersWithTasks map[int64]*userWithTasks, err error) {
|
||||
now = utils.GetTimeWithoutNanoSeconds(now)
|
||||
now = utils.GetTimeWithoutSeconds(now)
|
||||
nextMinute := now.Add(1 * time.Minute)
|
||||
|
||||
var tasks []*Task
|
||||
|
@ -78,10 +78,14 @@ func getUndoneOverdueTasks(s *xorm.Session, now time.Time) (usersWithTasks map[i
|
|||
tzs[t.User.Timezone] = tz
|
||||
}
|
||||
|
||||
// If it is 9:00 for that current user, add the task to their list of overdue tasks
|
||||
overdueMailTime := time.Date(now.Year(), now.Month(), now.Day(), 9, 0, 0, 0, tz)
|
||||
// If it is time for that current user, add the task to their list of overdue tasks
|
||||
tm, err := time.Parse("15:04", t.User.OverdueTasksRemindersTime)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
overdueMailTime := time.Date(now.Year(), now.Month(), now.Day(), tm.Hour(), tm.Minute(), 0, 0, tz)
|
||||
isTimeForReminder := overdueMailTime.After(now) || overdueMailTime.Equal(now.In(tz))
|
||||
wasTimeForReminder := overdueMailTime.Before(now.Add(time.Minute))
|
||||
wasTimeForReminder := overdueMailTime.Before(nextMinute)
|
||||
taskIsOverdueInUserTimezone := overdueMailTime.After(t.Task.DueDate.In(tz))
|
||||
if isTimeForReminder && wasTimeForReminder && taskIsOverdueInUserTimezone {
|
||||
_, exists := uts[t.User.ID]
|
||||
|
|
|
@ -61,7 +61,7 @@ func getTaskUsersForTasks(s *xorm.Session, taskIDs []int64, cond builder.Cond) (
|
|||
// Get all creators of tasks
|
||||
creators := make(map[int64]*user.User, len(taskIDs))
|
||||
err = s.
|
||||
Select("users.id, users.username, users.email, users.name, users.timezone").
|
||||
Select("users.*").
|
||||
Join("LEFT", "tasks", "tasks.created_by_id = users.id").
|
||||
In("tasks.id", taskIDs).
|
||||
Where(cond).
|
||||
|
|
|
@ -32,6 +32,7 @@ func TestListUsersFromList(t *testing.T) {
|
|||
Issuer: "local",
|
||||
EmailRemindersEnabled: true,
|
||||
OverdueTasksRemindersEnabled: true,
|
||||
OverdueTasksRemindersTime: "09:00",
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
}
|
||||
|
@ -42,6 +43,7 @@ func TestListUsersFromList(t *testing.T) {
|
|||
Issuer: "local",
|
||||
EmailRemindersEnabled: true,
|
||||
OverdueTasksRemindersEnabled: true,
|
||||
OverdueTasksRemindersTime: "09:00",
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
}
|
||||
|
@ -52,6 +54,7 @@ func TestListUsersFromList(t *testing.T) {
|
|||
Issuer: "local",
|
||||
EmailRemindersEnabled: true,
|
||||
OverdueTasksRemindersEnabled: true,
|
||||
OverdueTasksRemindersTime: "09:00",
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
}
|
||||
|
@ -63,6 +66,7 @@ func TestListUsersFromList(t *testing.T) {
|
|||
Issuer: "local",
|
||||
EmailRemindersEnabled: true,
|
||||
OverdueTasksRemindersEnabled: true,
|
||||
OverdueTasksRemindersTime: "09:00",
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
}
|
||||
|
@ -74,6 +78,7 @@ func TestListUsersFromList(t *testing.T) {
|
|||
Issuer: "local",
|
||||
EmailRemindersEnabled: true,
|
||||
OverdueTasksRemindersEnabled: true,
|
||||
OverdueTasksRemindersTime: "09:00",
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
}
|
||||
|
@ -84,6 +89,7 @@ func TestListUsersFromList(t *testing.T) {
|
|||
Issuer: "local",
|
||||
EmailRemindersEnabled: true,
|
||||
OverdueTasksRemindersEnabled: true,
|
||||
OverdueTasksRemindersTime: "09:00",
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
}
|
||||
|
@ -95,6 +101,7 @@ func TestListUsersFromList(t *testing.T) {
|
|||
EmailRemindersEnabled: true,
|
||||
DiscoverableByEmail: true,
|
||||
OverdueTasksRemindersEnabled: true,
|
||||
OverdueTasksRemindersTime: "09:00",
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
}
|
||||
|
@ -105,6 +112,7 @@ func TestListUsersFromList(t *testing.T) {
|
|||
Issuer: "local",
|
||||
EmailRemindersEnabled: true,
|
||||
OverdueTasksRemindersEnabled: true,
|
||||
OverdueTasksRemindersTime: "09:00",
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
}
|
||||
|
@ -115,6 +123,7 @@ func TestListUsersFromList(t *testing.T) {
|
|||
Issuer: "local",
|
||||
EmailRemindersEnabled: true,
|
||||
OverdueTasksRemindersEnabled: true,
|
||||
OverdueTasksRemindersTime: "09:00",
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
}
|
||||
|
@ -125,6 +134,7 @@ func TestListUsersFromList(t *testing.T) {
|
|||
Issuer: "local",
|
||||
EmailRemindersEnabled: true,
|
||||
OverdueTasksRemindersEnabled: true,
|
||||
OverdueTasksRemindersTime: "09:00",
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
}
|
||||
|
@ -136,6 +146,7 @@ func TestListUsersFromList(t *testing.T) {
|
|||
Issuer: "local",
|
||||
EmailRemindersEnabled: true,
|
||||
OverdueTasksRemindersEnabled: true,
|
||||
OverdueTasksRemindersTime: "09:00",
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
}
|
||||
|
@ -148,6 +159,7 @@ func TestListUsersFromList(t *testing.T) {
|
|||
EmailRemindersEnabled: true,
|
||||
DiscoverableByName: true,
|
||||
OverdueTasksRemindersEnabled: true,
|
||||
OverdueTasksRemindersTime: "09:00",
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
}
|
||||
|
@ -158,6 +170,7 @@ func TestListUsersFromList(t *testing.T) {
|
|||
Issuer: "local",
|
||||
EmailRemindersEnabled: true,
|
||||
OverdueTasksRemindersEnabled: true,
|
||||
OverdueTasksRemindersTime: "09:00",
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
}
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
package v1
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
|
@ -46,11 +48,13 @@ type UserSettings struct {
|
|||
DiscoverableByEmail bool `json:"discoverable_by_email"`
|
||||
// If enabled, the user will get an email for their overdue tasks each morning.
|
||||
OverdueTasksRemindersEnabled bool `json:"overdue_tasks_reminders_enabled"`
|
||||
// The time when the daily summary of overdue tasks will be sent via email.
|
||||
OverdueTasksRemindersTime string `json:"overdue_tasks_reminders_time" valid:"time,required"`
|
||||
// If a task is created without a specified list this value should be used. Applies
|
||||
// to tasks made directly in API and from clients.
|
||||
DefaultListID int64 `json:"default_list_id"`
|
||||
// The day when the week starts for this user. 0 = sunday, 1 = monday, etc.
|
||||
WeekStart int `json:"week_start"`
|
||||
WeekStart int `json:"week_start" valid:"range(0|7)"`
|
||||
// The user's language
|
||||
Language string `json:"language"`
|
||||
// The user's time zone. Used to send task reminders in the time zone of the user.
|
||||
|
@ -158,7 +162,16 @@ func UpdateGeneralUserSettings(c echo.Context) error {
|
|||
us := &UserSettings{}
|
||||
err := c.Bind(us)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "Bad user name provided.")
|
||||
var he *echo.HTTPError
|
||||
if errors.As(err, &he) {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid model provided. Error was: %s", he.Message))
|
||||
}
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "Invalid model provided.")
|
||||
}
|
||||
|
||||
err = c.Validate(us)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, err)
|
||||
}
|
||||
|
||||
u, err := user2.GetCurrentUser(c)
|
||||
|
@ -184,6 +197,7 @@ func UpdateGeneralUserSettings(c echo.Context) error {
|
|||
user.WeekStart = us.WeekStart
|
||||
user.Language = us.Language
|
||||
user.Timezone = us.Timezone
|
||||
user.OverdueTasksRemindersTime = us.OverdueTasksRemindersTime
|
||||
|
||||
_, err = user2.UpdateUser(s, user)
|
||||
if err != nil {
|
||||
|
|
|
@ -75,6 +75,7 @@ func UserShow(c echo.Context) error {
|
|||
WeekStart: u.WeekStart,
|
||||
Language: u.Language,
|
||||
Timezone: u.Timezone,
|
||||
OverdueTasksRemindersTime: u.OverdueTasksRemindersTime,
|
||||
},
|
||||
DeletionScheduledAt: u.DeletionScheduledAt,
|
||||
IsLocalUser: u.Issuer == user.IssuerLocal,
|
||||
|
|
|
@ -79,7 +79,6 @@ import (
|
|||
"code.vikunja.io/web"
|
||||
"code.vikunja.io/web/handler"
|
||||
|
||||
"github.com/asaskevich/govalidator"
|
||||
"github.com/getsentry/sentry-go"
|
||||
sentryecho "github.com/getsentry/sentry-go/echo"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
|
@ -88,31 +87,6 @@ import (
|
|||
elog "github.com/labstack/gommon/log"
|
||||
)
|
||||
|
||||
// CustomValidator is a dummy struct to use govalidator with echo
|
||||
type CustomValidator struct{}
|
||||
|
||||
// Validate validates stuff
|
||||
func (cv *CustomValidator) Validate(i interface{}) error {
|
||||
if _, err := govalidator.ValidateStruct(i); err != nil {
|
||||
|
||||
var errs []string
|
||||
for field, e := range govalidator.ErrorsByField(err) {
|
||||
errs = append(errs, field+": "+e)
|
||||
}
|
||||
|
||||
httperr := models.ValidationHTTPError{
|
||||
HTTPError: web.HTTPError{
|
||||
Code: models.ErrCodeInvalidData,
|
||||
Message: "Invalid Data",
|
||||
},
|
||||
InvalidFields: errs,
|
||||
}
|
||||
|
||||
return httperr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewEcho registers a new Echo instance
|
||||
func NewEcho() *echo.Echo {
|
||||
e := echo.New()
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
// Vikunja is a to-do list application to facilitate your life.
|
||||
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public Licensee as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public Licensee for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public Licensee
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package routes
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
|
||||
"code.vikunja.io/web"
|
||||
"github.com/asaskevich/govalidator"
|
||||
)
|
||||
|
||||
// CustomValidator is a dummy struct to use govalidator with echo
|
||||
type CustomValidator struct{}
|
||||
|
||||
func init() {
|
||||
govalidator.TagMap["time"] = govalidator.Validator(func(str string) bool {
|
||||
return govalidator.IsTime(str, "15:04")
|
||||
})
|
||||
}
|
||||
|
||||
// Validate validates stuff
|
||||
func (cv *CustomValidator) Validate(i interface{}) error {
|
||||
if _, err := govalidator.ValidateStruct(i); err != nil {
|
||||
|
||||
var errs []string
|
||||
for field, e := range govalidator.ErrorsByField(err) {
|
||||
errs = append(errs, field+": "+e)
|
||||
}
|
||||
|
||||
httperr := models.ValidationHTTPError{
|
||||
HTTPError: web.HTTPError{
|
||||
Code: models.ErrCodeInvalidData,
|
||||
Message: "Invalid Data",
|
||||
},
|
||||
InvalidFields: errs,
|
||||
}
|
||||
|
||||
return httperr
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -9184,6 +9184,10 @@ const docTemplate = `{
|
|||
"description": "If enabled, the user will get an email for their overdue tasks each morning.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"overdue_tasks_reminders_time": {
|
||||
"description": "The time when the daily summary of overdue tasks will be sent via email.",
|
||||
"type": "string"
|
||||
},
|
||||
"timezone": {
|
||||
"description": "The user's time zone. Used to send task reminders in the time zone of the user.",
|
||||
"type": "string"
|
||||
|
|
|
@ -9175,6 +9175,10 @@
|
|||
"description": "If enabled, the user will get an email for their overdue tasks each morning.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"overdue_tasks_reminders_time": {
|
||||
"description": "The time when the daily summary of overdue tasks will be sent via email.",
|
||||
"type": "string"
|
||||
},
|
||||
"timezone": {
|
||||
"description": "The user's time zone. Used to send task reminders in the time zone of the user.",
|
||||
"type": "string"
|
||||
|
|
|
@ -1310,6 +1310,10 @@ definitions:
|
|||
description: If enabled, the user will get an email for their overdue tasks
|
||||
each morning.
|
||||
type: boolean
|
||||
overdue_tasks_reminders_time:
|
||||
description: The time when the daily summary of overdue tasks will be sent
|
||||
via email.
|
||||
type: string
|
||||
timezone:
|
||||
description: The user's time zone. Used to send task reminders in the time
|
||||
zone of the user.
|
||||
|
|
|
@ -94,6 +94,7 @@ type User struct {
|
|||
DiscoverableByName bool `xorm:"bool default false index" json:"-"`
|
||||
DiscoverableByEmail bool `xorm:"bool default false index" json:"-"`
|
||||
OverdueTasksRemindersEnabled bool `xorm:"bool default true index" json:"-"`
|
||||
OverdueTasksRemindersTime string `xorm:"varchar(5) not null default '09:00'" json:"-"`
|
||||
DefaultListID int64 `xorm:"bigint null index" json:"-"`
|
||||
WeekStart int `xorm:"null" json:"-"`
|
||||
Language string `xorm:"varchar(50) null" json:"-"`
|
||||
|
@ -493,6 +494,7 @@ func UpdateUser(s *xorm.Session, user *User) (updatedUser *User, err error) {
|
|||
"week_start",
|
||||
"language",
|
||||
"timezone",
|
||||
"overdue_tasks_reminders_time",
|
||||
).
|
||||
Update(user)
|
||||
if err != nil {
|
||||
|
|
|
@ -30,3 +30,12 @@ func GetTimeWithoutNanoSeconds(t time.Time) time.Time {
|
|||
// so we make sure the time we use to get the reminders don't contain nanoseconds.
|
||||
return time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), 0, t.Location()).In(tz)
|
||||
}
|
||||
|
||||
// GetTimeWithoutSeconds returns a time.Time with the seconds set to 0.
|
||||
func GetTimeWithoutSeconds(t time.Time) time.Time {
|
||||
tz := config.GetTimeZone()
|
||||
|
||||
// By default, time.Now() includes nanoseconds which we don't save. That results in getting the wrong dates,
|
||||
// so we make sure the time we use to get the reminders don't contain nanoseconds.
|
||||
return time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), 0, 0, t.Location()).In(tz)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue