feat: rename lists to projects

This commit is contained in:
kolaente 2022-11-13 17:07:01 +01:00
parent 80266d1383
commit 349e6a5905
Signed by: konrad
GPG Key ID: F40E70337AB24C9B
113 changed files with 2753 additions and 2750 deletions

View File

@ -27,11 +27,11 @@ import (
ics "github.com/arran4/golang-ical" ics "github.com/arran4/golang-ical"
) )
func GetCaldavTodosForTasks(list *models.ListWithTasksAndBuckets, listTasks []*models.TaskWithComments) string { func GetCaldavTodosForTasks(project *models.ProjectWithTasksAndBuckets, projectTasks []*models.TaskWithComments) string {
// Make caldav todos from Vikunja todos // Make caldav todos from Vikunja todos
var caldavtodos []*Todo var caldavtodos []*Todo
for _, t := range listTasks { for _, t := range projectTasks {
duration := t.EndDate.Sub(t.StartDate) duration := t.EndDate.Sub(t.StartDate)
var categories []string var categories []string
@ -60,7 +60,7 @@ func GetCaldavTodosForTasks(list *models.ListWithTasksAndBuckets, listTasks []*m
} }
caldavConfig := &Config{ caldavConfig := &Config{
Name: list.Title, Name: project.Title,
ProdID: "Vikunja Todo App", ProdID: "Vikunja Todo App",
} }

View File

@ -73,7 +73,7 @@ func init() {
// User deletion flags // User deletion flags
userDeleteCmd.Flags().BoolVarP(&userFlagDeleteNow, "now", "n", false, "If provided, deletes the user immediately instead of sending them an email first.") userDeleteCmd.Flags().BoolVarP(&userFlagDeleteNow, "now", "n", false, "If provided, deletes the user immediately instead of sending them an email first.")
userCmd.AddCommand(userListCmd, userCreateCmd, userUpdateCmd, userResetPasswordCmd, userChangeEnabledCmd, userDeleteCmd) userCmd.AddCommand(userProjectCmd, userCreateCmd, userUpdateCmd, userResetPasswordCmd, userChangeEnabledCmd, userDeleteCmd)
rootCmd.AddCommand(userCmd) rootCmd.AddCommand(userCmd)
} }
@ -117,9 +117,9 @@ var userCmd = &cobra.Command{
Short: "Manage users locally through the cli.", Short: "Manage users locally through the cli.",
} }
var userListCmd = &cobra.Command{ var userProjectCmd = &cobra.Command{
Use: "list", Use: "project",
Short: "Shows a list of all users.", Short: "Shows a project of all users.",
PreRun: func(cmd *cobra.Command, args []string) { PreRun: func(cmd *cobra.Command, args []string) {
initialize.FullInit() initialize.FullInit()
}, },
@ -127,7 +127,7 @@ var userListCmd = &cobra.Command{
s := db.NewSession() s := db.NewSession()
defer s.Close() defer s.Close()
users, err := user.ListAllUsers(s) users, err := user.ProjectAllUsers(s)
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
log.Fatalf("Error getting users: %s", err) log.Fatalf("Error getting users: %s", err)

View File

@ -164,7 +164,7 @@ const (
DefaultSettingsDiscoverableByName Key = `defaultsettings.discoverable_by_name` DefaultSettingsDiscoverableByName Key = `defaultsettings.discoverable_by_name`
DefaultSettingsDiscoverableByEmail Key = `defaultsettings.discoverable_by_email` DefaultSettingsDiscoverableByEmail Key = `defaultsettings.discoverable_by_email`
DefaultSettingsOverdueTaskRemindersEnabled Key = `defaultsettings.overdue_tasks_reminders_enabled` DefaultSettingsOverdueTaskRemindersEnabled Key = `defaultsettings.overdue_tasks_reminders_enabled`
DefaultSettingsDefaultListID Key = `defaultsettings.default_list_id` DefaultSettingsDefaultProjectID Key = `defaultsettings.default_project_id`
DefaultSettingsWeekStart Key = `defaultsettings.week_start` DefaultSettingsWeekStart Key = `defaultsettings.week_start`
DefaultSettingsLanguage Key = `defaultsettings.language` DefaultSettingsLanguage Key = `defaultsettings.language`
DefaultSettingsTimezone Key = `defaultsettings.timezone` DefaultSettingsTimezone Key = `defaultsettings.timezone`
@ -370,7 +370,7 @@ func InitDefaultConfig() {
MigrationMicrosoftTodoEnable.setDefault(false) MigrationMicrosoftTodoEnable.setDefault(false)
// Avatar // Avatar
AvatarGravaterExpiration.setDefault(3600) AvatarGravaterExpiration.setDefault(3600)
// List Backgrounds // Project Backgrounds
BackgroundsEnabled.setDefault(true) BackgroundsEnabled.setDefault(true)
BackgroundsUploadEnabled.setDefault(true) BackgroundsUploadEnabled.setDefault(true)
BackgroundsUnsplashEnabled.setDefault(false) BackgroundsUnsplashEnabled.setDefault(false)

View File

@ -28,37 +28,37 @@ import (
// This tests the following behaviour: // This tests the following behaviour:
// 1. A namespace should not be editable if it is archived. // 1. A namespace should not be editable if it is archived.
// 1. With the exception being to un-archive it. // 1. With the exception being to un-archive it.
// 2. A list which belongs to an archived namespace cannot be edited. // 2. A project which belongs to an archived namespace cannot be edited.
// 3. An archived list should not be editable. // 3. An archived project should not be editable.
// 1. Except for un-archiving it. // 1. Except for un-archiving it.
// 4. It is not possible to un-archive a list individually if its namespace is archived. // 4. It is not possible to un-archive a project individually if its namespace is archived.
// 5. Creating new lists on an archived namespace should not work. // 5. Creating new projects on an archived namespace should not work.
// 6. Creating new tasks on an archived list should not work. // 6. Creating new tasks on an archived project should not work.
// 7. Creating new tasks on a list who's namespace is archived should not work. // 7. Creating new tasks on a project who's namespace is archived should not work.
// 8. Editing tasks on an archived list should not work. // 8. Editing tasks on an archived project should not work.
// 9. Editing tasks on a list who's namespace is archived should not work. // 9. Editing tasks on a project who's namespace is archived should not work.
// 10. Archived namespaces should not appear in the list with all namespaces. // 10. Archived namespaces should not appear in the project with all namespaces.
// 11. Archived lists should not appear in the list with all lists. // 11. Archived projects should not appear in the project with all projects.
// 12. Lists who's namespace is archived should not appear in the list with all lists. // 12. Projects who's namespace is archived should not appear in the project with all projects.
// //
// All of this is tested through integration tests because it's not yet clear if this will be implemented directly // All of this is tested through integration tests because it's not yet clear if this will be implemented directly
// or with some kind of middleware. // or with some kind of middleware.
// //
// Maybe the inheritance of lists from namespaces could be solved with some kind of is_archived_inherited flag - // Maybe the inheritance of projects from namespaces could be solved with some kind of is_archived_inherited flag -
// that way I'd only need to implement the checking on a list level and update the flag for all lists once the // that way I'd only need to implement the checking on a project level and update the flag for all projects once the
// namespace is archived. The archived flag would then be used to not accedentially unarchive lists which were // namespace is archived. The archived flag would then be used to not accedentially unarchive projects which were
// already individually archived when the namespace was archived. // already individually archived when the namespace was archived.
// Should still test it all though. // Should still test it all though.
// //
// Namespace 16 is archived // Namespace 16 is archived
// List 21 belongs to namespace 16 // Project 21 belongs to namespace 16
// List 22 is archived individually // Project 22 is archived individually
func TestArchived(t *testing.T) { func TestArchived(t *testing.T) {
testListHandler := webHandlerTest{ testProjectHandler := webHandlerTest{
user: &testuser1, user: &testuser1,
strFunc: func() handler.CObject { strFunc: func() handler.CObject {
return &models.List{} return &models.Project{}
}, },
t: t, t: t,
} }
@ -116,54 +116,54 @@ func TestArchived(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"is_archived":false`) assert.Contains(t, rec.Body.String(), `"is_archived":false`)
}) })
t.Run("no new lists", func(t *testing.T) { t.Run("no new projects", func(t *testing.T) {
_, err := testListHandler.testCreateWithUser(nil, map[string]string{"namespace": "16"}, `{"title":"Lorem"}`) _, err := testProjectHandler.testCreateWithUser(nil, map[string]string{"namespace": "16"}, `{"title":"Lorem"}`)
assert.Error(t, err) assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeNamespaceIsArchived) assertHandlerErrorCode(t, err, models.ErrCodeNamespaceIsArchived)
}) })
t.Run("should not appear in the list", func(t *testing.T) { t.Run("should not appear in the project", func(t *testing.T) {
rec, err := testNamespaceHandler.testReadAllWithUser(nil, nil) rec, err := testNamespaceHandler.testReadAllWithUser(nil, nil)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotContains(t, rec.Body.String(), `"title":"Archived testnamespace16"`) assert.NotContains(t, rec.Body.String(), `"title":"Archived testnamespace16"`)
}) })
t.Run("should appear in the list if explicitly requested", func(t *testing.T) { t.Run("should appear in the project if explicitly requested", func(t *testing.T) {
rec, err := testNamespaceHandler.testReadAllWithUser(url.Values{"is_archived": []string{"true"}}, nil) rec, err := testNamespaceHandler.testReadAllWithUser(url.Values{"is_archived": []string{"true"}}, nil)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Archived testnamespace16"`) assert.Contains(t, rec.Body.String(), `"title":"Archived testnamespace16"`)
}) })
}) })
t.Run("list", func(t *testing.T) { t.Run("project", func(t *testing.T) {
taskTests := func(taskID string, errCode int, t *testing.T) { taskTests := func(taskID string, errCode int, t *testing.T) {
t.Run("task", func(t *testing.T) { t.Run("task", func(t *testing.T) {
t.Run("edit task", func(t *testing.T) { t.Run("edit task", func(t *testing.T) {
_, err := testTaskHandler.testUpdateWithUser(nil, map[string]string{"listtask": taskID}, `{"title":"TestIpsum"}`) _, err := testTaskHandler.testUpdateWithUser(nil, map[string]string{"projecttask": taskID}, `{"title":"TestIpsum"}`)
assert.Error(t, err) assert.Error(t, err)
assertHandlerErrorCode(t, err, errCode) assertHandlerErrorCode(t, err, errCode)
}) })
t.Run("delete", func(t *testing.T) { t.Run("delete", func(t *testing.T) {
_, err := testTaskHandler.testDeleteWithUser(nil, map[string]string{"listtask": taskID}) _, err := testTaskHandler.testDeleteWithUser(nil, map[string]string{"projecttask": taskID})
assert.Error(t, err) assert.Error(t, err)
assertHandlerErrorCode(t, err, errCode) assertHandlerErrorCode(t, err, errCode)
}) })
t.Run("add new labels", func(t *testing.T) { t.Run("add new labels", func(t *testing.T) {
_, err := testLabelHandler.testCreateWithUser(nil, map[string]string{"listtask": taskID}, `{"label_id":1}`) _, err := testLabelHandler.testCreateWithUser(nil, map[string]string{"projecttask": taskID}, `{"label_id":1}`)
assert.Error(t, err) assert.Error(t, err)
assertHandlerErrorCode(t, err, errCode) assertHandlerErrorCode(t, err, errCode)
}) })
t.Run("remove lables", func(t *testing.T) { t.Run("remove lables", func(t *testing.T) {
_, err := testLabelHandler.testDeleteWithUser(nil, map[string]string{"listtask": taskID, "label": "4"}) _, err := testLabelHandler.testDeleteWithUser(nil, map[string]string{"projecttask": taskID, "label": "4"})
assert.Error(t, err) assert.Error(t, err)
assertHandlerErrorCode(t, err, errCode) assertHandlerErrorCode(t, err, errCode)
}) })
t.Run("add assignees", func(t *testing.T) { t.Run("add assignees", func(t *testing.T) {
_, err := testAssigneeHandler.testCreateWithUser(nil, map[string]string{"listtask": taskID}, `{"user_id":3}`) _, err := testAssigneeHandler.testCreateWithUser(nil, map[string]string{"projecttask": taskID}, `{"user_id":3}`)
assert.Error(t, err) assert.Error(t, err)
assertHandlerErrorCode(t, err, errCode) assertHandlerErrorCode(t, err, errCode)
}) })
t.Run("remove assignees", func(t *testing.T) { t.Run("remove assignees", func(t *testing.T) {
_, err := testAssigneeHandler.testDeleteWithUser(nil, map[string]string{"listtask": taskID, "user": "2"}) _, err := testAssigneeHandler.testDeleteWithUser(nil, map[string]string{"projecttask": taskID, "user": "2"})
assert.Error(t, err) assert.Error(t, err)
assertHandlerErrorCode(t, err, errCode) assertHandlerErrorCode(t, err, errCode)
}) })
@ -194,45 +194,45 @@ func TestArchived(t *testing.T) {
}) })
} }
// The list belongs to an archived namespace // The project belongs to an archived namespace
t.Run("archived namespace", func(t *testing.T) { t.Run("archived namespace", func(t *testing.T) {
t.Run("not editable", func(t *testing.T) { t.Run("not editable", func(t *testing.T) {
_, err := testListHandler.testUpdateWithUser(nil, map[string]string{"list": "21"}, `{"title":"TestIpsum","is_archived":true}`) _, err := testProjectHandler.testUpdateWithUser(nil, map[string]string{"project": "21"}, `{"title":"TestIpsum","is_archived":true}`)
assert.Error(t, err) assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeNamespaceIsArchived) assertHandlerErrorCode(t, err, models.ErrCodeNamespaceIsArchived)
}) })
t.Run("no new tasks", func(t *testing.T) { t.Run("no new tasks", func(t *testing.T) {
_, err := testTaskHandler.testCreateWithUser(nil, map[string]string{"list": "21"}, `{"title":"Lorem"}`) _, err := testTaskHandler.testCreateWithUser(nil, map[string]string{"project": "21"}, `{"title":"Lorem"}`)
assert.Error(t, err) assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeNamespaceIsArchived) assertHandlerErrorCode(t, err, models.ErrCodeNamespaceIsArchived)
}) })
t.Run("not unarchivable", func(t *testing.T) { t.Run("not unarchivable", func(t *testing.T) {
_, err := testListHandler.testUpdateWithUser(nil, map[string]string{"list": "21"}, `{"title":"LoremIpsum","is_archived":false}`) _, err := testProjectHandler.testUpdateWithUser(nil, map[string]string{"project": "21"}, `{"title":"LoremIpsum","is_archived":false}`)
assert.Error(t, err) assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeNamespaceIsArchived) assertHandlerErrorCode(t, err, models.ErrCodeNamespaceIsArchived)
}) })
taskTests("35", models.ErrCodeNamespaceIsArchived, t) taskTests("35", models.ErrCodeNamespaceIsArchived, t)
}) })
// The list itself is archived // The project itself is archived
t.Run("archived individually", func(t *testing.T) { t.Run("archived individually", func(t *testing.T) {
t.Run("not editable", func(t *testing.T) { t.Run("not editable", func(t *testing.T) {
_, err := testListHandler.testUpdateWithUser(nil, map[string]string{"list": "22"}, `{"title":"TestIpsum","is_archived":true}`) _, err := testProjectHandler.testUpdateWithUser(nil, map[string]string{"project": "22"}, `{"title":"TestIpsum","is_archived":true}`)
assert.Error(t, err) assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeListIsArchived) assertHandlerErrorCode(t, err, models.ErrCodeProjectIsArchived)
}) })
t.Run("no new tasks", func(t *testing.T) { t.Run("no new tasks", func(t *testing.T) {
_, err := testTaskHandler.testCreateWithUser(nil, map[string]string{"list": "22"}, `{"title":"Lorem"}`) _, err := testTaskHandler.testCreateWithUser(nil, map[string]string{"project": "22"}, `{"title":"Lorem"}`)
assert.Error(t, err) assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeListIsArchived) assertHandlerErrorCode(t, err, models.ErrCodeProjectIsArchived)
}) })
t.Run("unarchivable", func(t *testing.T) { t.Run("unarchivable", func(t *testing.T) {
rec, err := testListHandler.testUpdateWithUser(nil, map[string]string{"list": "22"}, `{"title":"LoremIpsum","is_archived":false,"namespace_id":1}`) rec, err := testProjectHandler.testUpdateWithUser(nil, map[string]string{"project": "22"}, `{"title":"LoremIpsum","is_archived":false,"namespace_id":1}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"is_archived":false`) assert.Contains(t, rec.Body.String(), `"is_archived":false`)
}) })
taskTests("36", models.ErrCodeListIsArchived, t) taskTests("36", models.ErrCodeProjectIsArchived, t)
}) })
}) })
} }

View File

@ -39,7 +39,7 @@ func TestBucket(t *testing.T) {
linkShare: &models.LinkSharing{ linkShare: &models.LinkSharing{
ID: 2, ID: 2,
Hash: "test2", Hash: "test2",
ListID: 2, ProjectID: 2,
Right: models.RightWrite, Right: models.RightWrite,
SharingType: models.SharingTypeWithoutPassword, SharingType: models.SharingTypeWithoutPassword,
SharedByID: 1, SharedByID: 1,
@ -51,17 +51,17 @@ func TestBucket(t *testing.T) {
} }
t.Run("ReadAll", func(t *testing.T) { t.Run("ReadAll", func(t *testing.T) {
t.Run("Normal", func(t *testing.T) { t.Run("Normal", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(nil, map[string]string{"list": "1"}) rec, err := testHandler.testReadAllWithUser(nil, map[string]string{"project": "1"})
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `testbucket1`) assert.Contains(t, rec.Body.String(), `testbucket1`)
assert.Contains(t, rec.Body.String(), `testbucket2`) assert.Contains(t, rec.Body.String(), `testbucket2`)
assert.Contains(t, rec.Body.String(), `testbucket3`) assert.Contains(t, rec.Body.String(), `testbucket3`)
assert.NotContains(t, rec.Body.String(), `testbucket4`) // Different List assert.NotContains(t, rec.Body.String(), `testbucket4`) // Different Project
}) })
}) })
t.Run("Update", func(t *testing.T) { t.Run("Update", func(t *testing.T) {
t.Run("Normal", func(t *testing.T) { t.Run("Normal", func(t *testing.T) {
// Check the list was loaded successfully afterwards, see testReadOneWithUser // Check the project was loaded successfully afterwards, see testReadOneWithUser
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"bucket": "1"}, `{"title":"TestLoremIpsum"}`) rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"bucket": "1"}, `{"title":"TestLoremIpsum"}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`) assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
@ -150,7 +150,7 @@ func TestBucket(t *testing.T) {
}) })
t.Run("Delete", func(t *testing.T) { t.Run("Delete", func(t *testing.T) {
t.Run("Normal", func(t *testing.T) { t.Run("Normal", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "1", "bucket": "1"}) rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "1", "bucket": "1"})
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`) assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
}) })
@ -162,70 +162,70 @@ func TestBucket(t *testing.T) {
t.Run("Rights check", func(t *testing.T) { t.Run("Rights check", func(t *testing.T) {
t.Run("Forbidden", func(t *testing.T) { t.Run("Forbidden", func(t *testing.T) {
// Owned by user13 // Owned by user13
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "20", "bucket": "5"}) _, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "20", "bucket": "5"})
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
t.Run("Shared Via Team readonly", func(t *testing.T) { t.Run("Shared Via Team readonly", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "6", "bucket": "6"}) _, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "6", "bucket": "6"})
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
t.Run("Shared Via Team write", func(t *testing.T) { t.Run("Shared Via Team write", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "7", "bucket": "7"}) rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "7", "bucket": "7"})
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`) assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
}) })
t.Run("Shared Via Team admin", func(t *testing.T) { t.Run("Shared Via Team admin", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "8", "bucket": "8"}) rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "8", "bucket": "8"})
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`) assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
}) })
t.Run("Shared Via User readonly", func(t *testing.T) { t.Run("Shared Via User readonly", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "9", "bucket": "9"}) _, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "9", "bucket": "9"})
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
t.Run("Shared Via User write", func(t *testing.T) { t.Run("Shared Via User write", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "10", "bucket": "10"}) rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "10", "bucket": "10"})
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`) assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
}) })
t.Run("Shared Via User admin", func(t *testing.T) { t.Run("Shared Via User admin", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "11", "bucket": "11"}) rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "11", "bucket": "11"})
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`) assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
}) })
t.Run("Shared Via NamespaceTeam readonly", func(t *testing.T) { t.Run("Shared Via NamespaceTeam readonly", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "12", "bucket": "12"}) _, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "12", "bucket": "12"})
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
t.Run("Shared Via NamespaceTeam write", func(t *testing.T) { t.Run("Shared Via NamespaceTeam write", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "13", "bucket": "13"}) rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "13", "bucket": "13"})
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`) assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
}) })
t.Run("Shared Via NamespaceTeam admin", func(t *testing.T) { t.Run("Shared Via NamespaceTeam admin", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "14", "bucket": "14"}) rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "14", "bucket": "14"})
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`) assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
}) })
t.Run("Shared Via NamespaceUser readonly", func(t *testing.T) { t.Run("Shared Via NamespaceUser readonly", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "15", "bucket": "15"}) _, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "15", "bucket": "15"})
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
t.Run("Shared Via NamespaceUser write", func(t *testing.T) { t.Run("Shared Via NamespaceUser write", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "16", "bucket": "16"}) rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "16", "bucket": "16"})
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`) assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
}) })
t.Run("Shared Via NamespaceUser admin", func(t *testing.T) { t.Run("Shared Via NamespaceUser admin", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "17", "bucket": "17"}) rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "17", "bucket": "17"})
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`) assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
}) })
@ -233,92 +233,92 @@ func TestBucket(t *testing.T) {
}) })
t.Run("Create", func(t *testing.T) { t.Run("Create", func(t *testing.T) {
t.Run("Normal", func(t *testing.T) { t.Run("Normal", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "1"}, `{"title":"Lorem Ipsum"}`) rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "1"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`) assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
}) })
t.Run("Nonexisting", func(t *testing.T) { t.Run("Nonexisting", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "9999"}, `{"title":"Lorem Ipsum"}`) _, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "9999"}, `{"title":"Lorem Ipsum"}`)
assert.Error(t, err) assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeListDoesNotExist) assertHandlerErrorCode(t, err, models.ErrCodeProjectDoesNotExist)
}) })
t.Run("Rights check", func(t *testing.T) { t.Run("Rights check", func(t *testing.T) {
t.Run("Forbidden", func(t *testing.T) { t.Run("Forbidden", func(t *testing.T) {
// Owned by user13 // Owned by user13
_, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "20"}, `{"title":"Lorem Ipsum"}`) _, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "20"}, `{"title":"Lorem Ipsum"}`)
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
t.Run("Shared Via Team readonly", func(t *testing.T) { t.Run("Shared Via Team readonly", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "6"}, `{"title":"Lorem Ipsum"}`) _, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "6"}, `{"title":"Lorem Ipsum"}`)
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
t.Run("Shared Via Team write", func(t *testing.T) { t.Run("Shared Via Team write", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "7"}, `{"title":"Lorem Ipsum"}`) rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "7"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`) assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
}) })
t.Run("Shared Via Team admin", func(t *testing.T) { t.Run("Shared Via Team admin", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "8"}, `{"title":"Lorem Ipsum"}`) rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "8"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`) assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
}) })
t.Run("Shared Via User readonly", func(t *testing.T) { t.Run("Shared Via User readonly", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "9"}, `{"title":"Lorem Ipsum"}`) _, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "9"}, `{"title":"Lorem Ipsum"}`)
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
t.Run("Shared Via User write", func(t *testing.T) { t.Run("Shared Via User write", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "10"}, `{"title":"Lorem Ipsum"}`) rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "10"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`) assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
}) })
t.Run("Shared Via User admin", func(t *testing.T) { t.Run("Shared Via User admin", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "11"}, `{"title":"Lorem Ipsum"}`) rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "11"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`) assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
}) })
t.Run("Shared Via NamespaceTeam readonly", func(t *testing.T) { t.Run("Shared Via NamespaceTeam readonly", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "12"}, `{"title":"Lorem Ipsum"}`) _, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "12"}, `{"title":"Lorem Ipsum"}`)
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
t.Run("Shared Via NamespaceTeam write", func(t *testing.T) { t.Run("Shared Via NamespaceTeam write", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "13"}, `{"title":"Lorem Ipsum"}`) rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "13"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`) assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
}) })
t.Run("Shared Via NamespaceTeam admin", func(t *testing.T) { t.Run("Shared Via NamespaceTeam admin", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "14"}, `{"title":"Lorem Ipsum"}`) rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "14"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`) assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
}) })
t.Run("Shared Via NamespaceUser readonly", func(t *testing.T) { t.Run("Shared Via NamespaceUser readonly", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "15"}, `{"title":"Lorem Ipsum"}`) _, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "15"}, `{"title":"Lorem Ipsum"}`)
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
t.Run("Shared Via NamespaceUser write", func(t *testing.T) { t.Run("Shared Via NamespaceUser write", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "16"}, `{"title":"Lorem Ipsum"}`) rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "16"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`) assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
}) })
t.Run("Shared Via NamespaceUser admin", func(t *testing.T) { t.Run("Shared Via NamespaceUser admin", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "17"}, `{"title":"Lorem Ipsum"}`) rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "17"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`) assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
}) })
}) })
t.Run("Link Share", func(t *testing.T) { t.Run("Link Share", func(t *testing.T) {
rec, err := testHandlerLinkShareWrite.testCreateWithLinkShare(nil, map[string]string{"list": "2"}, `{"title":"Lorem Ipsum"}`) rec, err := testHandlerLinkShareWrite.testCreateWithLinkShare(nil, map[string]string{"project": "2"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`) assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
db.AssertExists(t, "buckets", map[string]interface{}{ db.AssertExists(t, "buckets", map[string]interface{}{
"list_id": 2, "project_id": 2,
"created_by_id": -2, "created_by_id": -2,
"title": "Lorem Ipsum", "title": "Lorem Ipsum",
}, false) }, false)

View File

@ -31,14 +31,14 @@ func TestLinkSharingAuth(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, http.StatusOK, rec.Code) assert.Equal(t, http.StatusOK, rec.Code)
assert.Contains(t, rec.Body.String(), `"token":"`) assert.Contains(t, rec.Body.String(), `"token":"`)
assert.Contains(t, rec.Body.String(), `"list_id":1`) assert.Contains(t, rec.Body.String(), `"project_id":1`)
}) })
t.Run("Without Password, Password Provided", func(t *testing.T) { t.Run("Without Password, Password Provided", func(t *testing.T) {
rec, err := newTestRequest(t, http.MethodPost, apiv1.AuthenticateLinkShare, `{"password":"somethingsomething"}`, nil, map[string]string{"share": "test"}) rec, err := newTestRequest(t, http.MethodPost, apiv1.AuthenticateLinkShare, `{"password":"somethingsomething"}`, nil, map[string]string{"share": "test"})
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, http.StatusOK, rec.Code) assert.Equal(t, http.StatusOK, rec.Code)
assert.Contains(t, rec.Body.String(), `"token":"`) assert.Contains(t, rec.Body.String(), `"token":"`)
assert.Contains(t, rec.Body.String(), `"list_id":1`) assert.Contains(t, rec.Body.String(), `"project_id":1`)
}) })
t.Run("With Password, No Password Provided", func(t *testing.T) { t.Run("With Password, No Password Provided", func(t *testing.T) {
_, err := newTestRequest(t, http.MethodPost, apiv1.AuthenticateLinkShare, ``, nil, map[string]string{"share": "testWithPassword"}) _, err := newTestRequest(t, http.MethodPost, apiv1.AuthenticateLinkShare, ``, nil, map[string]string{"share": "testWithPassword"})
@ -50,7 +50,7 @@ func TestLinkSharingAuth(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, http.StatusOK, rec.Code) assert.Equal(t, http.StatusOK, rec.Code)
assert.Contains(t, rec.Body.String(), `"token":"`) assert.Contains(t, rec.Body.String(), `"token":"`)
assert.Contains(t, rec.Body.String(), `"list_id":1`) assert.Contains(t, rec.Body.String(), `"project_id":1`)
}) })
t.Run("With Wrong Password", func(t *testing.T) { t.Run("With Wrong Password", func(t *testing.T) {
_, err := newTestRequest(t, http.MethodPost, apiv1.AuthenticateLinkShare, `{"password":"someWrongPassword"}`, nil, map[string]string{"share": "testWithPassword"}) _, err := newTestRequest(t, http.MethodPost, apiv1.AuthenticateLinkShare, `{"password":"someWrongPassword"}`, nil, map[string]string{"share": "testWithPassword"})

View File

@ -31,7 +31,7 @@ func TestLinkSharing(t *testing.T) {
linkshareRead := &models.LinkSharing{ linkshareRead := &models.LinkSharing{
ID: 1, ID: 1,
Hash: "test1", Hash: "test1",
ListID: 1, ProjectID: 1,
Right: models.RightRead, Right: models.RightRead,
SharingType: models.SharingTypeWithoutPassword, SharingType: models.SharingTypeWithoutPassword,
SharedByID: 1, SharedByID: 1,
@ -40,7 +40,7 @@ func TestLinkSharing(t *testing.T) {
linkShareWrite := &models.LinkSharing{ linkShareWrite := &models.LinkSharing{
ID: 2, ID: 2,
Hash: "test2", Hash: "test2",
ListID: 2, ProjectID: 2,
Right: models.RightWrite, Right: models.RightWrite,
SharingType: models.SharingTypeWithoutPassword, SharingType: models.SharingTypeWithoutPassword,
SharedByID: 1, SharedByID: 1,
@ -49,7 +49,7 @@ func TestLinkSharing(t *testing.T) {
linkShareAdmin := &models.LinkSharing{ linkShareAdmin := &models.LinkSharing{
ID: 3, ID: 3,
Hash: "test3", Hash: "test3",
ListID: 3, ProjectID: 3,
Right: models.RightAdmin, Right: models.RightAdmin,
SharingType: models.SharingTypeWithoutPassword, SharingType: models.SharingTypeWithoutPassword,
SharedByID: 1, SharedByID: 1,
@ -65,102 +65,102 @@ func TestLinkSharing(t *testing.T) {
} }
t.Run("Forbidden", func(t *testing.T) { t.Run("Forbidden", func(t *testing.T) {
t.Run("read only", func(t *testing.T) { t.Run("read only", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "20"}, `{"right":0}`) _, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "20"}, `{"right":0}`)
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
t.Run("write", func(t *testing.T) { t.Run("write", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "20"}, `{"right":1}`) _, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "20"}, `{"right":1}`)
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
t.Run("admin", func(t *testing.T) { t.Run("admin", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "20"}, `{"right":2}`) _, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "20"}, `{"right":2}`)
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
}) })
t.Run("Read only access", func(t *testing.T) { t.Run("Read only access", func(t *testing.T) {
t.Run("read only", func(t *testing.T) { t.Run("read only", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "9"}, `{"right":0}`) _, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "9"}, `{"right":0}`)
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
t.Run("write", func(t *testing.T) { t.Run("write", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "9"}, `{"right":1}`) _, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "9"}, `{"right":1}`)
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
t.Run("admin", func(t *testing.T) { t.Run("admin", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "9"}, `{"right":2}`) _, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "9"}, `{"right":2}`)
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
}) })
t.Run("Write access", func(t *testing.T) { t.Run("Write access", func(t *testing.T) {
t.Run("read only", func(t *testing.T) { t.Run("read only", func(t *testing.T) {
req, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "10"}, `{"right":0}`) req, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "10"}, `{"right":0}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, req.Body.String(), `"hash":`) assert.Contains(t, req.Body.String(), `"hash":`)
}) })
t.Run("write", func(t *testing.T) { t.Run("write", func(t *testing.T) {
req, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "10"}, `{"right":1}`) req, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "10"}, `{"right":1}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, req.Body.String(), `"hash":`) assert.Contains(t, req.Body.String(), `"hash":`)
}) })
t.Run("admin", func(t *testing.T) { t.Run("admin", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "10"}, `{"right":2}`) _, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "10"}, `{"right":2}`)
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
}) })
t.Run("Admin access", func(t *testing.T) { t.Run("Admin access", func(t *testing.T) {
t.Run("read only", func(t *testing.T) { t.Run("read only", func(t *testing.T) {
req, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "11"}, `{"right":0}`) req, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "11"}, `{"right":0}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, req.Body.String(), `"hash":`) assert.Contains(t, req.Body.String(), `"hash":`)
}) })
t.Run("write", func(t *testing.T) { t.Run("write", func(t *testing.T) {
req, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "11"}, `{"right":1}`) req, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "11"}, `{"right":1}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, req.Body.String(), `"hash":`) assert.Contains(t, req.Body.String(), `"hash":`)
}) })
t.Run("admin", func(t *testing.T) { t.Run("admin", func(t *testing.T) {
req, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "11"}, `{"right":2}`) req, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "11"}, `{"right":2}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, req.Body.String(), `"hash":`) assert.Contains(t, req.Body.String(), `"hash":`)
}) })
}) })
}) })
t.Run("Lists", func(t *testing.T) { t.Run("Projects", func(t *testing.T) {
testHandlerListReadOnly := webHandlerTest{ testHandlerProjectReadOnly := webHandlerTest{
linkShare: linkshareRead, linkShare: linkshareRead,
strFunc: func() handler.CObject { strFunc: func() handler.CObject {
return &models.List{} return &models.Project{}
}, },
t: t, t: t,
} }
testHandlerListWrite := webHandlerTest{ testHandlerProjectWrite := webHandlerTest{
linkShare: linkShareWrite, linkShare: linkShareWrite,
strFunc: func() handler.CObject { strFunc: func() handler.CObject {
return &models.List{} return &models.Project{}
}, },
t: t, t: t,
} }
testHandlerListAdmin := webHandlerTest{ testHandlerProjectAdmin := webHandlerTest{
linkShare: linkShareAdmin, linkShare: linkShareAdmin,
strFunc: func() handler.CObject { strFunc: func() handler.CObject {
return &models.List{} return &models.Project{}
}, },
t: t, t: t,
} }
t.Run("ReadAll", func(t *testing.T) { t.Run("ReadAll", func(t *testing.T) {
t.Run("Normal", func(t *testing.T) { t.Run("Normal", func(t *testing.T) {
rec, err := testHandlerListReadOnly.testReadAllWithLinkShare(nil, nil) rec, err := testHandlerProjectReadOnly.testReadAllWithLinkShare(nil, nil)
assert.NoError(t, err) assert.NoError(t, err)
// Should only return the shared list, nothing else // Should only return the shared project, nothing else
assert.Contains(t, rec.Body.String(), `Test1`) assert.Contains(t, rec.Body.String(), `Test1`)
assert.NotContains(t, rec.Body.String(), `Test2`) assert.NotContains(t, rec.Body.String(), `Test2`)
assert.NotContains(t, rec.Body.String(), `Test3`) assert.NotContains(t, rec.Body.String(), `Test3`)
@ -168,9 +168,9 @@ func TestLinkSharing(t *testing.T) {
assert.NotContains(t, rec.Body.String(), `Test5`) assert.NotContains(t, rec.Body.String(), `Test5`)
}) })
t.Run("Search", func(t *testing.T) { t.Run("Search", func(t *testing.T) {
rec, err := testHandlerListReadOnly.testReadAllWithLinkShare(url.Values{"s": []string{"est1"}}, nil) rec, err := testHandlerProjectReadOnly.testReadAllWithLinkShare(url.Values{"s": []string{"est1"}}, nil)
assert.NoError(t, err) assert.NoError(t, err)
// Should only return the shared list, nothing else // Should only return the shared project, nothing else
assert.Contains(t, rec.Body.String(), `Test1`) assert.Contains(t, rec.Body.String(), `Test1`)
assert.NotContains(t, rec.Body.String(), `Test2`) assert.NotContains(t, rec.Body.String(), `Test2`)
assert.NotContains(t, rec.Body.String(), `Test3`) assert.NotContains(t, rec.Body.String(), `Test3`)
@ -180,35 +180,35 @@ func TestLinkSharing(t *testing.T) {
}) })
t.Run("ReadOne", func(t *testing.T) { t.Run("ReadOne", func(t *testing.T) {
t.Run("Normal", func(t *testing.T) { t.Run("Normal", func(t *testing.T) {
rec, err := testHandlerListReadOnly.testReadOneWithLinkShare(nil, map[string]string{"list": "1"}) rec, err := testHandlerProjectReadOnly.testReadOneWithLinkShare(nil, map[string]string{"project": "1"})
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Test1"`) assert.Contains(t, rec.Body.String(), `"title":"Test1"`)
assert.NotContains(t, rec.Body.String(), `"title":"Test2"`) assert.NotContains(t, rec.Body.String(), `"title":"Test2"`)
}) })
t.Run("Nonexisting", func(t *testing.T) { t.Run("Nonexisting", func(t *testing.T) {
_, err := testHandlerListReadOnly.testReadOneWithLinkShare(nil, map[string]string{"list": "9999999"}) _, err := testHandlerProjectReadOnly.testReadOneWithLinkShare(nil, map[string]string{"project": "9999999"})
assert.Error(t, err) assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeListDoesNotExist) assertHandlerErrorCode(t, err, models.ErrCodeProjectDoesNotExist)
}) })
t.Run("Rights check", func(t *testing.T) { t.Run("Rights check", func(t *testing.T) {
t.Run("Forbidden", func(t *testing.T) { t.Run("Forbidden", func(t *testing.T) {
// List 2, not shared with this token // Project 2, not shared with this token
_, err := testHandlerListReadOnly.testReadOneWithLinkShare(nil, map[string]string{"list": "2"}) _, err := testHandlerProjectReadOnly.testReadOneWithLinkShare(nil, map[string]string{"project": "2"})
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `You don't have the right to see this`) assert.Contains(t, err.(*echo.HTTPError).Message, `You don't have the right to see this`)
}) })
t.Run("Shared readonly", func(t *testing.T) { t.Run("Shared readonly", func(t *testing.T) {
rec, err := testHandlerListReadOnly.testReadOneWithLinkShare(nil, map[string]string{"list": "1"}) rec, err := testHandlerProjectReadOnly.testReadOneWithLinkShare(nil, map[string]string{"project": "1"})
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Test1"`) assert.Contains(t, rec.Body.String(), `"title":"Test1"`)
}) })
t.Run("Shared write", func(t *testing.T) { t.Run("Shared write", func(t *testing.T) {
rec, err := testHandlerListWrite.testReadOneWithLinkShare(nil, map[string]string{"list": "2"}) rec, err := testHandlerProjectWrite.testReadOneWithLinkShare(nil, map[string]string{"project": "2"})
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Test2"`) assert.Contains(t, rec.Body.String(), `"title":"Test2"`)
}) })
t.Run("Shared admin", func(t *testing.T) { t.Run("Shared admin", func(t *testing.T) {
rec, err := testHandlerListAdmin.testReadOneWithLinkShare(nil, map[string]string{"list": "3"}) rec, err := testHandlerProjectAdmin.testReadOneWithLinkShare(nil, map[string]string{"project": "3"})
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Test3"`) assert.Contains(t, rec.Body.String(), `"title":"Test3"`)
}) })
@ -216,28 +216,28 @@ func TestLinkSharing(t *testing.T) {
}) })
t.Run("Update", func(t *testing.T) { t.Run("Update", func(t *testing.T) {
t.Run("Nonexisting", func(t *testing.T) { t.Run("Nonexisting", func(t *testing.T) {
_, err := testHandlerListReadOnly.testUpdateWithLinkShare(nil, map[string]string{"list": "9999999"}, `{"title":"TestLoremIpsum"}`) _, err := testHandlerProjectReadOnly.testUpdateWithLinkShare(nil, map[string]string{"project": "9999999"}, `{"title":"TestLoremIpsum"}`)
assert.Error(t, err) assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeListDoesNotExist) assertHandlerErrorCode(t, err, models.ErrCodeProjectDoesNotExist)
}) })
t.Run("Rights check", func(t *testing.T) { t.Run("Rights check", func(t *testing.T) {
t.Run("Forbidden", func(t *testing.T) { t.Run("Forbidden", func(t *testing.T) {
_, err := testHandlerListReadOnly.testUpdateWithLinkShare(nil, map[string]string{"list": "2"}, `{"title":"TestLoremIpsum"}`) _, err := testHandlerProjectReadOnly.testUpdateWithLinkShare(nil, map[string]string{"project": "2"}, `{"title":"TestLoremIpsum"}`)
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
t.Run("Shared readonly", func(t *testing.T) { t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerListReadOnly.testUpdateWithLinkShare(nil, map[string]string{"list": "1"}, `{"title":"TestLoremIpsum"}`) _, err := testHandlerProjectReadOnly.testUpdateWithLinkShare(nil, map[string]string{"project": "1"}, `{"title":"TestLoremIpsum"}`)
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
t.Run("Shared write", func(t *testing.T) { t.Run("Shared write", func(t *testing.T) {
rec, err := testHandlerListWrite.testUpdateWithLinkShare(nil, map[string]string{"list": "2"}, `{"title":"TestLoremIpsum","namespace_id":1}`) rec, err := testHandlerProjectWrite.testUpdateWithLinkShare(nil, map[string]string{"project": "2"}, `{"title":"TestLoremIpsum","namespace_id":1}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`) assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
}) })
t.Run("Shared admin", func(t *testing.T) { t.Run("Shared admin", func(t *testing.T) {
rec, err := testHandlerListAdmin.testUpdateWithLinkShare(nil, map[string]string{"list": "3"}, `{"title":"TestLoremIpsum","namespace_id":2}`) rec, err := testHandlerProjectAdmin.testUpdateWithLinkShare(nil, map[string]string{"project": "3"}, `{"title":"TestLoremIpsum","namespace_id":2}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`) assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
}) })
@ -245,54 +245,54 @@ func TestLinkSharing(t *testing.T) {
}) })
t.Run("Delete", func(t *testing.T) { t.Run("Delete", func(t *testing.T) {
t.Run("Nonexisting", func(t *testing.T) { t.Run("Nonexisting", func(t *testing.T) {
_, err := testHandlerListReadOnly.testDeleteWithLinkShare(nil, map[string]string{"list": "9999999"}) _, err := testHandlerProjectReadOnly.testDeleteWithLinkShare(nil, map[string]string{"project": "9999999"})
assert.Error(t, err) assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeListDoesNotExist) assertHandlerErrorCode(t, err, models.ErrCodeProjectDoesNotExist)
}) })
t.Run("Rights check", func(t *testing.T) { t.Run("Rights check", func(t *testing.T) {
t.Run("Forbidden", func(t *testing.T) { t.Run("Forbidden", func(t *testing.T) {
_, err := testHandlerListReadOnly.testDeleteWithLinkShare(nil, map[string]string{"list": "1"}) _, err := testHandlerProjectReadOnly.testDeleteWithLinkShare(nil, map[string]string{"project": "1"})
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
t.Run("Shared readonly", func(t *testing.T) { t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerListReadOnly.testDeleteWithLinkShare(nil, map[string]string{"list": "1"}) _, err := testHandlerProjectReadOnly.testDeleteWithLinkShare(nil, map[string]string{"project": "1"})
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
t.Run("Shared write", func(t *testing.T) { t.Run("Shared write", func(t *testing.T) {
_, err := testHandlerListWrite.testDeleteWithLinkShare(nil, map[string]string{"list": "2"}) _, err := testHandlerProjectWrite.testDeleteWithLinkShare(nil, map[string]string{"project": "2"})
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
t.Run("Shared admin", func(t *testing.T) { t.Run("Shared admin", func(t *testing.T) {
rec, err := testHandlerListAdmin.testDeleteWithLinkShare(nil, map[string]string{"list": "3"}) rec, err := testHandlerProjectAdmin.testDeleteWithLinkShare(nil, map[string]string{"project": "3"})
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`) assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
}) })
}) })
}) })
// Creating a list should always be forbidden, since users need access to a namespace to create a list // Creating a project should always be forbidden, since users need access to a namespace to create a project
t.Run("Create", func(t *testing.T) { t.Run("Create", func(t *testing.T) {
t.Run("Nonexisting", func(t *testing.T) { t.Run("Nonexisting", func(t *testing.T) {
_, err := testHandlerListReadOnly.testCreateWithLinkShare(nil, map[string]string{"namespace": "999999"}, `{"title":"Lorem"}`) _, err := testHandlerProjectReadOnly.testCreateWithLinkShare(nil, map[string]string{"namespace": "999999"}, `{"title":"Lorem"}`)
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
t.Run("Rights check", func(t *testing.T) { t.Run("Rights check", func(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) { t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerListReadOnly.testCreateWithLinkShare(nil, map[string]string{"namespace": "1"}, `{"title":"Lorem","description":"Lorem Ipsum"}`) _, err := testHandlerProjectReadOnly.testCreateWithLinkShare(nil, map[string]string{"namespace": "1"}, `{"title":"Lorem","description":"Lorem Ipsum"}`)
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
t.Run("Shared write", func(t *testing.T) { t.Run("Shared write", func(t *testing.T) {
_, err := testHandlerListWrite.testCreateWithLinkShare(nil, map[string]string{"namespace": "2"}, `{"title":"Lorem","description":"Lorem Ipsum"}`) _, err := testHandlerProjectWrite.testCreateWithLinkShare(nil, map[string]string{"namespace": "2"}, `{"title":"Lorem","description":"Lorem Ipsum"}`)
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
t.Run("Shared admin", func(t *testing.T) { t.Run("Shared admin", func(t *testing.T) {
_, err := testHandlerListAdmin.testCreateWithLinkShare(nil, map[string]string{"namespace": "3"}, `{"title":"Lorem","description":"Lorem Ipsum"}`) _, err := testHandlerProjectAdmin.testCreateWithLinkShare(nil, map[string]string{"namespace": "3"}, `{"title":"Lorem","description":"Lorem Ipsum"}`)
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
@ -301,74 +301,74 @@ func TestLinkSharing(t *testing.T) {
t.Run("Right Management", func(t *testing.T) { t.Run("Right Management", func(t *testing.T) {
t.Run("Users", func(t *testing.T) { t.Run("Users", func(t *testing.T) {
testHandlerListUserReadOnly := webHandlerTest{ testHandlerProjectUserReadOnly := webHandlerTest{
linkShare: linkshareRead, linkShare: linkshareRead,
strFunc: func() handler.CObject { strFunc: func() handler.CObject {
return &models.ListUser{} return &models.ProjectUser{}
}, },
t: t, t: t,
} }
testHandlerListUserWrite := webHandlerTest{ testHandlerProjectUserWrite := webHandlerTest{
linkShare: linkShareWrite, linkShare: linkShareWrite,
strFunc: func() handler.CObject { strFunc: func() handler.CObject {
return &models.ListUser{} return &models.ProjectUser{}
}, },
t: t, t: t,
} }
testHandlerListUserAdmin := webHandlerTest{ testHandlerProjectUserAdmin := webHandlerTest{
linkShare: linkShareAdmin, linkShare: linkShareAdmin,
strFunc: func() handler.CObject { strFunc: func() handler.CObject {
return &models.ListUser{} return &models.ProjectUser{}
}, },
t: t, t: t,
} }
t.Run("ReadAll", func(t *testing.T) { t.Run("ReadAll", func(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) { t.Run("Shared readonly", func(t *testing.T) {
rec, err := testHandlerListUserReadOnly.testReadAllWithLinkShare(nil, map[string]string{"list": "1"}) rec, err := testHandlerProjectUserReadOnly.testReadAllWithLinkShare(nil, map[string]string{"project": "1"})
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[]`) assert.Contains(t, rec.Body.String(), `[]`)
}) })
t.Run("Shared write", func(t *testing.T) { t.Run("Shared write", func(t *testing.T) {
rec, err := testHandlerListUserWrite.testReadAllWithLinkShare(nil, map[string]string{"list": "2"}) rec, err := testHandlerProjectUserWrite.testReadAllWithLinkShare(nil, map[string]string{"project": "2"})
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[]`) assert.Contains(t, rec.Body.String(), `[]`)
}) })
t.Run("Shared admin", func(t *testing.T) { t.Run("Shared admin", func(t *testing.T) {
rec, err := testHandlerListUserAdmin.testReadAllWithLinkShare(nil, map[string]string{"list": "3"}) rec, err := testHandlerProjectUserAdmin.testReadAllWithLinkShare(nil, map[string]string{"project": "3"})
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"username":"user1"`) assert.Contains(t, rec.Body.String(), `"username":"user1"`)
}) })
}) })
t.Run("Create", func(t *testing.T) { t.Run("Create", func(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) { t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerListUserReadOnly.testCreateWithLinkShare(nil, map[string]string{"list": "1"}, `{"user_id":"user1"}`) _, err := testHandlerProjectUserReadOnly.testCreateWithLinkShare(nil, map[string]string{"project": "1"}, `{"user_id":"user1"}`)
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
t.Run("Shared write", func(t *testing.T) { t.Run("Shared write", func(t *testing.T) {
_, err := testHandlerListUserWrite.testCreateWithLinkShare(nil, map[string]string{"list": "2"}, `{"user_id":"user1"}`) _, err := testHandlerProjectUserWrite.testCreateWithLinkShare(nil, map[string]string{"project": "2"}, `{"user_id":"user1"}`)
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
t.Run("Shared admin", func(t *testing.T) { t.Run("Shared admin", func(t *testing.T) {
_, err := testHandlerListUserAdmin.testCreateWithLinkShare(nil, map[string]string{"list": "3"}, `{"user_id":"user1"}`) _, err := testHandlerProjectUserAdmin.testCreateWithLinkShare(nil, map[string]string{"project": "3"}, `{"user_id":"user1"}`)
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
}) })
t.Run("Update", func(t *testing.T) { t.Run("Update", func(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) { t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerListUserReadOnly.testUpdateWithLinkShare(nil, map[string]string{"list": "1"}, `{"user_id":"user1"}`) _, err := testHandlerProjectUserReadOnly.testUpdateWithLinkShare(nil, map[string]string{"project": "1"}, `{"user_id":"user1"}`)
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
t.Run("Shared write", func(t *testing.T) { t.Run("Shared write", func(t *testing.T) {
_, err := testHandlerListUserWrite.testUpdateWithLinkShare(nil, map[string]string{"list": "2"}, `{"user_id":"user1"}`) _, err := testHandlerProjectUserWrite.testUpdateWithLinkShare(nil, map[string]string{"project": "2"}, `{"user_id":"user1"}`)
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
t.Run("Shared admin", func(t *testing.T) { t.Run("Shared admin", func(t *testing.T) {
_, err := testHandlerListUserAdmin.testUpdateWithLinkShare(nil, map[string]string{"list": "3"}, `{"user_id":"user1"}`) _, err := testHandlerProjectUserAdmin.testUpdateWithLinkShare(nil, map[string]string{"project": "3"}, `{"user_id":"user1"}`)
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
@ -376,91 +376,91 @@ func TestLinkSharing(t *testing.T) {
}) })
t.Run("Delete", func(t *testing.T) { t.Run("Delete", func(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) { t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerListUserReadOnly.testDeleteWithLinkShare(nil, map[string]string{"list": "1"}) _, err := testHandlerProjectUserReadOnly.testDeleteWithLinkShare(nil, map[string]string{"project": "1"})
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
t.Run("Shared write", func(t *testing.T) { t.Run("Shared write", func(t *testing.T) {
_, err := testHandlerListUserWrite.testDeleteWithLinkShare(nil, map[string]string{"list": "2"}) _, err := testHandlerProjectUserWrite.testDeleteWithLinkShare(nil, map[string]string{"project": "2"})
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
t.Run("Shared admin", func(t *testing.T) { t.Run("Shared admin", func(t *testing.T) {
_, err := testHandlerListUserAdmin.testDeleteWithLinkShare(nil, map[string]string{"list": "3"}) _, err := testHandlerProjectUserAdmin.testDeleteWithLinkShare(nil, map[string]string{"project": "3"})
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
}) })
}) })
t.Run("Teams", func(t *testing.T) { t.Run("Teams", func(t *testing.T) {
testHandlerListTeamReadOnly := webHandlerTest{ testHandlerProjectTeamReadOnly := webHandlerTest{
linkShare: linkshareRead, linkShare: linkshareRead,
strFunc: func() handler.CObject { strFunc: func() handler.CObject {
return &models.TeamList{} return &models.TeamProject{}
}, },
t: t, t: t,
} }
testHandlerListTeamWrite := webHandlerTest{ testHandlerProjectTeamWrite := webHandlerTest{
linkShare: linkShareWrite, linkShare: linkShareWrite,
strFunc: func() handler.CObject { strFunc: func() handler.CObject {
return &models.TeamList{} return &models.TeamProject{}
}, },
t: t, t: t,
} }
testHandlerListTeamAdmin := webHandlerTest{ testHandlerProjectTeamAdmin := webHandlerTest{
linkShare: linkShareAdmin, linkShare: linkShareAdmin,
strFunc: func() handler.CObject { strFunc: func() handler.CObject {
return &models.TeamList{} return &models.TeamProject{}
}, },
t: t, t: t,
} }
t.Run("ReadAll", func(t *testing.T) { t.Run("ReadAll", func(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) { t.Run("Shared readonly", func(t *testing.T) {
rec, err := testHandlerListTeamReadOnly.testReadAllWithLinkShare(nil, map[string]string{"list": "1"}) rec, err := testHandlerProjectTeamReadOnly.testReadAllWithLinkShare(nil, map[string]string{"project": "1"})
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[]`) assert.Contains(t, rec.Body.String(), `[]`)
}) })
t.Run("Shared write", func(t *testing.T) { t.Run("Shared write", func(t *testing.T) {
rec, err := testHandlerListTeamWrite.testReadAllWithLinkShare(nil, map[string]string{"list": "2"}) rec, err := testHandlerProjectTeamWrite.testReadAllWithLinkShare(nil, map[string]string{"project": "2"})
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[]`) assert.Contains(t, rec.Body.String(), `[]`)
}) })
t.Run("Shared admin", func(t *testing.T) { t.Run("Shared admin", func(t *testing.T) {
rec, err := testHandlerListTeamAdmin.testReadAllWithLinkShare(nil, map[string]string{"list": "3"}) rec, err := testHandlerProjectTeamAdmin.testReadAllWithLinkShare(nil, map[string]string{"project": "3"})
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"name":"testteam1"`) assert.Contains(t, rec.Body.String(), `"name":"testteam1"`)
}) })
}) })
t.Run("Create", func(t *testing.T) { t.Run("Create", func(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) { t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerListTeamReadOnly.testCreateWithLinkShare(nil, map[string]string{"list": "1"}, `{"team_id":1}`) _, err := testHandlerProjectTeamReadOnly.testCreateWithLinkShare(nil, map[string]string{"project": "1"}, `{"team_id":1}`)
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
t.Run("Shared write", func(t *testing.T) { t.Run("Shared write", func(t *testing.T) {
_, err := testHandlerListTeamWrite.testCreateWithLinkShare(nil, map[string]string{"list": "2"}, `{"team_id":1}`) _, err := testHandlerProjectTeamWrite.testCreateWithLinkShare(nil, map[string]string{"project": "2"}, `{"team_id":1}`)
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
t.Run("Shared admin", func(t *testing.T) { t.Run("Shared admin", func(t *testing.T) {
_, err := testHandlerListTeamAdmin.testCreateWithLinkShare(nil, map[string]string{"list": "3"}, `{"team_id":1}`) _, err := testHandlerProjectTeamAdmin.testCreateWithLinkShare(nil, map[string]string{"project": "3"}, `{"team_id":1}`)
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
}) })
t.Run("Update", func(t *testing.T) { t.Run("Update", func(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) { t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerListTeamReadOnly.testUpdateWithLinkShare(nil, map[string]string{"list": "1"}, `{"team_id":1}`) _, err := testHandlerProjectTeamReadOnly.testUpdateWithLinkShare(nil, map[string]string{"project": "1"}, `{"team_id":1}`)
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
t.Run("Shared write", func(t *testing.T) { t.Run("Shared write", func(t *testing.T) {
_, err := testHandlerListTeamWrite.testUpdateWithLinkShare(nil, map[string]string{"list": "2"}, `{"team_id":1}`) _, err := testHandlerProjectTeamWrite.testUpdateWithLinkShare(nil, map[string]string{"project": "2"}, `{"team_id":1}`)
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
t.Run("Shared admin", func(t *testing.T) { t.Run("Shared admin", func(t *testing.T) {
_, err := testHandlerListTeamAdmin.testUpdateWithLinkShare(nil, map[string]string{"list": "3"}, `{"team_id":1}`) _, err := testHandlerProjectTeamAdmin.testUpdateWithLinkShare(nil, map[string]string{"project": "3"}, `{"team_id":1}`)
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
@ -468,17 +468,17 @@ func TestLinkSharing(t *testing.T) {
}) })
t.Run("Delete", func(t *testing.T) { t.Run("Delete", func(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) { t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerListTeamReadOnly.testDeleteWithLinkShare(nil, map[string]string{"list": "1"}) _, err := testHandlerProjectTeamReadOnly.testDeleteWithLinkShare(nil, map[string]string{"project": "1"})
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
t.Run("Shared write", func(t *testing.T) { t.Run("Shared write", func(t *testing.T) {
_, err := testHandlerListTeamWrite.testDeleteWithLinkShare(nil, map[string]string{"list": "2"}) _, err := testHandlerProjectTeamWrite.testDeleteWithLinkShare(nil, map[string]string{"project": "2"})
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
t.Run("Shared admin", func(t *testing.T) { t.Run("Shared admin", func(t *testing.T) {
_, err := testHandlerListTeamAdmin.testDeleteWithLinkShare(nil, map[string]string{"list": "3"}) _, err := testHandlerProjectTeamAdmin.testDeleteWithLinkShare(nil, map[string]string{"project": "3"})
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
@ -586,34 +586,34 @@ func TestLinkSharing(t *testing.T) {
}) })
t.Run("Create", func(t *testing.T) { t.Run("Create", func(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) { t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerTaskReadOnly.testCreateWithLinkShare(nil, map[string]string{"list": "1"}, `{"title":"Lorem Ipsum"}`) _, err := testHandlerTaskReadOnly.testCreateWithLinkShare(nil, map[string]string{"project": "1"}, `{"title":"Lorem Ipsum"}`)
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
t.Run("Shared write", func(t *testing.T) { t.Run("Shared write", func(t *testing.T) {
rec, err := testHandlerTaskWrite.testCreateWithLinkShare(nil, map[string]string{"list": "2"}, `{"title":"Lorem Ipsum"}`) rec, err := testHandlerTaskWrite.testCreateWithLinkShare(nil, map[string]string{"project": "2"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`) assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
}) })
t.Run("Shared admin", func(t *testing.T) { t.Run("Shared admin", func(t *testing.T) {
rec, err := testHandlerTaskAdmin.testCreateWithLinkShare(nil, map[string]string{"list": "3"}, `{"title":"Lorem Ipsum"}`) rec, err := testHandlerTaskAdmin.testCreateWithLinkShare(nil, map[string]string{"project": "3"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`) assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
}) })
}) })
t.Run("Update", func(t *testing.T) { t.Run("Update", func(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) { t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerTaskReadOnly.testUpdateWithLinkShare(nil, map[string]string{"listtask": "1"}, `{"title":"Lorem Ipsum"}`) _, err := testHandlerTaskReadOnly.testUpdateWithLinkShare(nil, map[string]string{"projecttask": "1"}, `{"title":"Lorem Ipsum"}`)
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
t.Run("Shared write", func(t *testing.T) { t.Run("Shared write", func(t *testing.T) {
rec, err := testHandlerTaskWrite.testUpdateWithLinkShare(nil, map[string]string{"listtask": "13"}, `{"title":"Lorem Ipsum"}`) rec, err := testHandlerTaskWrite.testUpdateWithLinkShare(nil, map[string]string{"projecttask": "13"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`) assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
}) })
t.Run("Shared admin", func(t *testing.T) { t.Run("Shared admin", func(t *testing.T) {
rec, err := testHandlerTaskAdmin.testUpdateWithLinkShare(nil, map[string]string{"listtask": "32"}, `{"title":"Lorem Ipsum"}`) rec, err := testHandlerTaskAdmin.testUpdateWithLinkShare(nil, map[string]string{"projecttask": "32"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`) assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
}) })
@ -621,17 +621,17 @@ func TestLinkSharing(t *testing.T) {
}) })
t.Run("Delete", func(t *testing.T) { t.Run("Delete", func(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) { t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerTaskReadOnly.testDeleteWithLinkShare(nil, map[string]string{"listtask": "1"}) _, err := testHandlerTaskReadOnly.testDeleteWithLinkShare(nil, map[string]string{"projecttask": "1"})
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
t.Run("Shared write", func(t *testing.T) { t.Run("Shared write", func(t *testing.T) {
rec, err := testHandlerTaskWrite.testDeleteWithLinkShare(nil, map[string]string{"listtask": "13"}) rec, err := testHandlerTaskWrite.testDeleteWithLinkShare(nil, map[string]string{"projecttask": "13"})
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `Successfully deleted.`) assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
}) })
t.Run("Shared admin", func(t *testing.T) { t.Run("Shared admin", func(t *testing.T) {
rec, err := testHandlerTaskAdmin.testDeleteWithLinkShare(nil, map[string]string{"listtask": "32"}) rec, err := testHandlerTaskAdmin.testDeleteWithLinkShare(nil, map[string]string{"projecttask": "32"})
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `Successfully deleted.`) assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
}) })
@ -738,34 +738,34 @@ func TestLinkSharing(t *testing.T) {
} }
t.Run("ReadAll", func(t *testing.T) { t.Run("ReadAll", func(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) { t.Run("Shared readonly", func(t *testing.T) {
rec, err := testHandlerLinkShareReadOnly.testReadAllWithLinkShare(nil, map[string]string{"list": "1"}) rec, err := testHandlerLinkShareReadOnly.testReadAllWithLinkShare(nil, map[string]string{"project": "1"})
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"hash":"test"`) assert.Contains(t, rec.Body.String(), `"hash":"test"`)
}) })
t.Run("Shared write", func(t *testing.T) { t.Run("Shared write", func(t *testing.T) {
rec, err := testHandlerLinkShareWrite.testReadAllWithLinkShare(nil, map[string]string{"list": "2"}) rec, err := testHandlerLinkShareWrite.testReadAllWithLinkShare(nil, map[string]string{"project": "2"})
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"hash":"test2"`) assert.Contains(t, rec.Body.String(), `"hash":"test2"`)
}) })
t.Run("Shared admin", func(t *testing.T) { t.Run("Shared admin", func(t *testing.T) {
rec, err := testHandlerLinkShareAdmin.testReadAllWithLinkShare(nil, map[string]string{"list": "3"}) rec, err := testHandlerLinkShareAdmin.testReadAllWithLinkShare(nil, map[string]string{"project": "3"})
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"hash":"test3"`) assert.Contains(t, rec.Body.String(), `"hash":"test3"`)
}) })
}) })
t.Run("Create", func(t *testing.T) { t.Run("Create", func(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) { t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerLinkShareReadOnly.testCreateWithLinkShare(nil, map[string]string{"list": "1"}, `{}`) _, err := testHandlerLinkShareReadOnly.testCreateWithLinkShare(nil, map[string]string{"project": "1"}, `{}`)
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
t.Run("Shared write", func(t *testing.T) { t.Run("Shared write", func(t *testing.T) {
_, err := testHandlerLinkShareWrite.testCreateWithLinkShare(nil, map[string]string{"list": "2"}, `{}`) _, err := testHandlerLinkShareWrite.testCreateWithLinkShare(nil, map[string]string{"project": "2"}, `{}`)
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
t.Run("Shared admin", func(t *testing.T) { t.Run("Shared admin", func(t *testing.T) {
_, err := testHandlerLinkShareAdmin.testCreateWithLinkShare(nil, map[string]string{"list": "3"}, `{}`) _, err := testHandlerLinkShareAdmin.testCreateWithLinkShare(nil, map[string]string{"project": "3"}, `{}`)
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })

View File

@ -26,11 +26,11 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestList(t *testing.T) { func TestProject(t *testing.T) {
testHandler := webHandlerTest{ testHandler := webHandlerTest{
user: &testuser1, user: &testuser1,
strFunc: func() handler.CObject { strFunc: func() handler.CObject {
return &models.List{} return &models.Project{}
}, },
t: t, t: t,
} }
@ -40,7 +40,7 @@ func TestList(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `Test1`) assert.Contains(t, rec.Body.String(), `Test1`)
assert.NotContains(t, rec.Body.String(), `Test2"`) assert.NotContains(t, rec.Body.String(), `Test2"`)
assert.Contains(t, rec.Body.String(), `Test3`) // Shared directly via users_list assert.Contains(t, rec.Body.String(), `Test3`) // Shared directly via users_project
assert.Contains(t, rec.Body.String(), `Test4`) // Shared via namespace assert.Contains(t, rec.Body.String(), `Test4`) // Shared via namespace
assert.NotContains(t, rec.Body.String(), `Test5`) assert.NotContains(t, rec.Body.String(), `Test5`)
assert.NotContains(t, rec.Body.String(), `Test21`) // Archived through namespace assert.NotContains(t, rec.Body.String(), `Test21`) // Archived through namespace
@ -55,12 +55,12 @@ func TestList(t *testing.T) {
assert.NotContains(t, rec.Body.String(), `Test4`) assert.NotContains(t, rec.Body.String(), `Test4`)
assert.NotContains(t, rec.Body.String(), `Test5`) assert.NotContains(t, rec.Body.String(), `Test5`)
}) })
t.Run("Normal with archived lists", func(t *testing.T) { t.Run("Normal with archived projects", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"is_archived": []string{"true"}}, nil) rec, err := testHandler.testReadAllWithUser(url.Values{"is_archived": []string{"true"}}, nil)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `Test1`) assert.Contains(t, rec.Body.String(), `Test1`)
assert.NotContains(t, rec.Body.String(), `Test2"`) assert.NotContains(t, rec.Body.String(), `Test2"`)
assert.Contains(t, rec.Body.String(), `Test3`) // Shared directly via users_list assert.Contains(t, rec.Body.String(), `Test3`) // Shared directly via users_project
assert.Contains(t, rec.Body.String(), `Test4`) // Shared via namespace assert.Contains(t, rec.Body.String(), `Test4`) // Shared via namespace
assert.NotContains(t, rec.Body.String(), `Test5`) assert.NotContains(t, rec.Body.String(), `Test5`)
assert.Contains(t, rec.Body.String(), `Test21`) // Archived through namespace assert.Contains(t, rec.Body.String(), `Test21`) // Archived through namespace
@ -69,7 +69,7 @@ func TestList(t *testing.T) {
}) })
t.Run("ReadOne", func(t *testing.T) { t.Run("ReadOne", func(t *testing.T) {
t.Run("Normal", func(t *testing.T) { t.Run("Normal", func(t *testing.T) {
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "1"}) rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"project": "1"})
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Test1"`) assert.Contains(t, rec.Body.String(), `"title":"Test1"`)
assert.NotContains(t, rec.Body.String(), `"title":"Test2"`) assert.NotContains(t, rec.Body.String(), `"title":"Test2"`)
@ -79,89 +79,89 @@ func TestList(t *testing.T) {
assert.Equal(t, "2", rec.Result().Header.Get("x-max-right")) // User 1 is owner so they should have admin rights. assert.Equal(t, "2", rec.Result().Header.Get("x-max-right")) // User 1 is owner so they should have admin rights.
}) })
t.Run("Nonexisting", func(t *testing.T) { t.Run("Nonexisting", func(t *testing.T) {
_, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "9999"}) _, err := testHandler.testReadOneWithUser(nil, map[string]string{"project": "9999"})
assert.Error(t, err) assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeListDoesNotExist) assertHandlerErrorCode(t, err, models.ErrCodeProjectDoesNotExist)
}) })
t.Run("Rights check", func(t *testing.T) { t.Run("Rights check", func(t *testing.T) {
t.Run("Forbidden", func(t *testing.T) { t.Run("Forbidden", func(t *testing.T) {
// Owned by user13 // Owned by user13
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "20"}) rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"project": "20"})
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `You don't have the right to see this`) assert.Contains(t, err.(*echo.HTTPError).Message, `You don't have the right to see this`)
assert.Empty(t, rec.Result().Header.Get("x-max-rights")) assert.Empty(t, rec.Result().Header.Get("x-max-rights"))
}) })
t.Run("Shared Via Team readonly", func(t *testing.T) { t.Run("Shared Via Team readonly", func(t *testing.T) {
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "6"}) rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"project": "6"})
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Test6"`) assert.Contains(t, rec.Body.String(), `"title":"Test6"`)
assert.Equal(t, "0", rec.Result().Header.Get("x-max-right")) assert.Equal(t, "0", rec.Result().Header.Get("x-max-right"))
}) })
t.Run("Shared Via Team write", func(t *testing.T) { t.Run("Shared Via Team write", func(t *testing.T) {
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "7"}) rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"project": "7"})
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Test7"`) assert.Contains(t, rec.Body.String(), `"title":"Test7"`)
assert.Equal(t, "1", rec.Result().Header.Get("x-max-right")) assert.Equal(t, "1", rec.Result().Header.Get("x-max-right"))
}) })
t.Run("Shared Via Team admin", func(t *testing.T) { t.Run("Shared Via Team admin", func(t *testing.T) {
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "8"}) rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"project": "8"})
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Test8"`) assert.Contains(t, rec.Body.String(), `"title":"Test8"`)
assert.Equal(t, "2", rec.Result().Header.Get("x-max-right")) assert.Equal(t, "2", rec.Result().Header.Get("x-max-right"))
}) })
t.Run("Shared Via User readonly", func(t *testing.T) { t.Run("Shared Via User readonly", func(t *testing.T) {
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "9"}) rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"project": "9"})
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Test9"`) assert.Contains(t, rec.Body.String(), `"title":"Test9"`)
assert.Equal(t, "0", rec.Result().Header.Get("x-max-right")) assert.Equal(t, "0", rec.Result().Header.Get("x-max-right"))
}) })
t.Run("Shared Via User write", func(t *testing.T) { t.Run("Shared Via User write", func(t *testing.T) {
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "10"}) rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"project": "10"})
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Test10"`) assert.Contains(t, rec.Body.String(), `"title":"Test10"`)
assert.Equal(t, "1", rec.Result().Header.Get("x-max-right")) assert.Equal(t, "1", rec.Result().Header.Get("x-max-right"))
}) })
t.Run("Shared Via User admin", func(t *testing.T) { t.Run("Shared Via User admin", func(t *testing.T) {
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "11"}) rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"project": "11"})
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Test11"`) assert.Contains(t, rec.Body.String(), `"title":"Test11"`)
assert.Equal(t, "2", rec.Result().Header.Get("x-max-right")) assert.Equal(t, "2", rec.Result().Header.Get("x-max-right"))
}) })
t.Run("Shared Via NamespaceTeam readonly", func(t *testing.T) { t.Run("Shared Via NamespaceTeam readonly", func(t *testing.T) {
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "12"}) rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"project": "12"})
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Test12"`) assert.Contains(t, rec.Body.String(), `"title":"Test12"`)
assert.Equal(t, "0", rec.Result().Header.Get("x-max-right")) assert.Equal(t, "0", rec.Result().Header.Get("x-max-right"))
}) })
t.Run("Shared Via NamespaceTeam write", func(t *testing.T) { t.Run("Shared Via NamespaceTeam write", func(t *testing.T) {
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "13"}) rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"project": "13"})
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Test13"`) assert.Contains(t, rec.Body.String(), `"title":"Test13"`)
assert.Equal(t, "1", rec.Result().Header.Get("x-max-right")) assert.Equal(t, "1", rec.Result().Header.Get("x-max-right"))
}) })
t.Run("Shared Via NamespaceTeam admin", func(t *testing.T) { t.Run("Shared Via NamespaceTeam admin", func(t *testing.T) {
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "14"}) rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"project": "14"})
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Test14"`) assert.Contains(t, rec.Body.String(), `"title":"Test14"`)
assert.Equal(t, "2", rec.Result().Header.Get("x-max-right")) assert.Equal(t, "2", rec.Result().Header.Get("x-max-right"))
}) })
t.Run("Shared Via NamespaceUser readonly", func(t *testing.T) { t.Run("Shared Via NamespaceUser readonly", func(t *testing.T) {
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "15"}) rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"project": "15"})
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Test15"`) assert.Contains(t, rec.Body.String(), `"title":"Test15"`)
assert.Equal(t, "0", rec.Result().Header.Get("x-max-right")) assert.Equal(t, "0", rec.Result().Header.Get("x-max-right"))
}) })
t.Run("Shared Via NamespaceUser write", func(t *testing.T) { t.Run("Shared Via NamespaceUser write", func(t *testing.T) {
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "16"}) rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"project": "16"})
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Test16"`) assert.Contains(t, rec.Body.String(), `"title":"Test16"`)
assert.Equal(t, "1", rec.Result().Header.Get("x-max-right")) assert.Equal(t, "1", rec.Result().Header.Get("x-max-right"))
}) })
t.Run("Shared Via NamespaceUser admin", func(t *testing.T) { t.Run("Shared Via NamespaceUser admin", func(t *testing.T) {
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "17"}) rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"project": "17"})
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Test17"`) assert.Contains(t, rec.Body.String(), `"title":"Test17"`)
assert.Equal(t, "2", rec.Result().Header.Get("x-max-right")) assert.Equal(t, "2", rec.Result().Header.Get("x-max-right"))
@ -170,101 +170,101 @@ func TestList(t *testing.T) {
}) })
t.Run("Update", func(t *testing.T) { t.Run("Update", func(t *testing.T) {
t.Run("Normal", func(t *testing.T) { t.Run("Normal", func(t *testing.T) {
// Check the list was loaded successfully afterwards, see testReadOneWithUser // Check the project was loaded successfully afterwards, see testReadOneWithUser
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "1"}, `{"title":"TestLoremIpsum","namespace_id":1}`) rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "1"}, `{"title":"TestLoremIpsum","namespace_id":1}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`) assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
// The description should not be updated but returned correctly // The description should not be updated but returned correctly
assert.Contains(t, rec.Body.String(), `description":"Lorem Ipsum`) assert.Contains(t, rec.Body.String(), `description":"Lorem Ipsum`)
}) })
t.Run("Nonexisting", func(t *testing.T) { t.Run("Nonexisting", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "9999"}, `{"title":"TestLoremIpsum"}`) _, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "9999"}, `{"title":"TestLoremIpsum"}`)
assert.Error(t, err) assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeListDoesNotExist) assertHandlerErrorCode(t, err, models.ErrCodeProjectDoesNotExist)
}) })
t.Run("Normal with updating the description", func(t *testing.T) { t.Run("Normal with updating the description", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "1"}, `{"title":"TestLoremIpsum","description":"Lorem Ipsum dolor sit amet","namespace_id":1}`) rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "1"}, `{"title":"TestLoremIpsum","description":"Lorem Ipsum dolor sit amet","namespace_id":1}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`) assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
assert.Contains(t, rec.Body.String(), `"description":"Lorem Ipsum dolor sit amet`) assert.Contains(t, rec.Body.String(), `"description":"Lorem Ipsum dolor sit amet`)
}) })
t.Run("Empty title", func(t *testing.T) { t.Run("Empty title", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "1"}, `{"title":""}`) _, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "1"}, `{"title":""}`)
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message.(models.ValidationHTTPError).InvalidFields, "title: non zero value required") assert.Contains(t, err.(*echo.HTTPError).Message.(models.ValidationHTTPError).InvalidFields, "title: non zero value required")
}) })
t.Run("Title too long", func(t *testing.T) { t.Run("Title too long", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "1"}, `{"title":"Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea taki"}`) _, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "1"}, `{"title":"Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea taki"}`)
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message.(models.ValidationHTTPError).InvalidFields[0], "does not validate as runelength(1|250)") assert.Contains(t, err.(*echo.HTTPError).Message.(models.ValidationHTTPError).InvalidFields[0], "does not validate as runelength(1|250)")
}) })
t.Run("Rights check", func(t *testing.T) { t.Run("Rights check", func(t *testing.T) {
t.Run("Forbidden", func(t *testing.T) { t.Run("Forbidden", func(t *testing.T) {
// Owned by user13 // Owned by user13
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "20"}, `{"title":"TestLoremIpsum"}`) _, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "20"}, `{"title":"TestLoremIpsum"}`)
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
t.Run("Shared Via Team readonly", func(t *testing.T) { t.Run("Shared Via Team readonly", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "6"}, `{"title":"TestLoremIpsum"}`) _, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "6"}, `{"title":"TestLoremIpsum"}`)
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
t.Run("Shared Via Team write", func(t *testing.T) { t.Run("Shared Via Team write", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "7"}, `{"title":"TestLoremIpsum","namespace_id":6}`) rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "7"}, `{"title":"TestLoremIpsum","namespace_id":6}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`) assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
}) })
t.Run("Shared Via Team admin", func(t *testing.T) { t.Run("Shared Via Team admin", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "8"}, `{"title":"TestLoremIpsum","namespace_id":6}`) rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "8"}, `{"title":"TestLoremIpsum","namespace_id":6}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`) assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
}) })
t.Run("Shared Via User readonly", func(t *testing.T) { t.Run("Shared Via User readonly", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "9"}, `{"title":"TestLoremIpsum"}`) _, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "9"}, `{"title":"TestLoremIpsum"}`)
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
t.Run("Shared Via User write", func(t *testing.T) { t.Run("Shared Via User write", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "10"}, `{"title":"TestLoremIpsum","namespace_id":6}`) rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "10"}, `{"title":"TestLoremIpsum","namespace_id":6}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`) assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
}) })
t.Run("Shared Via User admin", func(t *testing.T) { t.Run("Shared Via User admin", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "11"}, `{"title":"TestLoremIpsum","namespace_id":6}`) rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "11"}, `{"title":"TestLoremIpsum","namespace_id":6}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`) assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
}) })
t.Run("Shared Via NamespaceTeam readonly", func(t *testing.T) { t.Run("Shared Via NamespaceTeam readonly", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "12"}, `{"title":"TestLoremIpsum"}`) _, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "12"}, `{"title":"TestLoremIpsum"}`)
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
t.Run("Shared Via NamespaceTeam write", func(t *testing.T) { t.Run("Shared Via NamespaceTeam write", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "13"}, `{"title":"TestLoremIpsum","namespace_id":8}`) rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "13"}, `{"title":"TestLoremIpsum","namespace_id":8}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`) assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
}) })
t.Run("Shared Via NamespaceTeam admin", func(t *testing.T) { t.Run("Shared Via NamespaceTeam admin", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "14"}, `{"title":"TestLoremIpsum","namespace_id":9}`) rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "14"}, `{"title":"TestLoremIpsum","namespace_id":9}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`) assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
}) })
t.Run("Shared Via NamespaceUser readonly", func(t *testing.T) { t.Run("Shared Via NamespaceUser readonly", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "15"}, `{"title":"TestLoremIpsum"}`) _, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "15"}, `{"title":"TestLoremIpsum"}`)
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
t.Run("Shared Via NamespaceUser write", func(t *testing.T) { t.Run("Shared Via NamespaceUser write", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "16"}, `{"title":"TestLoremIpsum","namespace_id":11}`) rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "16"}, `{"title":"TestLoremIpsum","namespace_id":11}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`) assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
}) })
t.Run("Shared Via NamespaceUser admin", func(t *testing.T) { t.Run("Shared Via NamespaceUser admin", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "17"}, `{"title":"TestLoremIpsum","namespace_id":12}`) rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "17"}, `{"title":"TestLoremIpsum","namespace_id":12}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`) assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
}) })
@ -272,82 +272,82 @@ func TestList(t *testing.T) {
}) })
t.Run("Delete", func(t *testing.T) { t.Run("Delete", func(t *testing.T) {
t.Run("Normal", func(t *testing.T) { t.Run("Normal", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "1"}) rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "1"})
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`) assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
}) })
t.Run("Nonexisting", func(t *testing.T) { t.Run("Nonexisting", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "999"}) _, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "999"})
assert.Error(t, err) assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeListDoesNotExist) assertHandlerErrorCode(t, err, models.ErrCodeProjectDoesNotExist)
}) })
t.Run("Rights check", func(t *testing.T) { t.Run("Rights check", func(t *testing.T) {
t.Run("Forbidden", func(t *testing.T) { t.Run("Forbidden", func(t *testing.T) {
// Owned by user13 // Owned by user13
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "20"}) _, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "20"})
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
t.Run("Shared Via Team readonly", func(t *testing.T) { t.Run("Shared Via Team readonly", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "6"}) _, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "6"})
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
t.Run("Shared Via Team write", func(t *testing.T) { t.Run("Shared Via Team write", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "7"}) _, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "7"})
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
t.Run("Shared Via Team admin", func(t *testing.T) { t.Run("Shared Via Team admin", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "8"}) rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "8"})
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`) assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
}) })
t.Run("Shared Via User readonly", func(t *testing.T) { t.Run("Shared Via User readonly", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "9"}) _, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "9"})
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
t.Run("Shared Via User write", func(t *testing.T) { t.Run("Shared Via User write", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "10"}) _, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "10"})
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
t.Run("Shared Via User admin", func(t *testing.T) { t.Run("Shared Via User admin", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "11"}) rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "11"})
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`) assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
}) })
t.Run("Shared Via NamespaceTeam readonly", func(t *testing.T) { t.Run("Shared Via NamespaceTeam readonly", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "12"}) _, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "12"})
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
t.Run("Shared Via NamespaceTeam write", func(t *testing.T) { t.Run("Shared Via NamespaceTeam write", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "13"}) _, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "13"})
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
t.Run("Shared Via NamespaceTeam admin", func(t *testing.T) { t.Run("Shared Via NamespaceTeam admin", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "14"}) rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "14"})
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`) assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
}) })
t.Run("Shared Via NamespaceUser readonly", func(t *testing.T) { t.Run("Shared Via NamespaceUser readonly", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "15"}) _, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "15"})
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
t.Run("Shared Via NamespaceUser write", func(t *testing.T) { t.Run("Shared Via NamespaceUser write", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "16"}) _, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "16"})
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
t.Run("Shared Via NamespaceUser admin", func(t *testing.T) { t.Run("Shared Via NamespaceUser admin", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "17"}) rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "17"})
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`) assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
}) })
@ -355,7 +355,7 @@ func TestList(t *testing.T) {
}) })
t.Run("Create", func(t *testing.T) { t.Run("Create", func(t *testing.T) {
t.Run("Normal", func(t *testing.T) { t.Run("Normal", func(t *testing.T) {
// Check the list was loaded successfully after update, see testReadOneWithUser // Check the project was loaded successfully after update, see testReadOneWithUser
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"namespace": "1"}, `{"title":"Lorem"}`) rec, err := testHandler.testCreateWithUser(nil, map[string]string{"namespace": "1"}, `{"title":"Lorem"}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem"`) assert.Contains(t, rec.Body.String(), `"title":"Lorem"`)

View File

@ -33,9 +33,9 @@ func TestTaskCollection(t *testing.T) {
}, },
t: t, t: t,
} }
t.Run("ReadAll on list", func(t *testing.T) { t.Run("ReadAll on project", func(t *testing.T) {
urlParams := map[string]string{"list": "1"} urlParams := map[string]string{"project": "1"}
t.Run("Normal", func(t *testing.T) { t.Run("Normal", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(nil, urlParams) rec, err := testHandler.testReadAllWithUser(nil, urlParams)
@ -113,49 +113,49 @@ func TestTaskCollection(t *testing.T) {
t.Run("by priority", func(t *testing.T) { t.Run("by priority", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}}, urlParams) rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}}, urlParams)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`) assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`)
}) })
t.Run("by priority desc", func(t *testing.T) { t.Run("by priority desc", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"desc"}}, urlParams) rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"desc"}}, urlParams)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[{"id":3,"title":"task #3 high prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":100,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-3","index":3,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":4,"title":"task #4 low prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":1`) assert.Contains(t, rec.Body.String(), `[{"id":3,"title":"task #3 high prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":100,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-3","index":3,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":4,"title":"task #4 low prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":1`)
}) })
t.Run("by priority asc", func(t *testing.T) { t.Run("by priority asc", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"asc"}}, urlParams) rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"asc"}}, urlParams)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`) assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`)
}) })
// should equal duedate asc // should equal duedate asc
t.Run("by due_date", func(t *testing.T) { t.Run("by due_date", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}}, urlParams) rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}}, urlParams)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`) assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminder_dates":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`)
}) })
t.Run("by duedate desc", func(t *testing.T) { t.Run("by duedate desc", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"desc"}}, urlParams) rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"desc"}}, urlParams)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":6,"title":"task #6 lower due date`) assert.Contains(t, rec.Body.String(), `[{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminder_dates":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":6,"title":"task #6 lower due date`)
}) })
// Due date without unix suffix // Due date without unix suffix
t.Run("by duedate asc without suffix", func(t *testing.T) { t.Run("by duedate asc without suffix", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"asc"}}, urlParams) rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"asc"}}, urlParams)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`) assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminder_dates":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`)
}) })
t.Run("by due_date without suffix", func(t *testing.T) { t.Run("by due_date without suffix", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}}, urlParams) rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}}, urlParams)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`) assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminder_dates":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`)
}) })
t.Run("by duedate desc without suffix", func(t *testing.T) { t.Run("by duedate desc without suffix", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"desc"}}, urlParams) rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"desc"}}, urlParams)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":6,"title":"task #6 lower due date`) assert.Contains(t, rec.Body.String(), `[{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminder_dates":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":6,"title":"task #6 lower due date`)
}) })
t.Run("by duedate asc", func(t *testing.T) { t.Run("by duedate asc", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"asc"}}, urlParams) rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"asc"}}, urlParams)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`) assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminder_dates":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`)
}) })
t.Run("invalid sort parameter", func(t *testing.T) { t.Run("invalid sort parameter", func(t *testing.T) {
_, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"loremipsum"}}, urlParams) _, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"loremipsum"}}, urlParams)
@ -287,7 +287,7 @@ func TestTaskCollection(t *testing.T) {
t.Run("date range", func(t *testing.T) { t.Run("date range", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser( rec, err := testHandler.testReadAllWithUser(
nil, nil,
map[string]string{"list": "-2"}, // Actually a saved filter - contains the same filter arguments as the start and end date filter from above map[string]string{"project": "-2"}, // Actually a saved filter - contains the same filter arguments as the start and end date filter from above
) )
assert.NoError(t, err) assert.NoError(t, err)
assert.NotContains(t, rec.Body.String(), `task #1`) assert.NotContains(t, rec.Body.String(), `task #1`)
@ -341,7 +341,7 @@ func TestTaskCollection(t *testing.T) {
assert.Contains(t, rec.Body.String(), `task #24`) // Shared via namespace user readonly assert.Contains(t, rec.Body.String(), `task #24`) // Shared via namespace user readonly
assert.Contains(t, rec.Body.String(), `task #25`) // Shared via namespace user write assert.Contains(t, rec.Body.String(), `task #25`) // Shared via namespace user write
assert.Contains(t, rec.Body.String(), `task #26`) // Shared via namespace user admin assert.Contains(t, rec.Body.String(), `task #26`) // Shared via namespace user admin
// TODO: Add some cases where the user has access to the list, somhow shared // TODO: Add some cases where the user has access to the project, somhow shared
}) })
t.Run("Search", func(t *testing.T) { t.Run("Search", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"s": []string{"task #6"}}, nil) rec, err := testHandler.testReadAllWithUser(url.Values{"s": []string{"task #6"}}, nil)
@ -366,33 +366,33 @@ func TestTaskCollection(t *testing.T) {
t.Run("by priority", func(t *testing.T) { t.Run("by priority", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}}, nil) rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}}, nil)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`) assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`)
}) })
t.Run("by priority desc", func(t *testing.T) { t.Run("by priority desc", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"desc"}}, nil) rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"desc"}}, nil)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[{"id":3,"title":"task #3 high prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":100,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-3","index":3,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":4,"title":"task #4 low prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":1`) assert.Contains(t, rec.Body.String(), `[{"id":3,"title":"task #3 high prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":100,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-3","index":3,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":4,"title":"task #4 low prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":1`)
}) })
t.Run("by priority asc", func(t *testing.T) { t.Run("by priority asc", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"asc"}}, nil) rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"asc"}}, nil)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`) assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`)
}) })
// should equal duedate asc // should equal duedate asc
t.Run("by due_date", func(t *testing.T) { t.Run("by due_date", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}}, nil) rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}}, nil)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`) assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminder_dates":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminder_dates":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`)
}) })
t.Run("by duedate desc", func(t *testing.T) { t.Run("by duedate desc", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"desc"}}, nil) rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"desc"}}, nil)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":6,"title":"task #6 lower due date`) assert.Contains(t, rec.Body.String(), `[{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminder_dates":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":6,"title":"task #6 lower due date`)
}) })
t.Run("by duedate asc", func(t *testing.T) { t.Run("by duedate asc", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"asc"}}, nil) rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"asc"}}, nil)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`) assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminder_dates":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminder_dates":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`)
}) })
t.Run("invalid parameter", func(t *testing.T) { t.Run("invalid parameter", func(t *testing.T) {
// Invalid parameter should not sort at all // Invalid parameter should not sort at all

View File

@ -39,7 +39,7 @@ func TestTaskComments(t *testing.T) {
linkShare: &models.LinkSharing{ linkShare: &models.LinkSharing{
ID: 2, ID: 2,
Hash: "test2", Hash: "test2",
ListID: 2, ProjectID: 2,
Right: models.RightWrite, Right: models.RightWrite,
SharingType: models.SharingTypeWithoutPassword, SharingType: models.SharingTypeWithoutPassword,
SharedByID: 1, SharedByID: 1,

View File

@ -39,7 +39,7 @@ func TestTask(t *testing.T) {
linkShare: &models.LinkSharing{ linkShare: &models.LinkSharing{
ID: 2, ID: 2,
Hash: "test2", Hash: "test2",
ListID: 2, ProjectID: 2,
Right: models.RightWrite, Right: models.RightWrite,
SharingType: models.SharingTypeWithoutPassword, SharingType: models.SharingTypeWithoutPassword,
SharedByID: 1, SharedByID: 1,
@ -54,157 +54,157 @@ func TestTask(t *testing.T) {
t.Run("Update", func(t *testing.T) { t.Run("Update", func(t *testing.T) {
t.Run("Update task items", func(t *testing.T) { t.Run("Update task items", func(t *testing.T) {
t.Run("Title", func(t *testing.T) { t.Run("Title", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"title":"Lorem Ipsum"}`) rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "1"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`) assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
assert.NotContains(t, rec.Body.String(), `"title":"task #1"`) assert.NotContains(t, rec.Body.String(), `"title":"task #1"`)
}) })
t.Run("Description", func(t *testing.T) { t.Run("Description", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"description":"Dolor sit amet"}`) rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "1"}, `{"description":"Dolor sit amet"}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"description":"Dolor sit amet"`) assert.Contains(t, rec.Body.String(), `"description":"Dolor sit amet"`)
assert.NotContains(t, rec.Body.String(), `"description":"Lorem Ipsum"`) assert.NotContains(t, rec.Body.String(), `"description":"Lorem Ipsum"`)
}) })
t.Run("Description to empty", func(t *testing.T) { t.Run("Description to empty", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"description":""}`) rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "1"}, `{"description":""}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"description":""`) assert.Contains(t, rec.Body.String(), `"description":""`)
assert.NotContains(t, rec.Body.String(), `"description":"Lorem Ipsum"`) assert.NotContains(t, rec.Body.String(), `"description":"Lorem Ipsum"`)
}) })
t.Run("Done", func(t *testing.T) { t.Run("Done", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"done":true}`) rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "1"}, `{"done":true}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"done":true`) assert.Contains(t, rec.Body.String(), `"done":true`)
assert.NotContains(t, rec.Body.String(), `"done":false`) assert.NotContains(t, rec.Body.String(), `"done":false`)
}) })
t.Run("Undone", func(t *testing.T) { t.Run("Undone", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "2"}, `{"done":false}`) rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "2"}, `{"done":false}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"done":false`) assert.Contains(t, rec.Body.String(), `"done":false`)
assert.NotContains(t, rec.Body.String(), `"done":true`) assert.NotContains(t, rec.Body.String(), `"done":true`)
}) })
t.Run("Due date", func(t *testing.T) { t.Run("Due date", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"due_date": "2020-02-10T10:00:00Z"}`) rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "1"}, `{"due_date": "2020-02-10T10:00:00Z"}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"due_date":"2020-02-10T10:00:00Z"`) assert.Contains(t, rec.Body.String(), `"due_date":"2020-02-10T10:00:00Z"`)
assert.NotContains(t, rec.Body.String(), `"due_date":0`) assert.NotContains(t, rec.Body.String(), `"due_date":0`)
}) })
t.Run("Due date unset", func(t *testing.T) { t.Run("Due date unset", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "5"}, `{"due_date": null}`) rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "5"}, `{"due_date": null}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"due_date":"0001-01-01T00:00:00Z"`) assert.Contains(t, rec.Body.String(), `"due_date":"0001-01-01T00:00:00Z"`)
assert.NotContains(t, rec.Body.String(), `"due_date":"2020-02-10T10:00:00Z"`) assert.NotContains(t, rec.Body.String(), `"due_date":"2020-02-10T10:00:00Z"`)
}) })
t.Run("Reminders", func(t *testing.T) { t.Run("Reminders", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"reminder_dates": ["2020-02-10T10:00:00Z","2020-02-11T10:00:00Z"]}`) rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "1"}, `{"reminder_dates": ["2020-02-10T10:00:00Z","2020-02-11T10:00:00Z"]}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"reminder_dates":["2020-02-10T10:00:00Z","2020-02-11T10:00:00Z"]`) assert.Contains(t, rec.Body.String(), `"reminder_dates":["2020-02-10T10:00:00Z","2020-02-11T10:00:00Z"]`)
assert.NotContains(t, rec.Body.String(), `"reminder_dates": null`) assert.NotContains(t, rec.Body.String(), `"reminder_dates": null`)
}) })
t.Run("Reminders unset to empty array", func(t *testing.T) { t.Run("Reminders unset to empty array", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "27"}, `{"reminder_dates": []}`) rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "27"}, `{"reminder_dates": []}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"reminder_dates":null`) assert.Contains(t, rec.Body.String(), `"reminder_dates":null`)
assert.NotContains(t, rec.Body.String(), `"reminder_dates":[1543626724,1543626824]`) assert.NotContains(t, rec.Body.String(), `"reminder_dates":[1543626724,1543626824]`)
}) })
t.Run("Reminders unset to null", func(t *testing.T) { t.Run("Reminders unset to null", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "27"}, `{"reminder_dates": null}`) rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "27"}, `{"reminder_dates": null}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"reminder_dates":null`) assert.Contains(t, rec.Body.String(), `"reminder_dates":null`)
assert.NotContains(t, rec.Body.String(), `"reminder_dates":[1543626724,1543626824]`) assert.NotContains(t, rec.Body.String(), `"reminder_dates":[1543626724,1543626824]`)
}) })
t.Run("Repeat after", func(t *testing.T) { t.Run("Repeat after", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"repeat_after":3600}`) rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "1"}, `{"repeat_after":3600}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"repeat_after":3600`) assert.Contains(t, rec.Body.String(), `"repeat_after":3600`)
assert.NotContains(t, rec.Body.String(), `"repeat_after":0`) assert.NotContains(t, rec.Body.String(), `"repeat_after":0`)
}) })
t.Run("Repeat after unset", func(t *testing.T) { t.Run("Repeat after unset", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "28"}, `{"repeat_after":0}`) rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "28"}, `{"repeat_after":0}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"repeat_after":0`) assert.Contains(t, rec.Body.String(), `"repeat_after":0`)
assert.NotContains(t, rec.Body.String(), `"repeat_after":3600`) assert.NotContains(t, rec.Body.String(), `"repeat_after":3600`)
}) })
t.Run("Repeat after update done", func(t *testing.T) { t.Run("Repeat after update done", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "28"}, `{"done":true}`) rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "28"}, `{"done":true}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"done":false`) assert.Contains(t, rec.Body.String(), `"done":false`)
assert.NotContains(t, rec.Body.String(), `"done":true`) assert.NotContains(t, rec.Body.String(), `"done":true`)
}) })
t.Run("Assignees", func(t *testing.T) { t.Run("Assignees", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"assignees":[{"id":1}]}`) rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "1"}, `{"assignees":[{"id":1}]}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"assignees":[{"id":1`) assert.Contains(t, rec.Body.String(), `"assignees":[{"id":1`)
assert.NotContains(t, rec.Body.String(), `"assignees":[]`) assert.NotContains(t, rec.Body.String(), `"assignees":[]`)
}) })
t.Run("Removing Assignees empty array", func(t *testing.T) { t.Run("Removing Assignees empty array", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "30"}, `{"assignees":[]}`) rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "30"}, `{"assignees":[]}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"assignees":null`) assert.Contains(t, rec.Body.String(), `"assignees":null`)
assert.NotContains(t, rec.Body.String(), `"assignees":[{"id":1`) assert.NotContains(t, rec.Body.String(), `"assignees":[{"id":1`)
}) })
t.Run("Removing Assignees null", func(t *testing.T) { t.Run("Removing Assignees null", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "30"}, `{"assignees":null}`) rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "30"}, `{"assignees":null}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"assignees":null`) assert.Contains(t, rec.Body.String(), `"assignees":null`)
assert.NotContains(t, rec.Body.String(), `"assignees":[{"id":1`) assert.NotContains(t, rec.Body.String(), `"assignees":[{"id":1`)
}) })
t.Run("Priority", func(t *testing.T) { t.Run("Priority", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"priority":100}`) rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "1"}, `{"priority":100}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"priority":100`) assert.Contains(t, rec.Body.String(), `"priority":100`)
assert.NotContains(t, rec.Body.String(), `"priority":0`) assert.NotContains(t, rec.Body.String(), `"priority":0`)
}) })
t.Run("Priority to 0", func(t *testing.T) { t.Run("Priority to 0", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "3"}, `{"priority":0}`) rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "3"}, `{"priority":0}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"priority":0`) assert.Contains(t, rec.Body.String(), `"priority":0`)
assert.NotContains(t, rec.Body.String(), `"priority":100`) assert.NotContains(t, rec.Body.String(), `"priority":100`)
}) })
t.Run("Start date", func(t *testing.T) { t.Run("Start date", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"start_date":"2020-02-10T10:00:00Z"}`) rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "1"}, `{"start_date":"2020-02-10T10:00:00Z"}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"start_date":"2020-02-10T10:00:00Z"`) assert.Contains(t, rec.Body.String(), `"start_date":"2020-02-10T10:00:00Z"`)
assert.NotContains(t, rec.Body.String(), `"start_date":0`) assert.NotContains(t, rec.Body.String(), `"start_date":0`)
}) })
t.Run("Start date unset", func(t *testing.T) { t.Run("Start date unset", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "7"}, `{"start_date":"0001-01-01T00:00:00Z"}`) rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "7"}, `{"start_date":"0001-01-01T00:00:00Z"}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"start_date":"0001-01-01T00:00:00Z"`) assert.Contains(t, rec.Body.String(), `"start_date":"0001-01-01T00:00:00Z"`)
assert.NotContains(t, rec.Body.String(), `"start_date":"2020-02-10T10:00:00Z"`) assert.NotContains(t, rec.Body.String(), `"start_date":"2020-02-10T10:00:00Z"`)
}) })
t.Run("End date", func(t *testing.T) { t.Run("End date", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"end_date":"2020-02-10T12:00:00Z"}`) rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "1"}, `{"end_date":"2020-02-10T12:00:00Z"}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"end_date":"2020-02-10T12:00:00Z"`) assert.Contains(t, rec.Body.String(), `"end_date":"2020-02-10T12:00:00Z"`)
assert.NotContains(t, rec.Body.String(), `"end_date":""`) assert.NotContains(t, rec.Body.String(), `"end_date":""`)
}) })
t.Run("End date unset", func(t *testing.T) { t.Run("End date unset", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "8"}, `{"end_date":"0001-01-01T00:00:00Z"}`) rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "8"}, `{"end_date":"0001-01-01T00:00:00Z"}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"end_date":"0001-01-01T00:00:00Z"`) assert.Contains(t, rec.Body.String(), `"end_date":"0001-01-01T00:00:00Z"`)
assert.NotContains(t, rec.Body.String(), `"end_date":"2020-02-10T10:00:00Z"`) assert.NotContains(t, rec.Body.String(), `"end_date":"2020-02-10T10:00:00Z"`)
}) })
t.Run("Color", func(t *testing.T) { t.Run("Color", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"hex_color":"f0f0f0"}`) rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "1"}, `{"hex_color":"f0f0f0"}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"hex_color":"f0f0f0"`) assert.Contains(t, rec.Body.String(), `"hex_color":"f0f0f0"`)
assert.NotContains(t, rec.Body.String(), `"hex_color":""`) assert.NotContains(t, rec.Body.String(), `"hex_color":""`)
}) })
t.Run("Color unset", func(t *testing.T) { t.Run("Color unset", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "31"}, `{"hex_color":""}`) rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "31"}, `{"hex_color":""}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"hex_color":""`) assert.Contains(t, rec.Body.String(), `"hex_color":""`)
assert.NotContains(t, rec.Body.String(), `"hex_color":"f0f0f0"`) assert.NotContains(t, rec.Body.String(), `"hex_color":"f0f0f0"`)
}) })
t.Run("Percent Done", func(t *testing.T) { t.Run("Percent Done", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"percent_done":0.1}`) rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "1"}, `{"percent_done":0.1}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"percent_done":0.1`) assert.Contains(t, rec.Body.String(), `"percent_done":0.1`)
assert.NotContains(t, rec.Body.String(), `"percent_done":0,`) assert.NotContains(t, rec.Body.String(), `"percent_done":0,`)
}) })
t.Run("Percent Done unset", func(t *testing.T) { t.Run("Percent Done unset", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "33"}, `{"percent_done":0}`) rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "33"}, `{"percent_done":0}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"percent_done":0,`) assert.Contains(t, rec.Body.String(), `"percent_done":0,`)
assert.NotContains(t, rec.Body.String(), `"percent_done":0.1`) assert.NotContains(t, rec.Body.String(), `"percent_done":0.1`)
@ -212,112 +212,112 @@ func TestTask(t *testing.T) {
}) })
t.Run("Nonexisting", func(t *testing.T) { t.Run("Nonexisting", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "99999"}, `{"title":"Lorem Ipsum"}`) _, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "99999"}, `{"title":"Lorem Ipsum"}`)
assert.Error(t, err) assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeTaskDoesNotExist) assertHandlerErrorCode(t, err, models.ErrCodeTaskDoesNotExist)
}) })
t.Run("Rights check", func(t *testing.T) { t.Run("Rights check", func(t *testing.T) {
t.Run("Forbidden", func(t *testing.T) { t.Run("Forbidden", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "14"}, `{"title":"Lorem Ipsum"}`) _, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "14"}, `{"title":"Lorem Ipsum"}`)
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
t.Run("Shared Via Team readonly", func(t *testing.T) { t.Run("Shared Via Team readonly", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "15"}, `{"title":"Lorem Ipsum"}`) _, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "15"}, `{"title":"Lorem Ipsum"}`)
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
t.Run("Shared Via Team write", func(t *testing.T) { t.Run("Shared Via Team write", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "16"}, `{"title":"Lorem Ipsum"}`) rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "16"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`) assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
}) })
t.Run("Shared Via Team admin", func(t *testing.T) { t.Run("Shared Via Team admin", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "17"}, `{"title":"Lorem Ipsum"}`) rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "17"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`) assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
}) })
t.Run("Shared Via User readonly", func(t *testing.T) { t.Run("Shared Via User readonly", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "18"}, `{"title":"Lorem Ipsum"}`) _, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "18"}, `{"title":"Lorem Ipsum"}`)
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
t.Run("Shared Via User write", func(t *testing.T) { t.Run("Shared Via User write", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "19"}, `{"title":"Lorem Ipsum"}`) rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "19"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`) assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
}) })
t.Run("Shared Via User admin", func(t *testing.T) { t.Run("Shared Via User admin", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "20"}, `{"title":"Lorem Ipsum"}`) rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "20"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`) assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
}) })
t.Run("Shared Via NamespaceTeam readonly", func(t *testing.T) { t.Run("Shared Via NamespaceTeam readonly", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "21"}, `{"title":"Lorem Ipsum"}`) _, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "21"}, `{"title":"Lorem Ipsum"}`)
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
t.Run("Shared Via NamespaceTeam write", func(t *testing.T) { t.Run("Shared Via NamespaceTeam write", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "22"}, `{"title":"Lorem Ipsum"}`) rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "22"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`) assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
}) })
t.Run("Shared Via NamespaceTeam admin", func(t *testing.T) { t.Run("Shared Via NamespaceTeam admin", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "23"}, `{"title":"Lorem Ipsum"}`) rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "23"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`) assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
}) })
t.Run("Shared Via NamespaceUser readonly", func(t *testing.T) { t.Run("Shared Via NamespaceUser readonly", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "24"}, `{"title":"Lorem Ipsum"}`) _, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "24"}, `{"title":"Lorem Ipsum"}`)
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
t.Run("Shared Via NamespaceUser write", func(t *testing.T) { t.Run("Shared Via NamespaceUser write", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "25"}, `{"title":"Lorem Ipsum"}`) rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "25"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`) assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
}) })
t.Run("Shared Via NamespaceUser admin", func(t *testing.T) { t.Run("Shared Via NamespaceUser admin", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "26"}, `{"title":"Lorem Ipsum"}`) rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "26"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`) assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
}) })
}) })
t.Run("Move to other list", func(t *testing.T) { t.Run("Move to other project", func(t *testing.T) {
t.Run("normal", func(t *testing.T) { t.Run("normal", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"list_id":7}`) rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "1"}, `{"project_id":7}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"list_id":7`) assert.Contains(t, rec.Body.String(), `"project_id":7`)
assert.NotContains(t, rec.Body.String(), `"list_id":1`) assert.NotContains(t, rec.Body.String(), `"project_id":1`)
}) })
t.Run("Forbidden", func(t *testing.T) { t.Run("Forbidden", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"list_id":20}`) _, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "1"}, `{"project_id":20}`)
assert.Error(t, err) assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrorCodeGenericForbidden) assertHandlerErrorCode(t, err, models.ErrorCodeGenericForbidden)
}) })
t.Run("Read Only", func(t *testing.T) { t.Run("Read Only", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"list_id":6}`) _, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "1"}, `{"project_id":6}`)
assert.Error(t, err) assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrorCodeGenericForbidden) assertHandlerErrorCode(t, err, models.ErrorCodeGenericForbidden)
}) })
}) })
t.Run("Bucket", func(t *testing.T) { t.Run("Bucket", func(t *testing.T) {
t.Run("Normal", func(t *testing.T) { t.Run("Normal", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"bucket_id":3}`) rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "1"}, `{"bucket_id":3}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"bucket_id":3`) assert.Contains(t, rec.Body.String(), `"bucket_id":3`)
assert.NotContains(t, rec.Body.String(), `"bucket_id":1`) assert.NotContains(t, rec.Body.String(), `"bucket_id":1`)
}) })
t.Run("Different List", func(t *testing.T) { t.Run("Different Project", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"bucket_id":4}`) _, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "1"}, `{"bucket_id":4}`)
assert.Error(t, err) assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeBucketDoesNotBelongToList) assertHandlerErrorCode(t, err, models.ErrCodeBucketDoesNotBelongToProject)
}) })
t.Run("Nonexisting Bucket", func(t *testing.T) { t.Run("Nonexisting Bucket", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"bucket_id":9999}`) _, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "1"}, `{"bucket_id":9999}`)
assert.Error(t, err) assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeBucketDoesNotExist) assertHandlerErrorCode(t, err, models.ErrCodeBucketDoesNotExist)
}) })
@ -325,81 +325,81 @@ func TestTask(t *testing.T) {
}) })
t.Run("Delete", func(t *testing.T) { t.Run("Delete", func(t *testing.T) {
t.Run("Normal", func(t *testing.T) { t.Run("Normal", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"listtask": "1"}) rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "1"})
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `Successfully deleted.`) assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
}) })
t.Run("Nonexisting", func(t *testing.T) { t.Run("Nonexisting", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"listtask": "99999"}) _, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "99999"})
assert.Error(t, err) assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeTaskDoesNotExist) assertHandlerErrorCode(t, err, models.ErrCodeTaskDoesNotExist)
}) })
t.Run("Rights check", func(t *testing.T) { t.Run("Rights check", func(t *testing.T) {
t.Run("Forbidden", func(t *testing.T) { t.Run("Forbidden", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"listtask": "14"}) _, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "14"})
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
t.Run("Shared Via Team readonly", func(t *testing.T) { t.Run("Shared Via Team readonly", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"listtask": "15"}) _, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "15"})
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
t.Run("Shared Via Team write", func(t *testing.T) { t.Run("Shared Via Team write", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"listtask": "16"}) rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "16"})
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `Successfully deleted.`) assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
}) })
t.Run("Shared Via Team admin", func(t *testing.T) { t.Run("Shared Via Team admin", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"listtask": "17"}) rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "17"})
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `Successfully deleted.`) assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
}) })
t.Run("Shared Via User readonly", func(t *testing.T) { t.Run("Shared Via User readonly", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"listtask": "18"}) _, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "18"})
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
t.Run("Shared Via User write", func(t *testing.T) { t.Run("Shared Via User write", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"listtask": "19"}) rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "19"})
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `Successfully deleted.`) assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
}) })
t.Run("Shared Via User admin", func(t *testing.T) { t.Run("Shared Via User admin", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"listtask": "20"}) rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "20"})
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `Successfully deleted.`) assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
}) })
t.Run("Shared Via NamespaceTeam readonly", func(t *testing.T) { t.Run("Shared Via NamespaceTeam readonly", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"listtask": "21"}) _, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "21"})
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
t.Run("Shared Via NamespaceTeam write", func(t *testing.T) { t.Run("Shared Via NamespaceTeam write", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"listtask": "22"}) rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "22"})
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `Successfully deleted.`) assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
}) })
t.Run("Shared Via NamespaceTeam admin", func(t *testing.T) { t.Run("Shared Via NamespaceTeam admin", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"listtask": "23"}) rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "23"})
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `Successfully deleted.`) assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
}) })
t.Run("Shared Via NamespaceUser readonly", func(t *testing.T) { t.Run("Shared Via NamespaceUser readonly", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"listtask": "24"}) _, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "24"})
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
t.Run("Shared Via NamespaceUser write", func(t *testing.T) { t.Run("Shared Via NamespaceUser write", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"listtask": "25"}) rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "25"})
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `Successfully deleted.`) assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
}) })
t.Run("Shared Via NamespaceUser admin", func(t *testing.T) { t.Run("Shared Via NamespaceUser admin", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"listtask": "26"}) rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "26"})
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `Successfully deleted.`) assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
}) })
@ -407,110 +407,110 @@ func TestTask(t *testing.T) {
}) })
t.Run("Create", func(t *testing.T) { t.Run("Create", func(t *testing.T) {
t.Run("Normal", func(t *testing.T) { t.Run("Normal", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "1"}, `{"title":"Lorem Ipsum"}`) rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "1"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`) assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
}) })
t.Run("Nonexisting", func(t *testing.T) { t.Run("Nonexisting", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "9999"}, `{"title":"Lorem Ipsum"}`) _, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "9999"}, `{"title":"Lorem Ipsum"}`)
assert.Error(t, err) assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeListDoesNotExist) assertHandlerErrorCode(t, err, models.ErrCodeProjectDoesNotExist)
}) })
t.Run("Rights check", func(t *testing.T) { t.Run("Rights check", func(t *testing.T) {
t.Run("Forbidden", func(t *testing.T) { t.Run("Forbidden", func(t *testing.T) {
// Owned by user13 // Owned by user13
_, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "20"}, `{"title":"Lorem Ipsum"}`) _, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "20"}, `{"title":"Lorem Ipsum"}`)
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
t.Run("Shared Via Team readonly", func(t *testing.T) { t.Run("Shared Via Team readonly", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "6"}, `{"title":"Lorem Ipsum"}`) _, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "6"}, `{"title":"Lorem Ipsum"}`)
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
t.Run("Shared Via Team write", func(t *testing.T) { t.Run("Shared Via Team write", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "7"}, `{"title":"Lorem Ipsum"}`) rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "7"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`) assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
}) })
t.Run("Shared Via Team admin", func(t *testing.T) { t.Run("Shared Via Team admin", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "8"}, `{"title":"Lorem Ipsum"}`) rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "8"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`) assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
}) })
t.Run("Shared Via User readonly", func(t *testing.T) { t.Run("Shared Via User readonly", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "9"}, `{"title":"Lorem Ipsum"}`) _, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "9"}, `{"title":"Lorem Ipsum"}`)
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
t.Run("Shared Via User write", func(t *testing.T) { t.Run("Shared Via User write", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "10"}, `{"title":"Lorem Ipsum"}`) rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "10"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`) assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
}) })
t.Run("Shared Via User admin", func(t *testing.T) { t.Run("Shared Via User admin", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "11"}, `{"title":"Lorem Ipsum"}`) rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "11"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`) assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
}) })
t.Run("Shared Via NamespaceTeam readonly", func(t *testing.T) { t.Run("Shared Via NamespaceTeam readonly", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "12"}, `{"title":"Lorem Ipsum"}`) _, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "12"}, `{"title":"Lorem Ipsum"}`)
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
t.Run("Shared Via NamespaceTeam write", func(t *testing.T) { t.Run("Shared Via NamespaceTeam write", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "13"}, `{"title":"Lorem Ipsum"}`) rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "13"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`) assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
}) })
t.Run("Shared Via NamespaceTeam admin", func(t *testing.T) { t.Run("Shared Via NamespaceTeam admin", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "14"}, `{"title":"Lorem Ipsum"}`) rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "14"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`) assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
}) })
t.Run("Shared Via NamespaceUser readonly", func(t *testing.T) { t.Run("Shared Via NamespaceUser readonly", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "15"}, `{"title":"Lorem Ipsum"}`) _, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "15"}, `{"title":"Lorem Ipsum"}`)
assert.Error(t, err) assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
}) })
t.Run("Shared Via NamespaceUser write", func(t *testing.T) { t.Run("Shared Via NamespaceUser write", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "16"}, `{"title":"Lorem Ipsum"}`) rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "16"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`) assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
}) })
t.Run("Shared Via NamespaceUser admin", func(t *testing.T) { t.Run("Shared Via NamespaceUser admin", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "17"}, `{"title":"Lorem Ipsum"}`) rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "17"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`) assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
}) })
}) })
t.Run("Bucket", func(t *testing.T) { t.Run("Bucket", func(t *testing.T) {
t.Run("Normal", func(t *testing.T) { t.Run("Normal", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "1"}, `{"title":"Lorem Ipsum","bucket_id":3}`) rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "1"}, `{"title":"Lorem Ipsum","bucket_id":3}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"bucket_id":3`) assert.Contains(t, rec.Body.String(), `"bucket_id":3`)
assert.NotContains(t, rec.Body.String(), `"bucket_id":1`) assert.NotContains(t, rec.Body.String(), `"bucket_id":1`)
}) })
t.Run("Different List", func(t *testing.T) { t.Run("Different Project", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "1"}, `{"title":"Lorem Ipsum","bucket_id":4}`) _, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "1"}, `{"title":"Lorem Ipsum","bucket_id":4}`)
assert.Error(t, err) assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeBucketDoesNotBelongToList) assertHandlerErrorCode(t, err, models.ErrCodeBucketDoesNotBelongToProject)
}) })
t.Run("Nonexisting Bucket", func(t *testing.T) { t.Run("Nonexisting Bucket", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "1"}, `{"title":"Lorem Ipsum","bucket_id":9999}`) _, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "1"}, `{"title":"Lorem Ipsum","bucket_id":9999}`)
assert.Error(t, err) assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeBucketDoesNotExist) assertHandlerErrorCode(t, err, models.ErrCodeBucketDoesNotExist)
}) })
}) })
t.Run("Link Share", func(t *testing.T) { t.Run("Link Share", func(t *testing.T) {
rec, err := testHandlerLinkShareWrite.testCreateWithLinkShare(nil, map[string]string{"list": "2"}, `{"title":"Lorem Ipsum"}`) rec, err := testHandlerLinkShareWrite.testCreateWithLinkShare(nil, map[string]string{"project": "2"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`) assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
db.AssertExists(t, "tasks", map[string]interface{}{ db.AssertExists(t, "tasks", map[string]interface{}{
"list_id": 2, "project_id": 2,
"title": "Lorem Ipsum", "title": "Lorem Ipsum",
"created_by_id": -2, "created_by_id": -2,
}, false) }, false)

View File

@ -24,14 +24,14 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestUserList(t *testing.T) { func TestUserProject(t *testing.T) {
t.Run("Normal test", func(t *testing.T) { t.Run("Normal test", func(t *testing.T) {
rec, err := newTestRequestWithUser(t, http.MethodPost, apiv1.UserList, &testuser1, "", nil, nil) rec, err := newTestRequestWithUser(t, http.MethodPost, apiv1.UserProject, &testuser1, "", nil, nil)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, "null\n", rec.Body.String()) assert.Equal(t, "null\n", rec.Body.String())
}) })
t.Run("Search for user3", func(t *testing.T) { t.Run("Search for user3", func(t *testing.T) {
rec, err := newTestRequestWithUser(t, http.MethodPost, apiv1.UserList, &testuser1, "", map[string][]string{"s": {"user3"}}, nil) rec, err := newTestRequestWithUser(t, http.MethodPost, apiv1.UserProject, &testuser1, "", map[string][]string{"s": {"user3"}}, nil)
assert.NoError(t, err) assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `user3`) assert.Contains(t, rec.Body.String(), `user3`)
assert.NotContains(t, rec.Body.String(), `user1`) assert.NotContains(t, rec.Body.String(), `user1`)

View File

@ -28,8 +28,8 @@ import (
) )
const ( const (
// ListCountKey is the name of the key in which we save the list count // ProjectCountKey is the name of the key in which we save the project count
ListCountKey = `listcount` ProjectCountKey = `projectcount`
// UserCountKey is the name of the key we use to store total users in redis // UserCountKey is the name of the key we use to store total users in redis
UserCountKey = `usercount` UserCountKey = `usercount`
@ -65,16 +65,16 @@ func InitMetrics() {
GetRegistry() GetRegistry()
// Register total list count metric // Register total project count metric
err := registry.Register(promauto.NewGaugeFunc(prometheus.GaugeOpts{ err := registry.Register(promauto.NewGaugeFunc(prometheus.GaugeOpts{
Name: "vikunja_list_count", Name: "vikunja_project_count",
Help: "The number of lists on this instance", Help: "The number of projects on this instance",
}, func() float64 { }, func() float64 {
count, _ := GetCount(ListCountKey) count, _ := GetCount(ProjectCountKey)
return float64(count) return float64(count)
})) }))
if err != nil { if err != nil {
log.Criticalf("Could not register metrics for %s: %s", ListCountKey, err) log.Criticalf("Could not register metrics for %s: %s", ProjectCountKey, err)
} }
// Register total user count metric // Register total user count metric
@ -147,7 +147,7 @@ func GetCount(key string) (count int64, err error) {
return return
} }
// SetCount sets the list count to a given value // SetCount sets the project count to a given value
func SetCount(count int64, key string) error { func SetCount(count int64, key string) error {
return keyvalue.Put(key, count) return keyvalue.Put(key, count)
} }

View File

@ -24,13 +24,13 @@ import (
// BulkTask is the definition of a bulk update task // BulkTask is the definition of a bulk update task
type BulkTask struct { type BulkTask struct {
// A list of task ids to update // A project of task ids to update
IDs []int64 `json:"task_ids"` IDs []int64 `json:"task_ids"`
Tasks []*Task `json:"-"` Tasks []*Task `json:"-"`
Task Task
} }
func (bt *BulkTask) checkIfTasksAreOnTheSameList(s *xorm.Session) (err error) { func (bt *BulkTask) checkIfTasksAreOnTheSameProject(s *xorm.Session) (err error) {
// Get the tasks // Get the tasks
err = bt.GetTasksByIDs(s) err = bt.GetTasksByIDs(s)
if err != nil { if err != nil {
@ -41,11 +41,11 @@ func (bt *BulkTask) checkIfTasksAreOnTheSameList(s *xorm.Session) (err error) {
return ErrBulkTasksNeedAtLeastOne{} return ErrBulkTasksNeedAtLeastOne{}
} }
// Check if all tasks are in the same list // Check if all tasks are in the same project
var firstListID = bt.Tasks[0].ListID var firstProjectID = bt.Tasks[0].ProjectID
for _, t := range bt.Tasks { for _, t := range bt.Tasks {
if t.ListID != firstListID { if t.ProjectID != firstProjectID {
return ErrBulkTasksMustBeInSameList{firstListID, t.ListID} return ErrBulkTasksMustBeInSameProject{firstProjectID, t.ProjectID}
} }
} }
@ -55,13 +55,13 @@ func (bt *BulkTask) checkIfTasksAreOnTheSameList(s *xorm.Session) (err error) {
// CanUpdate checks if a user is allowed to update a task // CanUpdate checks if a user is allowed to update a task
func (bt *BulkTask) CanUpdate(s *xorm.Session, a web.Auth) (bool, error) { func (bt *BulkTask) CanUpdate(s *xorm.Session, a web.Auth) (bool, error) {
err := bt.checkIfTasksAreOnTheSameList(s) err := bt.checkIfTasksAreOnTheSameProject(s)
if err != nil { if err != nil {
return false, err return false, err
} }
// A user can update an task if he has write acces to its list // A user can update an task if he has write acces to its project
l := &List{ID: bt.Tasks[0].ListID} l := &Project{ID: bt.Tasks[0].ProjectID}
return l.CanWrite(s, a) return l.CanWrite(s, a)
} }
@ -72,10 +72,10 @@ func (bt *BulkTask) CanUpdate(s *xorm.Session, a web.Auth) (bool, error) {
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Security JWTKeyAuth // @Security JWTKeyAuth
// @Param task body models.BulkTask true "The task object. Looks like a normal task, the only difference is it uses an array of list_ids to update." // @Param task body models.BulkTask true "The task object. Looks like a normal task, the only difference is it uses an array of project_ids to update."
// @Success 200 {object} models.Task "The updated task object." // @Success 200 {object} models.Task "The updated task object."
// @Failure 400 {object} web.HTTPError "Invalid task object provided." // @Failure 400 {object} web.HTTPError "Invalid task object provided."
// @Failure 403 {object} web.HTTPError "The user does not have access to the task (aka its list)" // @Failure 403 {object} web.HTTPError "The user does not have access to the task (aka its project)"
// @Failure 500 {object} models.Message "Internal error" // @Failure 500 {object} models.Message "Internal error"
// @Router /tasks/bulk [post] // @Router /tasks/bulk [post]
func (bt *BulkTask) Update(s *xorm.Session, a web.Auth) (err error) { func (bt *BulkTask) Update(s *xorm.Session, a web.Auth) (err error) {

View File

@ -47,7 +47,7 @@ func TestBulkTask_Update(t *testing.T) {
}, },
}, },
{ {
name: "Test with one task on different list", name: "Test with one task on different project",
fields: fields{ fields: fields{
IDs: []int64{10, 11, 12, 13}, IDs: []int64{10, 11, 12, 13},
Task: Task{ Task: Task{

View File

@ -110,222 +110,222 @@ func (err ValidationHTTPError) Error() string {
} }
// =========== // ===========
// List errors // Project errors
// =========== // ===========
// ErrListDoesNotExist represents a "ErrListDoesNotExist" kind of error. Used if the list does not exist. // ErrProjectDoesNotExist represents a "ErrProjectDoesNotExist" kind of error. Used if the project does not exist.
type ErrListDoesNotExist struct { type ErrProjectDoesNotExist struct {
ID int64 ID int64
} }
// IsErrListDoesNotExist checks if an error is a ErrListDoesNotExist. // IsErrProjectDoesNotExist checks if an error is a ErrProjectDoesNotExist.
func IsErrListDoesNotExist(err error) bool { func IsErrProjectDoesNotExist(err error) bool {
_, ok := err.(ErrListDoesNotExist) _, ok := err.(ErrProjectDoesNotExist)
return ok return ok
} }
func (err ErrListDoesNotExist) Error() string { func (err ErrProjectDoesNotExist) Error() string {
return fmt.Sprintf("List does not exist [ID: %d]", err.ID) return fmt.Sprintf("Project does not exist [ID: %d]", err.ID)
} }
// ErrCodeListDoesNotExist holds the unique world-error code of this error // ErrCodeProjectDoesNotExist holds the unique world-error code of this error
const ErrCodeListDoesNotExist = 3001 const ErrCodeProjectDoesNotExist = 3001
// HTTPError holds the http error description // HTTPError holds the http error description
func (err ErrListDoesNotExist) HTTPError() web.HTTPError { func (err ErrProjectDoesNotExist) HTTPError() web.HTTPError {
return web.HTTPError{HTTPCode: http.StatusNotFound, Code: ErrCodeListDoesNotExist, Message: "This list does not exist."} return web.HTTPError{HTTPCode: http.StatusNotFound, Code: ErrCodeProjectDoesNotExist, Message: "This project does not exist."}
} }
// ErrNeedToHaveListReadAccess represents an error, where the user dont has read access to that List // ErrNeedToHaveProjectReadAccess represents an error, where the user dont has read access to that Project
type ErrNeedToHaveListReadAccess struct { type ErrNeedToHaveProjectReadAccess struct {
ListID int64 ProjectID int64
UserID int64 UserID int64
} }
// IsErrNeedToHaveListReadAccess checks if an error is a ErrListDoesNotExist. // IsErrNeedToHaveProjectReadAccess checks if an error is a ErrProjectDoesNotExist.
func IsErrNeedToHaveListReadAccess(err error) bool { func IsErrNeedToHaveProjectReadAccess(err error) bool {
_, ok := err.(ErrNeedToHaveListReadAccess) _, ok := err.(ErrNeedToHaveProjectReadAccess)
return ok return ok
} }
func (err ErrNeedToHaveListReadAccess) Error() string { func (err ErrNeedToHaveProjectReadAccess) Error() string {
return fmt.Sprintf("User needs to have read access to that list [ListID: %d, UserID: %d]", err.ListID, err.UserID) return fmt.Sprintf("User needs to have read access to that project [ProjectID: %d, UserID: %d]", err.ProjectID, err.UserID)
} }
// ErrCodeNeedToHaveListReadAccess holds the unique world-error code of this error // ErrCodeNeedToHaveProjectReadAccess holds the unique world-error code of this error
const ErrCodeNeedToHaveListReadAccess = 3004 const ErrCodeNeedToHaveProjectReadAccess = 3004
// HTTPError holds the http error description // HTTPError holds the http error description
func (err ErrNeedToHaveListReadAccess) HTTPError() web.HTTPError { func (err ErrNeedToHaveProjectReadAccess) HTTPError() web.HTTPError {
return web.HTTPError{HTTPCode: http.StatusForbidden, Code: ErrCodeNeedToHaveListReadAccess, Message: "You need to have read access to this list."} return web.HTTPError{HTTPCode: http.StatusForbidden, Code: ErrCodeNeedToHaveProjectReadAccess, Message: "You need to have read access to this project."}
} }
// ErrListTitleCannotBeEmpty represents a "ErrListTitleCannotBeEmpty" kind of error. Used if the list does not exist. // ErrProjectTitleCannotBeEmpty represents a "ErrProjectTitleCannotBeEmpty" kind of error. Used if the project does not exist.
type ErrListTitleCannotBeEmpty struct{} type ErrProjectTitleCannotBeEmpty struct{}
// IsErrListTitleCannotBeEmpty checks if an error is a ErrListTitleCannotBeEmpty. // IsErrProjectTitleCannotBeEmpty checks if an error is a ErrProjectTitleCannotBeEmpty.
func IsErrListTitleCannotBeEmpty(err error) bool { func IsErrProjectTitleCannotBeEmpty(err error) bool {
_, ok := err.(ErrListTitleCannotBeEmpty) _, ok := err.(ErrProjectTitleCannotBeEmpty)
return ok return ok
} }
func (err ErrListTitleCannotBeEmpty) Error() string { func (err ErrProjectTitleCannotBeEmpty) Error() string {
return "List title cannot be empty." return "Project title cannot be empty."
} }
// ErrCodeListTitleCannotBeEmpty holds the unique world-error code of this error // ErrCodeProjectTitleCannotBeEmpty holds the unique world-error code of this error
const ErrCodeListTitleCannotBeEmpty = 3005 const ErrCodeProjectTitleCannotBeEmpty = 3005
// HTTPError holds the http error description // HTTPError holds the http error description
func (err ErrListTitleCannotBeEmpty) HTTPError() web.HTTPError { func (err ErrProjectTitleCannotBeEmpty) HTTPError() web.HTTPError {
return web.HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrCodeListTitleCannotBeEmpty, Message: "You must provide at least a list title."} return web.HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrCodeProjectTitleCannotBeEmpty, Message: "You must provide at least a project title."}
} }
// ErrListShareDoesNotExist represents a "ErrListShareDoesNotExist" kind of error. Used if the list share does not exist. // ErrProjectShareDoesNotExist represents a "ErrProjectShareDoesNotExist" kind of error. Used if the project share does not exist.
type ErrListShareDoesNotExist struct { type ErrProjectShareDoesNotExist struct {
ID int64 ID int64
Hash string Hash string
} }
// IsErrListShareDoesNotExist checks if an error is a ErrListShareDoesNotExist. // IsErrProjectShareDoesNotExist checks if an error is a ErrProjectShareDoesNotExist.
func IsErrListShareDoesNotExist(err error) bool { func IsErrProjectShareDoesNotExist(err error) bool {
_, ok := err.(ErrListShareDoesNotExist) _, ok := err.(ErrProjectShareDoesNotExist)
return ok return ok
} }
func (err ErrListShareDoesNotExist) Error() string { func (err ErrProjectShareDoesNotExist) Error() string {
return "List share does not exist." return "Project share does not exist."
} }
// ErrCodeListShareDoesNotExist holds the unique world-error code of this error // ErrCodeProjectShareDoesNotExist holds the unique world-error code of this error
const ErrCodeListShareDoesNotExist = 3006 const ErrCodeProjectShareDoesNotExist = 3006
// HTTPError holds the http error description // HTTPError holds the http error description
func (err ErrListShareDoesNotExist) HTTPError() web.HTTPError { func (err ErrProjectShareDoesNotExist) HTTPError() web.HTTPError {
return web.HTTPError{HTTPCode: http.StatusNotFound, Code: ErrCodeListShareDoesNotExist, Message: "The list share does not exist."} return web.HTTPError{HTTPCode: http.StatusNotFound, Code: ErrCodeProjectShareDoesNotExist, Message: "The project share does not exist."}
} }
// ErrListIdentifierIsNotUnique represents a "ErrListIdentifierIsNotUnique" kind of error. Used if the provided list identifier is not unique. // ErrProjectIdentifierIsNotUnique represents a "ErrProjectIdentifierIsNotUnique" kind of error. Used if the provided project identifier is not unique.
type ErrListIdentifierIsNotUnique struct { type ErrProjectIdentifierIsNotUnique struct {
Identifier string Identifier string
} }
// IsErrListIdentifierIsNotUnique checks if an error is a ErrListIdentifierIsNotUnique. // IsErrProjectIdentifierIsNotUnique checks if an error is a ErrProjectIdentifierIsNotUnique.
func IsErrListIdentifierIsNotUnique(err error) bool { func IsErrProjectIdentifierIsNotUnique(err error) bool {
_, ok := err.(ErrListIdentifierIsNotUnique) _, ok := err.(ErrProjectIdentifierIsNotUnique)
return ok return ok
} }
func (err ErrListIdentifierIsNotUnique) Error() string { func (err ErrProjectIdentifierIsNotUnique) Error() string {
return "List identifier is not unique." return "Project identifier is not unique."
} }
// ErrCodeListIdentifierIsNotUnique holds the unique world-error code of this error // ErrCodeProjectIdentifierIsNotUnique holds the unique world-error code of this error
const ErrCodeListIdentifierIsNotUnique = 3007 const ErrCodeProjectIdentifierIsNotUnique = 3007
// HTTPError holds the http error description // HTTPError holds the http error description
func (err ErrListIdentifierIsNotUnique) HTTPError() web.HTTPError { func (err ErrProjectIdentifierIsNotUnique) HTTPError() web.HTTPError {
return web.HTTPError{ return web.HTTPError{
HTTPCode: http.StatusBadRequest, HTTPCode: http.StatusBadRequest,
Code: ErrCodeListIdentifierIsNotUnique, Code: ErrCodeProjectIdentifierIsNotUnique,
Message: "A list with this identifier already exists.", Message: "A project with this identifier already exists.",
} }
} }
// ErrListIsArchived represents an error, where a list is archived // ErrProjectIsArchived represents an error, where a project is archived
type ErrListIsArchived struct { type ErrProjectIsArchived struct {
ListID int64 ProjectID int64
} }
// IsErrListIsArchived checks if an error is a list is archived error. // IsErrProjectIsArchived checks if an error is a project is archived error.
func IsErrListIsArchived(err error) bool { func IsErrProjectIsArchived(err error) bool {
_, ok := err.(ErrListIsArchived) _, ok := err.(ErrProjectIsArchived)
return ok return ok
} }
func (err ErrListIsArchived) Error() string { func (err ErrProjectIsArchived) Error() string {
return fmt.Sprintf("List is archived [ListID: %d]", err.ListID) return fmt.Sprintf("Project is archived [ProjectID: %d]", err.ProjectID)
} }
// ErrCodeListIsArchived holds the unique world-error code of this error // ErrCodeProjectIsArchived holds the unique world-error code of this error
const ErrCodeListIsArchived = 3008 const ErrCodeProjectIsArchived = 3008
// HTTPError holds the http error description // HTTPError holds the http error description
func (err ErrListIsArchived) HTTPError() web.HTTPError { func (err ErrProjectIsArchived) HTTPError() web.HTTPError {
return web.HTTPError{HTTPCode: http.StatusPreconditionFailed, Code: ErrCodeListIsArchived, Message: "This list is archived. Editing or creating new tasks is not possible."} return web.HTTPError{HTTPCode: http.StatusPreconditionFailed, Code: ErrCodeProjectIsArchived, Message: "This project is archived. Editing or creating new tasks is not possible."}
} }
// ErrListCannotBelongToAPseudoNamespace represents an error where a list cannot belong to a pseudo namespace // ErrProjectCannotBelongToAPseudoNamespace represents an error where a project cannot belong to a pseudo namespace
type ErrListCannotBelongToAPseudoNamespace struct { type ErrProjectCannotBelongToAPseudoNamespace struct {
ListID int64 ProjectID int64
NamespaceID int64 NamespaceID int64
} }
// IsErrListCannotBelongToAPseudoNamespace checks if an error is a list is archived error. // IsErrProjectCannotBelongToAPseudoNamespace checks if an error is a project is archived error.
func IsErrListCannotBelongToAPseudoNamespace(err error) bool { func IsErrProjectCannotBelongToAPseudoNamespace(err error) bool {
_, ok := err.(*ErrListCannotBelongToAPseudoNamespace) _, ok := err.(*ErrProjectCannotBelongToAPseudoNamespace)
return ok return ok
} }
func (err *ErrListCannotBelongToAPseudoNamespace) Error() string { func (err *ErrProjectCannotBelongToAPseudoNamespace) Error() string {
return fmt.Sprintf("List cannot belong to a pseudo namespace [ListID: %d, NamespaceID: %d]", err.ListID, err.NamespaceID) return fmt.Sprintf("Project cannot belong to a pseudo namespace [ProjectID: %d, NamespaceID: %d]", err.ProjectID, err.NamespaceID)
} }
// ErrCodeListCannotBelongToAPseudoNamespace holds the unique world-error code of this error // ErrCodeProjectCannotBelongToAPseudoNamespace holds the unique world-error code of this error
const ErrCodeListCannotBelongToAPseudoNamespace = 3009 const ErrCodeProjectCannotBelongToAPseudoNamespace = 3009
// HTTPError holds the http error description // HTTPError holds the http error description
func (err *ErrListCannotBelongToAPseudoNamespace) HTTPError() web.HTTPError { func (err *ErrProjectCannotBelongToAPseudoNamespace) HTTPError() web.HTTPError {
return web.HTTPError{ return web.HTTPError{
HTTPCode: http.StatusPreconditionFailed, HTTPCode: http.StatusPreconditionFailed,
Code: ErrCodeListCannotBelongToAPseudoNamespace, Code: ErrCodeProjectCannotBelongToAPseudoNamespace,
Message: "This list cannot belong a dynamically generated namespace.", Message: "This project cannot belong a dynamically generated namespace.",
} }
} }
// ErrListMustBelongToANamespace represents an error where a list must belong to a namespace // ErrProjectMustBelongToANamespace represents an error where a project must belong to a namespace
type ErrListMustBelongToANamespace struct { type ErrProjectMustBelongToANamespace struct {
ListID int64 ProjectID int64
NamespaceID int64 NamespaceID int64
} }
// IsErrListMustBelongToANamespace checks if an error is a list must belong to a namespace error. // IsErrProjectMustBelongToANamespace checks if an error is a project must belong to a namespace error.
func IsErrListMustBelongToANamespace(err error) bool { func IsErrProjectMustBelongToANamespace(err error) bool {
_, ok := err.(*ErrListMustBelongToANamespace) _, ok := err.(*ErrProjectMustBelongToANamespace)
return ok return ok
} }
func (err *ErrListMustBelongToANamespace) Error() string { func (err *ErrProjectMustBelongToANamespace) Error() string {
return fmt.Sprintf("List must belong to a namespace [ListID: %d, NamespaceID: %d]", err.ListID, err.NamespaceID) return fmt.Sprintf("Project must belong to a namespace [ProjectID: %d, NamespaceID: %d]", err.ProjectID, err.NamespaceID)
} }
// ErrCodeListMustBelongToANamespace holds the unique world-error code of this error // ErrCodeProjectMustBelongToANamespace holds the unique world-error code of this error
const ErrCodeListMustBelongToANamespace = 3010 const ErrCodeProjectMustBelongToANamespace = 3010
// HTTPError holds the http error description // HTTPError holds the http error description
func (err *ErrListMustBelongToANamespace) HTTPError() web.HTTPError { func (err *ErrProjectMustBelongToANamespace) HTTPError() web.HTTPError {
return web.HTTPError{ return web.HTTPError{
HTTPCode: http.StatusPreconditionFailed, HTTPCode: http.StatusPreconditionFailed,
Code: ErrCodeListMustBelongToANamespace, Code: ErrCodeProjectMustBelongToANamespace,
Message: "This list must belong to a namespace.", Message: "This project must belong to a namespace.",
} }
} }
// ================ // ================
// List task errors // Project task errors
// ================ // ================
// ErrTaskCannotBeEmpty represents a "ErrListDoesNotExist" kind of error. Used if the list does not exist. // ErrTaskCannotBeEmpty represents a "ErrProjectDoesNotExist" kind of error. Used if the project does not exist.
type ErrTaskCannotBeEmpty struct{} type ErrTaskCannotBeEmpty struct{}
// IsErrTaskCannotBeEmpty checks if an error is a ErrListDoesNotExist. // IsErrTaskCannotBeEmpty checks if an error is a ErrProjectDoesNotExist.
func IsErrTaskCannotBeEmpty(err error) bool { func IsErrTaskCannotBeEmpty(err error) bool {
_, ok := err.(ErrTaskCannotBeEmpty) _, ok := err.(ErrTaskCannotBeEmpty)
return ok return ok
} }
func (err ErrTaskCannotBeEmpty) Error() string { func (err ErrTaskCannotBeEmpty) Error() string {
return "List task title cannot be empty." return "Project task title cannot be empty."
} }
// ErrCodeTaskCannotBeEmpty holds the unique world-error code of this error // ErrCodeTaskCannotBeEmpty holds the unique world-error code of this error
@ -333,22 +333,22 @@ const ErrCodeTaskCannotBeEmpty = 4001
// HTTPError holds the http error description // HTTPError holds the http error description
func (err ErrTaskCannotBeEmpty) HTTPError() web.HTTPError { func (err ErrTaskCannotBeEmpty) HTTPError() web.HTTPError {
return web.HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrCodeTaskCannotBeEmpty, Message: "You must provide at least a list task title."} return web.HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrCodeTaskCannotBeEmpty, Message: "You must provide at least a project task title."}
} }
// ErrTaskDoesNotExist represents a "ErrListDoesNotExist" kind of error. Used if the list does not exist. // ErrTaskDoesNotExist represents a "ErrProjectDoesNotExist" kind of error. Used if the project does not exist.
type ErrTaskDoesNotExist struct { type ErrTaskDoesNotExist struct {
ID int64 ID int64
} }
// IsErrTaskDoesNotExist checks if an error is a ErrListDoesNotExist. // IsErrTaskDoesNotExist checks if an error is a ErrProjectDoesNotExist.
func IsErrTaskDoesNotExist(err error) bool { func IsErrTaskDoesNotExist(err error) bool {
_, ok := err.(ErrTaskDoesNotExist) _, ok := err.(ErrTaskDoesNotExist)
return ok return ok
} }
func (err ErrTaskDoesNotExist) Error() string { func (err ErrTaskDoesNotExist) Error() string {
return fmt.Sprintf("List task does not exist. [ID: %d]", err.ID) return fmt.Sprintf("Project task does not exist. [ID: %d]", err.ID)
} }
// ErrCodeTaskDoesNotExist holds the unique world-error code of this error // ErrCodeTaskDoesNotExist holds the unique world-error code of this error
@ -359,28 +359,28 @@ func (err ErrTaskDoesNotExist) HTTPError() web.HTTPError {
return web.HTTPError{HTTPCode: http.StatusNotFound, Code: ErrCodeTaskDoesNotExist, Message: "This task does not exist"} return web.HTTPError{HTTPCode: http.StatusNotFound, Code: ErrCodeTaskDoesNotExist, Message: "This task does not exist"}
} }
// ErrBulkTasksMustBeInSameList represents a "ErrBulkTasksMustBeInSameList" kind of error. // ErrBulkTasksMustBeInSameProject represents a "ErrBulkTasksMustBeInSameProject" kind of error.
type ErrBulkTasksMustBeInSameList struct { type ErrBulkTasksMustBeInSameProject struct {
ShouldBeID int64 ShouldBeID int64
IsID int64 IsID int64
} }
// IsErrBulkTasksMustBeInSameList checks if an error is a ErrBulkTasksMustBeInSameList. // IsErrBulkTasksMustBeInSameProject checks if an error is a ErrBulkTasksMustBeInSameProject.
func IsErrBulkTasksMustBeInSameList(err error) bool { func IsErrBulkTasksMustBeInSameProject(err error) bool {
_, ok := err.(ErrBulkTasksMustBeInSameList) _, ok := err.(ErrBulkTasksMustBeInSameProject)
return ok return ok
} }
func (err ErrBulkTasksMustBeInSameList) Error() string { func (err ErrBulkTasksMustBeInSameProject) Error() string {
return fmt.Sprintf("All bulk editing tasks must be in the same list. [Should be: %d, is: %d]", err.ShouldBeID, err.IsID) return fmt.Sprintf("All bulk editing tasks must be in the same project. [Should be: %d, is: %d]", err.ShouldBeID, err.IsID)
} }
// ErrCodeBulkTasksMustBeInSameList holds the unique world-error code of this error // ErrCodeBulkTasksMustBeInSameProject holds the unique world-error code of this error
const ErrCodeBulkTasksMustBeInSameList = 4003 const ErrCodeBulkTasksMustBeInSameProject = 4003
// HTTPError holds the http error description // HTTPError holds the http error description
func (err ErrBulkTasksMustBeInSameList) HTTPError() web.HTTPError { func (err ErrBulkTasksMustBeInSameProject) HTTPError() web.HTTPError {
return web.HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrCodeBulkTasksMustBeInSameList, Message: "All tasks must be in the same list."} return web.HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrCodeBulkTasksMustBeInSameProject, Message: "All tasks must be in the same project."}
} }
// ErrBulkTasksNeedAtLeastOne represents a "ErrBulkTasksNeedAtLeastOne" kind of error. // ErrBulkTasksNeedAtLeastOne represents a "ErrBulkTasksNeedAtLeastOne" kind of error.
@ -1042,7 +1042,7 @@ const ErrCodeNamespaceIsArchived = 5012
// HTTPError holds the http error description // HTTPError holds the http error description
func (err ErrNamespaceIsArchived) HTTPError() web.HTTPError { func (err ErrNamespaceIsArchived) HTTPError() web.HTTPError {
return web.HTTPError{HTTPCode: http.StatusPreconditionFailed, Code: ErrCodeNamespaceIsArchived, Message: "This namespaces is archived. Editing or creating new lists is not possible."} return web.HTTPError{HTTPCode: http.StatusPreconditionFailed, Code: ErrCodeNamespaceIsArchived, Message: "This namespaces is archived. Editing or creating new projects is not possible."}
} }
// ============ // ============
@ -1095,7 +1095,7 @@ func (err ErrTeamDoesNotExist) HTTPError() web.HTTPError {
return web.HTTPError{HTTPCode: http.StatusNotFound, Code: ErrCodeTeamDoesNotExist, Message: "This team does not exist."} return web.HTTPError{HTTPCode: http.StatusNotFound, Code: ErrCodeTeamDoesNotExist, Message: "This team does not exist."}
} }
// ErrTeamAlreadyHasAccess represents an error where a team already has access to a list/namespace // ErrTeamAlreadyHasAccess represents an error where a team already has access to a project/namespace
type ErrTeamAlreadyHasAccess struct { type ErrTeamAlreadyHasAccess struct {
TeamID int64 TeamID int64
ID int64 ID int64
@ -1167,38 +1167,38 @@ func (err ErrCannotDeleteLastTeamMember) HTTPError() web.HTTPError {
return web.HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrCodeCannotDeleteLastTeamMember, Message: "You cannot delete the last member of a team."} return web.HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrCodeCannotDeleteLastTeamMember, Message: "You cannot delete the last member of a team."}
} }
// ErrTeamDoesNotHaveAccessToList represents an error, where the Team is not the owner of that List (used i.e. when deleting a List) // ErrTeamDoesNotHaveAccessToProject represents an error, where the Team is not the owner of that Project (used i.e. when deleting a Project)
type ErrTeamDoesNotHaveAccessToList struct { type ErrTeamDoesNotHaveAccessToProject struct {
ListID int64 ProjectID int64
TeamID int64 TeamID int64
} }
// IsErrTeamDoesNotHaveAccessToList checks if an error is a ErrListDoesNotExist. // IsErrTeamDoesNotHaveAccessToProject checks if an error is a ErrProjectDoesNotExist.
func IsErrTeamDoesNotHaveAccessToList(err error) bool { func IsErrTeamDoesNotHaveAccessToProject(err error) bool {
_, ok := err.(ErrTeamDoesNotHaveAccessToList) _, ok := err.(ErrTeamDoesNotHaveAccessToProject)
return ok return ok
} }
func (err ErrTeamDoesNotHaveAccessToList) Error() string { func (err ErrTeamDoesNotHaveAccessToProject) Error() string {
return fmt.Sprintf("Team does not have access to the list [ListID: %d, TeamID: %d]", err.ListID, err.TeamID) return fmt.Sprintf("Team does not have access to the project [ProjectID: %d, TeamID: %d]", err.ProjectID, err.TeamID)
} }
// ErrCodeTeamDoesNotHaveAccessToList holds the unique world-error code of this error // ErrCodeTeamDoesNotHaveAccessToProject holds the unique world-error code of this error
const ErrCodeTeamDoesNotHaveAccessToList = 6007 const ErrCodeTeamDoesNotHaveAccessToProject = 6007
// HTTPError holds the http error description // HTTPError holds the http error description
func (err ErrTeamDoesNotHaveAccessToList) HTTPError() web.HTTPError { func (err ErrTeamDoesNotHaveAccessToProject) HTTPError() web.HTTPError {
return web.HTTPError{HTTPCode: http.StatusForbidden, Code: ErrCodeTeamDoesNotHaveAccessToList, Message: "This team does not have access to the list."} return web.HTTPError{HTTPCode: http.StatusForbidden, Code: ErrCodeTeamDoesNotHaveAccessToProject, Message: "This team does not have access to the project."}
} }
// ==================== // ====================
// User <-> List errors // User <-> Project errors
// ==================== // ====================
// ErrUserAlreadyHasAccess represents an error where a user already has access to a list/namespace // ErrUserAlreadyHasAccess represents an error where a user already has access to a project/namespace
type ErrUserAlreadyHasAccess struct { type ErrUserAlreadyHasAccess struct {
UserID int64 UserID int64
ListID int64 ProjectID int64
} }
// IsErrUserAlreadyHasAccess checks if an error is ErrUserAlreadyHasAccess. // IsErrUserAlreadyHasAccess checks if an error is ErrUserAlreadyHasAccess.
@ -1208,7 +1208,7 @@ func IsErrUserAlreadyHasAccess(err error) bool {
} }
func (err ErrUserAlreadyHasAccess) Error() string { func (err ErrUserAlreadyHasAccess) Error() string {
return fmt.Sprintf("User already has access to that list. [User ID: %d, List ID: %d]", err.UserID, err.ListID) return fmt.Sprintf("User already has access to that project. [User ID: %d, Project ID: %d]", err.UserID, err.ProjectID)
} }
// ErrCodeUserAlreadyHasAccess holds the unique world-error code of this error // ErrCodeUserAlreadyHasAccess holds the unique world-error code of this error
@ -1216,31 +1216,31 @@ const ErrCodeUserAlreadyHasAccess = 7002
// HTTPError holds the http error description // HTTPError holds the http error description
func (err ErrUserAlreadyHasAccess) HTTPError() web.HTTPError { func (err ErrUserAlreadyHasAccess) HTTPError() web.HTTPError {
return web.HTTPError{HTTPCode: http.StatusConflict, Code: ErrCodeUserAlreadyHasAccess, Message: "This user already has access to this list."} return web.HTTPError{HTTPCode: http.StatusConflict, Code: ErrCodeUserAlreadyHasAccess, Message: "This user already has access to this project."}
} }
// ErrUserDoesNotHaveAccessToList represents an error, where the user is not the owner of that List (used i.e. when deleting a List) // ErrUserDoesNotHaveAccessToProject represents an error, where the user is not the owner of that Project (used i.e. when deleting a Project)
type ErrUserDoesNotHaveAccessToList struct { type ErrUserDoesNotHaveAccessToProject struct {
ListID int64 ProjectID int64
UserID int64 UserID int64
} }
// IsErrUserDoesNotHaveAccessToList checks if an error is a ErrListDoesNotExist. // IsErrUserDoesNotHaveAccessToProject checks if an error is a ErrProjectDoesNotExist.
func IsErrUserDoesNotHaveAccessToList(err error) bool { func IsErrUserDoesNotHaveAccessToProject(err error) bool {
_, ok := err.(ErrUserDoesNotHaveAccessToList) _, ok := err.(ErrUserDoesNotHaveAccessToProject)
return ok return ok
} }
func (err ErrUserDoesNotHaveAccessToList) Error() string { func (err ErrUserDoesNotHaveAccessToProject) Error() string {
return fmt.Sprintf("User does not have access to the list [ListID: %d, UserID: %d]", err.ListID, err.UserID) return fmt.Sprintf("User does not have access to the project [ProjectID: %d, UserID: %d]", err.ProjectID, err.UserID)
} }
// ErrCodeUserDoesNotHaveAccessToList holds the unique world-error code of this error // ErrCodeUserDoesNotHaveAccessToProject holds the unique world-error code of this error
const ErrCodeUserDoesNotHaveAccessToList = 7003 const ErrCodeUserDoesNotHaveAccessToProject = 7003
// HTTPError holds the http error description // HTTPError holds the http error description
func (err ErrUserDoesNotHaveAccessToList) HTTPError() web.HTTPError { func (err ErrUserDoesNotHaveAccessToProject) HTTPError() web.HTTPError {
return web.HTTPError{HTTPCode: http.StatusForbidden, Code: ErrCodeUserDoesNotHaveAccessToList, Message: "This user does not have access to the list."} return web.HTTPError{HTTPCode: http.StatusForbidden, Code: ErrCodeUserDoesNotHaveAccessToProject, Message: "This user does not have access to the project."}
} }
// ============= // =============
@ -1392,38 +1392,38 @@ func (err ErrBucketDoesNotExist) HTTPError() web.HTTPError {
} }
} }
// ErrBucketDoesNotBelongToList represents an error where a kanban bucket does not belong to a list // ErrBucketDoesNotBelongToProject represents an error where a kanban bucket does not belong to a project
type ErrBucketDoesNotBelongToList struct { type ErrBucketDoesNotBelongToProject struct {
BucketID int64 BucketID int64
ListID int64 ProjectID int64
} }
// IsErrBucketDoesNotBelongToList checks if an error is ErrBucketDoesNotBelongToList. // IsErrBucketDoesNotBelongToProject checks if an error is ErrBucketDoesNotBelongToProject.
func IsErrBucketDoesNotBelongToList(err error) bool { func IsErrBucketDoesNotBelongToProject(err error) bool {
_, ok := err.(ErrBucketDoesNotBelongToList) _, ok := err.(ErrBucketDoesNotBelongToProject)
return ok return ok
} }
func (err ErrBucketDoesNotBelongToList) Error() string { func (err ErrBucketDoesNotBelongToProject) Error() string {
return fmt.Sprintf("Bucket does not not belong to list [BucketID: %d, ListID: %d]", err.BucketID, err.ListID) return fmt.Sprintf("Bucket does not not belong to project [BucketID: %d, ProjectID: %d]", err.BucketID, err.ProjectID)
} }
// ErrCodeBucketDoesNotBelongToList holds the unique world-error code of this error // ErrCodeBucketDoesNotBelongToProject holds the unique world-error code of this error
const ErrCodeBucketDoesNotBelongToList = 10002 const ErrCodeBucketDoesNotBelongToProject = 10002
// HTTPError holds the http error description // HTTPError holds the http error description
func (err ErrBucketDoesNotBelongToList) HTTPError() web.HTTPError { func (err ErrBucketDoesNotBelongToProject) HTTPError() web.HTTPError {
return web.HTTPError{ return web.HTTPError{
HTTPCode: http.StatusBadRequest, HTTPCode: http.StatusBadRequest,
Code: ErrCodeBucketDoesNotBelongToList, Code: ErrCodeBucketDoesNotBelongToProject,
Message: "This bucket does not belong to that list.", Message: "This bucket does not belong to that project.",
} }
} }
// ErrCannotRemoveLastBucket represents an error where a kanban bucket is the last on a list and thus cannot be removed. // ErrCannotRemoveLastBucket represents an error where a kanban bucket is the last on a project and thus cannot be removed.
type ErrCannotRemoveLastBucket struct { type ErrCannotRemoveLastBucket struct {
BucketID int64 BucketID int64
ListID int64 ProjectID int64
} }
// IsErrCannotRemoveLastBucket checks if an error is ErrCannotRemoveLastBucket. // IsErrCannotRemoveLastBucket checks if an error is ErrCannotRemoveLastBucket.
@ -1433,7 +1433,7 @@ func IsErrCannotRemoveLastBucket(err error) bool {
} }
func (err ErrCannotRemoveLastBucket) Error() string { func (err ErrCannotRemoveLastBucket) Error() string {
return fmt.Sprintf("Cannot remove last bucket of list [BucketID: %d, ListID: %d]", err.BucketID, err.ListID) return fmt.Sprintf("Cannot remove last bucket of project [BucketID: %d, ProjectID: %d]", err.BucketID, err.ProjectID)
} }
// ErrCodeCannotRemoveLastBucket holds the unique world-error code of this error // ErrCodeCannotRemoveLastBucket holds the unique world-error code of this error
@ -1444,7 +1444,7 @@ func (err ErrCannotRemoveLastBucket) HTTPError() web.HTTPError {
return web.HTTPError{ return web.HTTPError{
HTTPCode: http.StatusPreconditionFailed, HTTPCode: http.StatusPreconditionFailed,
Code: ErrCodeCannotRemoveLastBucket, Code: ErrCodeCannotRemoveLastBucket,
Message: "You cannot remove the last bucket on this list.", Message: "You cannot remove the last bucket on this project.",
} }
} }
@ -1477,32 +1477,32 @@ func (err ErrBucketLimitExceeded) HTTPError() web.HTTPError {
} }
} }
// ErrOnlyOneDoneBucketPerList represents an error where a bucket is set to the done bucket but one already exists for its list. // ErrOnlyOneDoneBucketPerProject represents an error where a bucket is set to the done bucket but one already exists for its project.
type ErrOnlyOneDoneBucketPerList struct { type ErrOnlyOneDoneBucketPerProject struct {
BucketID int64 BucketID int64
ListID int64 ProjectID int64
DoneBucketID int64 DoneBucketID int64
} }
// IsErrOnlyOneDoneBucketPerList checks if an error is ErrBucketLimitExceeded. // IsErrOnlyOneDoneBucketPerProject checks if an error is ErrBucketLimitExceeded.
func IsErrOnlyOneDoneBucketPerList(err error) bool { func IsErrOnlyOneDoneBucketPerProject(err error) bool {
_, ok := err.(*ErrOnlyOneDoneBucketPerList) _, ok := err.(*ErrOnlyOneDoneBucketPerProject)
return ok return ok
} }
func (err *ErrOnlyOneDoneBucketPerList) Error() string { func (err *ErrOnlyOneDoneBucketPerProject) Error() string {
return fmt.Sprintf("There can be only one done bucket per list [BucketID: %d, ListID: %d]", err.BucketID, err.ListID) return fmt.Sprintf("There can be only one done bucket per project [BucketID: %d, ProjectID: %d]", err.BucketID, err.ProjectID)
} }
// ErrCodeOnlyOneDoneBucketPerList holds the unique world-error code of this error // ErrCodeOnlyOneDoneBucketPerProject holds the unique world-error code of this error
const ErrCodeOnlyOneDoneBucketPerList = 10005 const ErrCodeOnlyOneDoneBucketPerProject = 10005
// HTTPError holds the http error description // HTTPError holds the http error description
func (err *ErrOnlyOneDoneBucketPerList) HTTPError() web.HTTPError { func (err *ErrOnlyOneDoneBucketPerProject) HTTPError() web.HTTPError {
return web.HTTPError{ return web.HTTPError{
HTTPCode: http.StatusPreconditionFailed, HTTPCode: http.StatusPreconditionFailed,
Code: ErrCodeOnlyOneDoneBucketPerList, Code: ErrCodeOnlyOneDoneBucketPerProject,
Message: "There can be only one done bucket per list.", Message: "There can be only one done bucket per project.",
} }
} }

View File

@ -214,68 +214,68 @@ func (t *NamespaceDeletedEvent) Name() string {
} }
///////////////// /////////////////
// List Events // // Project Events //
///////////////// /////////////////
// ListCreatedEvent represents an event where a list has been created // ProjectCreatedEvent represents an event where a project has been created
type ListCreatedEvent struct { type ProjectCreatedEvent struct {
List *List Project *Project
Doer *user.User Doer *user.User
} }
// Name defines the name for ListCreatedEvent // Name defines the name for ProjectCreatedEvent
func (l *ListCreatedEvent) Name() string { func (l *ProjectCreatedEvent) Name() string {
return "list.created" return "project.created"
} }
// ListUpdatedEvent represents an event where a list has been updated // ProjectUpdatedEvent represents an event where a project has been updated
type ListUpdatedEvent struct { type ProjectUpdatedEvent struct {
List *List Project *Project
Doer web.Auth Doer web.Auth
} }
// Name defines the name for ListUpdatedEvent // Name defines the name for ProjectUpdatedEvent
func (l *ListUpdatedEvent) Name() string { func (l *ProjectUpdatedEvent) Name() string {
return "list.updated" return "project.updated"
} }
// ListDeletedEvent represents an event where a list has been deleted // ProjectDeletedEvent represents an event where a project has been deleted
type ListDeletedEvent struct { type ProjectDeletedEvent struct {
List *List Project *Project
Doer web.Auth Doer web.Auth
} }
// Name defines the name for ListDeletedEvent // Name defines the name for ProjectDeletedEvent
func (t *ListDeletedEvent) Name() string { func (t *ProjectDeletedEvent) Name() string {
return "list.deleted" return "project.deleted"
} }
//////////////////// ////////////////////
// Sharing Events // // Sharing Events //
//////////////////// ////////////////////
// ListSharedWithUserEvent represents an event where a list has been shared with a user // ProjectSharedWithUserEvent represents an event where a project has been shared with a user
type ListSharedWithUserEvent struct { type ProjectSharedWithUserEvent struct {
List *List Project *Project
User *user.User User *user.User
Doer web.Auth Doer web.Auth
} }
// Name defines the name for ListSharedWithUserEvent // Name defines the name for ProjectSharedWithUserEvent
func (l *ListSharedWithUserEvent) Name() string { func (l *ProjectSharedWithUserEvent) Name() string {
return "list.shared.user" return "project.shared.user"
} }
// ListSharedWithTeamEvent represents an event where a list has been shared with a team // ProjectSharedWithTeamEvent represents an event where a project has been shared with a team
type ListSharedWithTeamEvent struct { type ProjectSharedWithTeamEvent struct {
List *List Project *Project
Team *Team Team *Team
Doer web.Auth Doer web.Auth
} }
// Name defines the name for ListSharedWithTeamEvent // Name defines the name for ProjectSharedWithTeamEvent
func (l *ListSharedWithTeamEvent) Name() string { func (l *ProjectSharedWithTeamEvent) Name() string {
return "list.shared.team" return "project.shared.team"
} }
// NamespaceSharedWithUserEvent represents an event where a namespace has been shared with a user // NamespaceSharedWithUserEvent represents an event where a namespace has been shared with a user

View File

@ -57,7 +57,7 @@ func ExportUserData(s *xorm.Session, u *user.User) (err error) {
defer dumpWriter.Close() defer dumpWriter.Close()
// Get the data // Get the data
err = exportListsAndTasks(s, u, dumpWriter) err = exportProjectsAndTasks(s, u, dumpWriter)
if err != nil { if err != nil {
return err return err
} }
@ -72,7 +72,7 @@ func ExportUserData(s *xorm.Session, u *user.User) (err error) {
return err return err
} }
// Background files // Background files
err = exportListBackgrounds(s, u, dumpWriter) err = exportProjectBackgrounds(s, u, dumpWriter)
if err != nil { if err != nil {
return err return err
} }
@ -121,7 +121,7 @@ func ExportUserData(s *xorm.Session, u *user.User) (err error) {
}) })
} }
func exportListsAndTasks(s *xorm.Session, u *user.User, wr *zip.Writer) (err error) { func exportProjectsAndTasks(s *xorm.Session, u *user.User, wr *zip.Writer) (err error) {
namspaces, _, _, err := (&Namespace{IsArchived: true}).ReadAll(s, u, "", -1, 0) namspaces, _, _, err := (&Namespace{IsArchived: true}).ReadAll(s, u, "", -1, 0)
if err != nil { if err != nil {
@ -129,29 +129,29 @@ func exportListsAndTasks(s *xorm.Session, u *user.User, wr *zip.Writer) (err err
} }
namespaceIDs := []int64{} namespaceIDs := []int64{}
namespaces := []*NamespaceWithListsAndTasks{} namespaces := []*NamespaceWithProjectsAndTasks{}
listMap := make(map[int64]*ListWithTasksAndBuckets) projectMap := make(map[int64]*ProjectWithTasksAndBuckets)
listIDs := []int64{} projectIDs := []int64{}
for _, n := range namspaces.([]*NamespaceWithLists) { for _, n := range namspaces.([]*NamespaceWithProjects) {
if n.ID < 1 { if n.ID < 1 {
// Don't include filters // Don't include filters
continue continue
} }
nn := &NamespaceWithListsAndTasks{ nn := &NamespaceWithProjectsAndTasks{
Namespace: n.Namespace, Namespace: n.Namespace,
Lists: []*ListWithTasksAndBuckets{}, Projects: []*ProjectWithTasksAndBuckets{},
} }
for _, l := range n.Lists { for _, l := range n.Projects {
ll := &ListWithTasksAndBuckets{ ll := &ProjectWithTasksAndBuckets{
List: *l, Project: *l,
BackgroundFileID: l.BackgroundFileID, BackgroundFileID: l.BackgroundFileID,
Tasks: []*TaskWithComments{}, Tasks: []*TaskWithComments{},
} }
nn.Lists = append(nn.Lists, ll) nn.Projects = append(nn.Projects, ll)
listMap[l.ID] = ll projectMap[l.ID] = ll
listIDs = append(listIDs, l.ID) projectIDs = append(projectIDs, l.ID)
} }
namespaceIDs = append(namespaceIDs, n.ID) namespaceIDs = append(namespaceIDs, n.ID)
@ -162,13 +162,13 @@ func exportListsAndTasks(s *xorm.Session, u *user.User, wr *zip.Writer) (err err
return nil return nil
} }
// Get all lists // Get all projects
lists, err := getListsForNamespaces(s, namespaceIDs, true) projects, err := getProjectsForNamespaces(s, namespaceIDs, true)
if err != nil { if err != nil {
return err return err
} }
tasks, _, _, err := getTasksForLists(s, lists, u, &taskOptions{ tasks, _, _, err := getTasksForProjects(s, projects, u, &taskOptions{
page: 0, page: 0,
perPage: -1, perPage: -1,
}) })
@ -181,17 +181,17 @@ func exportListsAndTasks(s *xorm.Session, u *user.User, wr *zip.Writer) (err err
taskMap[t.ID] = &TaskWithComments{ taskMap[t.ID] = &TaskWithComments{
Task: *t, Task: *t,
} }
if _, exists := listMap[t.ListID]; !exists { if _, exists := projectMap[t.ProjectID]; !exists {
log.Debugf("[User Data Export] List %d does not exist for task %d, omitting", t.ListID, t.ID) log.Debugf("[User Data Export] Project %d does not exist for task %d, omitting", t.ProjectID, t.ID)
continue continue
} }
listMap[t.ListID].Tasks = append(listMap[t.ListID].Tasks, taskMap[t.ID]) projectMap[t.ProjectID].Tasks = append(projectMap[t.ProjectID].Tasks, taskMap[t.ID])
} }
comments := []*TaskComment{} comments := []*TaskComment{}
err = s. err = s.
Join("LEFT", "tasks", "tasks.id = task_comments.task_id"). Join("LEFT", "tasks", "tasks.id = task_comments.task_id").
In("tasks.list_id", listIDs). In("tasks.project_id", projectIDs).
Find(&comments) Find(&comments)
if err != nil { if err != nil {
return return
@ -206,17 +206,17 @@ func exportListsAndTasks(s *xorm.Session, u *user.User, wr *zip.Writer) (err err
} }
buckets := []*Bucket{} buckets := []*Bucket{}
err = s.In("list_id", listIDs).Find(&buckets) err = s.In("project_id", projectIDs).Find(&buckets)
if err != nil { if err != nil {
return return
} }
for _, b := range buckets { for _, b := range buckets {
if _, exists := listMap[b.ListID]; !exists { if _, exists := projectMap[b.ProjectID]; !exists {
log.Debugf("[User Data Export] List %d does not exist for bucket %d, omitting", b.ListID, b.ID) log.Debugf("[User Data Export] Project %d does not exist for bucket %d, omitting", b.ProjectID, b.ID)
continue continue
} }
listMap[b.ListID].Buckets = append(listMap[b.ListID].Buckets, b) projectMap[b.ProjectID].Buckets = append(projectMap[b.ProjectID].Buckets, b)
} }
data, err := json.Marshal(namespaces) data, err := json.Marshal(namespaces)
@ -228,9 +228,9 @@ func exportListsAndTasks(s *xorm.Session, u *user.User, wr *zip.Writer) (err err
} }
func exportTaskAttachments(s *xorm.Session, u *user.User, wr *zip.Writer) (err error) { func exportTaskAttachments(s *xorm.Session, u *user.User, wr *zip.Writer) (err error) {
lists, _, _, err := getRawListsForUser( projects, _, _, err := getRawProjectsForUser(
s, s,
&listOptions{ &projectOptions{
user: u, user: u,
page: -1, page: -1,
}, },
@ -239,7 +239,7 @@ func exportTaskAttachments(s *xorm.Session, u *user.User, wr *zip.Writer) (err e
return err return err
} }
tasks, _, _, err := getRawTasksForLists(s, lists, u, &taskOptions{page: -1}) tasks, _, _, err := getRawTasksForProjects(s, projects, u, &taskOptions{page: -1})
if err != nil { if err != nil {
return err return err
} }
@ -279,10 +279,10 @@ func exportSavedFilters(s *xorm.Session, u *user.User, wr *zip.Writer) (err erro
return utils.WriteBytesToZip("filters.json", data, wr) return utils.WriteBytesToZip("filters.json", data, wr)
} }
func exportListBackgrounds(s *xorm.Session, u *user.User, wr *zip.Writer) (err error) { func exportProjectBackgrounds(s *xorm.Session, u *user.User, wr *zip.Writer) (err error) {
lists, _, _, err := getRawListsForUser( projects, _, _, err := getRawProjectsForUser(
s, s,
&listOptions{ &projectOptions{
user: u, user: u,
page: -1, page: -1,
}, },
@ -292,7 +292,7 @@ func exportListBackgrounds(s *xorm.Session, u *user.User, wr *zip.Writer) (err e
} }
fs := make(map[int64]io.ReadCloser) fs := make(map[int64]io.ReadCloser)
for _, l := range lists { for _, l := range projects {
if l.BackgroundFileID == 0 { if l.BackgroundFileID == 0 {
continue continue
} }

View File

@ -29,7 +29,7 @@ type FavoriteKind int
const ( const (
FavoriteKindUnknown FavoriteKind = iota FavoriteKindUnknown FavoriteKind = iota
FavoriteKindTask FavoriteKindTask
FavoriteKindList FavoriteKindProject
) )
// Favorite represents an entity which is a favorite to someone // Favorite represents an entity which is a favorite to someone

View File

@ -31,8 +31,8 @@ type Bucket struct {
ID int64 `xorm:"bigint autoincr not null unique pk" json:"id" param:"bucket"` ID int64 `xorm:"bigint autoincr not null unique pk" json:"id" param:"bucket"`
// The title of this bucket. // The title of this bucket.
Title string `xorm:"text not null" valid:"required" minLength:"1" json:"title"` Title string `xorm:"text not null" valid:"required" minLength:"1" json:"title"`
// The list this bucket belongs to. // The project this bucket belongs to.
ListID int64 `xorm:"bigint not null" json:"list_id" param:"list"` ProjectID int64 `xorm:"bigint not null" json:"project_id" param:"project"`
// All tasks which belong to this bucket. // All tasks which belong to this bucket.
Tasks []*Task `xorm:"-" json:"tasks"` Tasks []*Task `xorm:"-" json:"tasks"`
@ -77,19 +77,19 @@ func getBucketByID(s *xorm.Session, id int64) (b *Bucket, err error) {
return return
} }
func getDefaultBucket(s *xorm.Session, listID int64) (bucket *Bucket, err error) { func getDefaultBucket(s *xorm.Session, projectID int64) (bucket *Bucket, err error) {
bucket = &Bucket{} bucket = &Bucket{}
_, err = s. _, err = s.
Where("list_id = ?", listID). Where("project_id = ?", projectID).
OrderBy("position asc"). OrderBy("position asc").
Get(bucket) Get(bucket)
return return
} }
func getDoneBucketForList(s *xorm.Session, listID int64) (bucket *Bucket, err error) { func getDoneBucketForProject(s *xorm.Session, projectID int64) (bucket *Bucket, err error) {
bucket = &Bucket{} bucket = &Bucket{}
exists, err := s. exists, err := s.
Where("list_id = ? and is_done_bucket = ?", listID, true). Where("project_id = ? and is_done_bucket = ?", projectID, true).
Get(bucket) Get(bucket)
if err != nil { if err != nil {
return nil, err return nil, err
@ -101,14 +101,14 @@ func getDoneBucketForList(s *xorm.Session, listID int64) (bucket *Bucket, err er
return return
} }
// ReadAll returns all buckets with their tasks for a certain list // ReadAll returns all buckets with their tasks for a certain project
// @Summary Get all kanban buckets of a list // @Summary Get all kanban buckets of a project
// @Description Returns all kanban buckets with belong to a list including their tasks. // @Description Returns all kanban buckets with belong to a project including their tasks.
// @tags task // @tags task
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Security JWTKeyAuth // @Security JWTKeyAuth
// @Param id path int true "List Id" // @Param id path int true "Project Id"
// @Param page query int false "The page number for tasks. Used for pagination. If not provided, the first page of results is returned." // @Param page query int false "The page number for tasks. Used for pagination. If not provided, the first page of results is returned."
// @Param per_page query int false "The maximum number of tasks per bucket per page. This parameter is limited by the configured maximum of items per page." // @Param per_page query int false "The maximum number of tasks per bucket per page. This parameter is limited by the configured maximum of items per page."
// @Param s query string false "Search tasks by task text." // @Param s query string false "Search tasks by task text."
@ -119,15 +119,15 @@ func getDoneBucketForList(s *xorm.Session, listID int64) (bucket *Bucket, err er
// @Param filter_include_nulls query string false "If set to true the result will include filtered fields whose value is set to `null`. Available values are `true` or `false`. Defaults to `false`." // @Param filter_include_nulls query string false "If set to true the result will include filtered fields whose value is set to `null`. Available values are `true` or `false`. Defaults to `false`."
// @Success 200 {array} models.Bucket "The buckets with their tasks" // @Success 200 {array} models.Bucket "The buckets with their tasks"
// @Failure 500 {object} models.Message "Internal server error" // @Failure 500 {object} models.Message "Internal server error"
// @Router /lists/{id}/buckets [get] // @Router /projects/{id}/buckets [get]
func (b *Bucket) ReadAll(s *xorm.Session, auth web.Auth, search string, page int, perPage int) (result interface{}, resultCount int, numberOfTotalItems int64, err error) { func (b *Bucket) ReadAll(s *xorm.Session, auth web.Auth, search string, page int, perPage int) (result interface{}, resultCount int, numberOfTotalItems int64, err error) {
list, err := GetListSimpleByID(s, b.ListID) project, err := GetProjectSimpleByID(s, b.ProjectID)
if err != nil { if err != nil {
return nil, 0, 0, err return nil, 0, 0, err
} }
can, _, err := list.CanRead(s, auth) can, _, err := project.CanRead(s, auth)
if err != nil { if err != nil {
return nil, 0, 0, err return nil, 0, 0, err
} }
@ -135,10 +135,10 @@ func (b *Bucket) ReadAll(s *xorm.Session, auth web.Auth, search string, page int
return nil, 0, 0, ErrGenericForbidden{} return nil, 0, 0, ErrGenericForbidden{}
} }
// Get all buckets for this list // Get all buckets for this project
buckets := []*Bucket{} buckets := []*Bucket{}
err = s. err = s.
Where("list_id = ?", b.ListID). Where("project_id = ?", b.ProjectID).
OrderBy("position"). OrderBy("position").
Find(&buckets) Find(&buckets)
if err != nil { if err != nil {
@ -202,7 +202,7 @@ func (b *Bucket) ReadAll(s *xorm.Session, auth web.Auth, search string, page int
opts.filters[bucketFilterIndex].value = id opts.filters[bucketFilterIndex].value = id
ts, _, _, err := getRawTasksForLists(s, []*List{{ID: bucket.ListID}}, auth, opts) ts, _, _, err := getRawTasksForProjects(s, []*Project{{ID: bucket.ProjectID}}, auth, opts)
if err != nil { if err != nil {
return nil, 0, 0, err return nil, 0, 0, err
} }
@ -226,7 +226,7 @@ func (b *Bucket) ReadAll(s *xorm.Session, auth web.Auth, search string, page int
for _, task := range tasks { for _, task := range tasks {
// Check if the bucket exists in the map to prevent nil pointer panics // Check if the bucket exists in the map to prevent nil pointer panics
if _, exists := bucketMap[task.BucketID]; !exists { if _, exists := bucketMap[task.BucketID]; !exists {
log.Debugf("Tried to put task %d into bucket %d which does not exist in list %d", task.ID, task.BucketID, b.ListID) log.Debugf("Tried to put task %d into bucket %d which does not exist in project %d", task.ID, task.BucketID, b.ProjectID)
continue continue
} }
bucketMap[task.BucketID].Tasks = append(bucketMap[task.BucketID].Tasks, task) bucketMap[task.BucketID].Tasks = append(bucketMap[task.BucketID].Tasks, task)
@ -237,18 +237,18 @@ func (b *Bucket) ReadAll(s *xorm.Session, auth web.Auth, search string, page int
// Create creates a new bucket // Create creates a new bucket
// @Summary Create a new bucket // @Summary Create a new bucket
// @Description Creates a new kanban bucket on a list. // @Description Creates a new kanban bucket on a project.
// @tags task // @tags task
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Security JWTKeyAuth // @Security JWTKeyAuth
// @Param id path int true "List Id" // @Param id path int true "Project Id"
// @Param bucket body models.Bucket true "The bucket object" // @Param bucket body models.Bucket true "The bucket object"
// @Success 200 {object} models.Bucket "The created bucket object." // @Success 200 {object} models.Bucket "The created bucket object."
// @Failure 400 {object} web.HTTPError "Invalid bucket object provided." // @Failure 400 {object} web.HTTPError "Invalid bucket object provided."
// @Failure 404 {object} web.HTTPError "The list does not exist." // @Failure 404 {object} web.HTTPError "The project does not exist."
// @Failure 500 {object} models.Message "Internal error" // @Failure 500 {object} models.Message "Internal error"
// @Router /lists/{id}/buckets [put] // @Router /projects/{id}/buckets [put]
func (b *Bucket) Create(s *xorm.Session, a web.Auth) (err error) { func (b *Bucket) Create(s *xorm.Session, a web.Auth) (err error) {
b.CreatedBy, err = GetUserOrLinkShareUser(s, a) b.CreatedBy, err = GetUserOrLinkShareUser(s, a)
if err != nil { if err != nil {
@ -273,24 +273,24 @@ func (b *Bucket) Create(s *xorm.Session, a web.Auth) (err error) {
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Security JWTKeyAuth // @Security JWTKeyAuth
// @Param listID path int true "List Id" // @Param projectID path int true "Project Id"
// @Param bucketID path int true "Bucket Id" // @Param bucketID path int true "Bucket Id"
// @Param bucket body models.Bucket true "The bucket object" // @Param bucket body models.Bucket true "The bucket object"
// @Success 200 {object} models.Bucket "The created bucket object." // @Success 200 {object} models.Bucket "The created bucket object."
// @Failure 400 {object} web.HTTPError "Invalid bucket object provided." // @Failure 400 {object} web.HTTPError "Invalid bucket object provided."
// @Failure 404 {object} web.HTTPError "The bucket does not exist." // @Failure 404 {object} web.HTTPError "The bucket does not exist."
// @Failure 500 {object} models.Message "Internal error" // @Failure 500 {object} models.Message "Internal error"
// @Router /lists/{listID}/buckets/{bucketID} [post] // @Router /projects/{projectID}/buckets/{bucketID} [post]
func (b *Bucket) Update(s *xorm.Session, a web.Auth) (err error) { func (b *Bucket) Update(s *xorm.Session, a web.Auth) (err error) {
doneBucket, err := getDoneBucketForList(s, b.ListID) doneBucket, err := getDoneBucketForProject(s, b.ProjectID)
if err != nil { if err != nil {
return err return err
} }
if doneBucket != nil && doneBucket.IsDoneBucket && b.IsDoneBucket && doneBucket.ID != b.ID { if doneBucket != nil && doneBucket.IsDoneBucket && b.IsDoneBucket && doneBucket.ID != b.ID {
return &ErrOnlyOneDoneBucketPerList{ return &ErrOnlyOneDoneBucketPerProject{
BucketID: b.ID, BucketID: b.ID,
ListID: b.ListID, ProjectID: b.ProjectID,
DoneBucketID: doneBucket.ID, DoneBucketID: doneBucket.ID,
} }
} }
@ -309,28 +309,28 @@ func (b *Bucket) Update(s *xorm.Session, a web.Auth) (err error) {
// Delete removes a bucket, but no tasks // Delete removes a bucket, but no tasks
// @Summary Deletes an existing bucket // @Summary Deletes an existing bucket
// @Description Deletes an existing kanban bucket and dissociates all of its task. It does not delete any tasks. You cannot delete the last bucket on a list. // @Description Deletes an existing kanban bucket and dissociates all of its task. It does not delete any tasks. You cannot delete the last bucket on a project.
// @tags task // @tags task
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Security JWTKeyAuth // @Security JWTKeyAuth
// @Param listID path int true "List Id" // @Param projectID path int true "Project Id"
// @Param bucketID path int true "Bucket Id" // @Param bucketID path int true "Bucket Id"
// @Success 200 {object} models.Message "Successfully deleted." // @Success 200 {object} models.Message "Successfully deleted."
// @Failure 404 {object} web.HTTPError "The bucket does not exist." // @Failure 404 {object} web.HTTPError "The bucket does not exist."
// @Failure 500 {object} models.Message "Internal error" // @Failure 500 {object} models.Message "Internal error"
// @Router /lists/{listID}/buckets/{bucketID} [delete] // @Router /projects/{projectID}/buckets/{bucketID} [delete]
func (b *Bucket) Delete(s *xorm.Session, a web.Auth) (err error) { func (b *Bucket) Delete(s *xorm.Session, a web.Auth) (err error) {
// Prevent removing the last bucket // Prevent removing the last bucket
total, err := s.Where("list_id = ?", b.ListID).Count(&Bucket{}) total, err := s.Where("project_id = ?", b.ProjectID).Count(&Bucket{})
if err != nil { if err != nil {
return return
} }
if total <= 1 { if total <= 1 {
return ErrCannotRemoveLastBucket{ return ErrCannotRemoveLastBucket{
BucketID: b.ID, BucketID: b.ID,
ListID: b.ListID, ProjectID: b.ProjectID,
} }
} }
@ -341,7 +341,7 @@ func (b *Bucket) Delete(s *xorm.Session, a web.Auth) (err error) {
} }
// Get the default bucket // Get the default bucket
defaultBucket, err := getDefaultBucket(s, b.ListID) defaultBucket, err := getDefaultBucket(s, b.ProjectID)
if err != nil { if err != nil {
return return
} }

View File

@ -23,7 +23,7 @@ import (
// CanCreate checks if a user can create a new bucket // CanCreate checks if a user can create a new bucket
func (b *Bucket) CanCreate(s *xorm.Session, a web.Auth) (bool, error) { func (b *Bucket) CanCreate(s *xorm.Session, a web.Auth) (bool, error) {
l := &List{ID: b.ListID} l := &Project{ID: b.ProjectID}
return l.CanWrite(s, a) return l.CanWrite(s, a)
} }
@ -43,6 +43,6 @@ func (b *Bucket) canDoBucket(s *xorm.Session, a web.Auth) (bool, error) {
if err != nil { if err != nil {
return false, err return false, err
} }
l := &List{ID: bb.ListID} l := &Project{ID: bb.ProjectID}
return l.CanWrite(s, a) return l.CanWrite(s, a)
} }

View File

@ -33,7 +33,7 @@ func TestBucket_ReadAll(t *testing.T) {
defer s.Close() defer s.Close()
testuser := &user.User{ID: 1} testuser := &user.User{ID: 1}
b := &Bucket{ListID: 1} b := &Bucket{ProjectID: 1}
bucketsInterface, _, _, err := b.ReadAll(s, testuser, "", 0, 0) bucketsInterface, _, _, err := b.ReadAll(s, testuser, "", 0, 0)
assert.NoError(t, err) assert.NoError(t, err)
@ -53,7 +53,7 @@ func TestBucket_ReadAll(t *testing.T) {
assert.Len(t, buckets[1].Tasks, 3) assert.Len(t, buckets[1].Tasks, 3)
assert.Len(t, buckets[2].Tasks, 3) assert.Len(t, buckets[2].Tasks, 3)
// Assert we have bucket 1, 2, 3 but not 4 (that belongs to a different list) and their position // Assert we have bucket 1, 2, 3 but not 4 (that belongs to a different project) and their position
assert.Equal(t, int64(1), buckets[0].ID) assert.Equal(t, int64(1), buckets[0].ID)
assert.Equal(t, int64(2), buckets[1].ID) assert.Equal(t, int64(2), buckets[1].ID)
assert.Equal(t, int64(3), buckets[2].ID) assert.Equal(t, int64(3), buckets[2].ID)
@ -77,7 +77,7 @@ func TestBucket_ReadAll(t *testing.T) {
testuser := &user.User{ID: 1} testuser := &user.User{ID: 1}
b := &Bucket{ b := &Bucket{
ListID: 1, ProjectID: 1,
TaskCollection: TaskCollection{ TaskCollection: TaskCollection{
FilterBy: []string{"title"}, FilterBy: []string{"title"},
FilterComparator: []string{"like"}, FilterComparator: []string{"like"},
@ -98,11 +98,11 @@ func TestBucket_ReadAll(t *testing.T) {
defer s.Close() defer s.Close()
linkShare := &LinkSharing{ linkShare := &LinkSharing{
ID: 1, ID: 1,
ListID: 1, ProjectID: 1,
Right: RightRead, Right: RightRead,
} }
b := &Bucket{ListID: 1} b := &Bucket{ProjectID: 1}
result, _, _, err := b.ReadAll(s, linkShare, "", 0, 0) result, _, _, err := b.ReadAll(s, linkShare, "", 0, 0)
assert.NoError(t, err) assert.NoError(t, err)
buckets, _ := result.([]*Bucket) buckets, _ := result.([]*Bucket)
@ -116,7 +116,7 @@ func TestBucket_ReadAll(t *testing.T) {
defer s.Close() defer s.Close()
testuser := &user.User{ID: 12} testuser := &user.User{ID: 12}
b := &Bucket{ListID: 23} b := &Bucket{ProjectID: 23}
result, _, _, err := b.ReadAll(s, testuser, "", 0, 0) result, _, _, err := b.ReadAll(s, testuser, "", 0, 0)
assert.NoError(t, err) assert.NoError(t, err)
buckets, _ := result.([]*Bucket) buckets, _ := result.([]*Bucket)
@ -135,8 +135,8 @@ func TestBucket_Delete(t *testing.T) {
defer s.Close() defer s.Close()
b := &Bucket{ b := &Bucket{
ID: 2, // The second bucket only has 3 tasks ID: 2, // The second bucket only has 3 tasks
ListID: 1, ProjectID: 1,
} }
err := b.Delete(s, user) err := b.Delete(s, user)
assert.NoError(t, err) assert.NoError(t, err)
@ -149,18 +149,18 @@ func TestBucket_Delete(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, tasks, 16) assert.Len(t, tasks, 16)
db.AssertMissing(t, "buckets", map[string]interface{}{ db.AssertMissing(t, "buckets", map[string]interface{}{
"id": 2, "id": 2,
"list_id": 1, "project_id": 1,
}) })
}) })
t.Run("last bucket in list", func(t *testing.T) { t.Run("last bucket in project", func(t *testing.T) {
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
s := db.NewSession() s := db.NewSession()
defer s.Close() defer s.Close()
b := &Bucket{ b := &Bucket{
ID: 34, ID: 34,
ListID: 18, ProjectID: 18,
} }
err := b.Delete(s, user) err := b.Delete(s, user)
assert.Error(t, err) assert.Error(t, err)
@ -169,8 +169,8 @@ func TestBucket_Delete(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
db.AssertExists(t, "buckets", map[string]interface{}{ db.AssertExists(t, "buckets", map[string]interface{}{
"id": 34, "id": 34,
"list_id": 18, "project_id": 18,
}, false) }, false)
}) })
} }
@ -217,19 +217,19 @@ func TestBucket_Update(t *testing.T) {
testAndAssertBucketUpdate(t, b, s) testAndAssertBucketUpdate(t, b, s)
}) })
t.Run("only one done bucket per list", func(t *testing.T) { t.Run("only one done bucket per project", func(t *testing.T) {
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
s := db.NewSession() s := db.NewSession()
defer s.Close() defer s.Close()
b := &Bucket{ b := &Bucket{
ID: 1, ID: 1,
ListID: 1, ProjectID: 1,
IsDoneBucket: true, IsDoneBucket: true,
} }
err := b.Update(s, &user.User{ID: 1}) err := b.Update(s, &user.User{ID: 1})
assert.Error(t, err) assert.Error(t, err)
assert.True(t, IsErrOnlyOneDoneBucketPerList(err)) assert.True(t, IsErrOnlyOneDoneBucketPerProject(err))
}) })
} }

View File

@ -77,7 +77,7 @@ func (l *Label) hasAccessToLabel(s *xorm.Session, a web.Auth) (has bool, maxRigh
builder. builder.
Select("id"). Select("id").
From("tasks"). From("tasks").
Where(builder.In("list_id", getUserListsStatement(u.ID).Select("l.id"))), Where(builder.In("project_id", getUserProjectsStatement(u.ID).Select("l.id"))),
) )
ll := &LabelTask{} ll := &LabelTask{}

View File

@ -35,7 +35,7 @@ import (
type LabelTask struct { type LabelTask struct {
// The unique, numeric id of this label. // The unique, numeric id of this label.
ID int64 `xorm:"bigint autoincr not null unique pk" json:"-"` ID int64 `xorm:"bigint autoincr not null unique pk" json:"-"`
TaskID int64 `xorm:"bigint INDEX not null" json:"-" param:"listtask"` TaskID int64 `xorm:"bigint INDEX not null" json:"-" param:"projecttask"`
// The label id you want to associate with a task. // The label id you want to associate with a task.
LabelID int64 `xorm:"bigint INDEX not null" json:"label_id" param:"label"` LabelID int64 `xorm:"bigint INDEX not null" json:"label_id" param:"label"`
// A timestamp when this task was created. You cannot change this value. // A timestamp when this task was created. You cannot change this value.
@ -52,7 +52,7 @@ func (LabelTask) TableName() string {
// Delete deletes a label on a task // Delete deletes a label on a task
// @Summary Remove a label from a task // @Summary Remove a label from a task
// @Description Remove a label from a task. The user needs to have write-access to the list to be able do this. // @Description Remove a label from a task. The user needs to have write-access to the project to be able do this.
// @tags labels // @tags labels
// @Accept json // @Accept json
// @Produce json // @Produce json
@ -71,7 +71,7 @@ func (lt *LabelTask) Delete(s *xorm.Session, a web.Auth) (err error) {
// Create adds a label to a task // Create adds a label to a task
// @Summary Add a label to a task // @Summary Add a label to a task
// @Description Add a label to a task. The user needs to have write-access to the list to be able do this. // @Description Add a label to a task. The user needs to have write-access to the project to be able do this.
// @tags labels // @tags labels
// @Accept json // @Accept json
// @Produce json // @Produce json
@ -100,7 +100,7 @@ func (lt *LabelTask) Create(s *xorm.Session, a web.Auth) (err error) {
return err return err
} }
err = updateListByTaskID(s, lt.TaskID) err = updateProjectByTaskID(s, lt.TaskID)
return return
} }
@ -180,7 +180,7 @@ func GetLabelsByTaskIDs(s *xorm.Session, opts *LabelByTaskIDsOptions) (ls []*Lab
builder. builder.
Select("id"). Select("id").
From("tasks"). From("tasks").
Where(builder.In("list_id", getUserListsStatement(opts.GetForUser).Select("l.id"))), Where(builder.In("project_id", getUserProjectsStatement(opts.GetForUser).Select("l.id"))),
), cond) ), cond)
} }
if opts.GetUnusedLabels { if opts.GetUnusedLabels {
@ -309,17 +309,17 @@ func (t *Task) UpdateTaskLabels(s *xorm.Session, creator web.Auth, labels []*Lab
for _, oldLabel := range allLabels { for _, oldLabel := range allLabels {
found = false found = false
if newLabels[oldLabel.ID] != nil { if newLabels[oldLabel.ID] != nil {
found = true // If a new label is already in the list with old labels found = true // If a new label is already in the project with old labels
} }
// Put all labels which are only on the old list to the trash // Put all labels which are only on the old project to the trash
if !found { if !found {
labelsToDelete = append(labelsToDelete, oldLabel.ID) labelsToDelete = append(labelsToDelete, oldLabel.ID)
} else { } else {
t.Labels = append(t.Labels, oldLabel) t.Labels = append(t.Labels, oldLabel)
} }
// Put it in a list with all old labels, just using the loop here // Put it in a project with all old labels, just using the loop here
oldLabels[oldLabel.ID] = oldLabel oldLabels[oldLabel.ID] = oldLabel
} }
@ -365,7 +365,7 @@ func (t *Task) UpdateTaskLabels(s *xorm.Session, creator web.Auth, labels []*Lab
t.Labels = append(t.Labels, label) t.Labels = append(t.Labels, label)
} }
err = updateListLastUpdated(s, &List{ID: t.ListID}) err = updateProjectLastUpdated(s, &Project{ID: t.ProjectID})
return return
} }
@ -373,7 +373,7 @@ func (t *Task) UpdateTaskLabels(s *xorm.Session, creator web.Auth, labels []*Lab
type LabelTaskBulk struct { type LabelTaskBulk struct {
// All labels you want to update at once. // All labels you want to update at once.
Labels []*Label `json:"labels"` Labels []*Label `json:"labels"`
TaskID int64 `json:"-" param:"listtask"` TaskID int64 `json:"-" param:"projecttask"`
web.CRUDable `json:"-"` web.CRUDable `json:"-"`
web.Rights `json:"-"` web.Rights `json:"-"`

View File

@ -42,17 +42,17 @@ const (
SharingTypeWithPassword SharingTypeWithPassword
) )
// LinkSharing represents a shared list // LinkSharing represents a shared project
type LinkSharing struct { type LinkSharing struct {
// The ID of the shared thing // The ID of the shared thing
ID int64 `xorm:"bigint autoincr not null unique pk" json:"id" param:"share"` ID int64 `xorm:"bigint autoincr not null unique pk" json:"id" param:"share"`
// The public id to get this shared list // The public id to get this shared project
Hash string `xorm:"varchar(40) not null unique" json:"hash" param:"hash"` Hash string `xorm:"varchar(40) not null unique" json:"hash" param:"hash"`
// The name of this link share. All actions someone takes while being authenticated with that link will appear with that name. // The name of this link share. All actions someone takes while being authenticated with that link will appear with that name.
Name string `xorm:"text null" json:"name"` Name string `xorm:"text null" json:"name"`
// The ID of the shared list // The ID of the shared project
ListID int64 `xorm:"bigint not null" json:"-" param:"list"` ProjectID int64 `xorm:"bigint not null" json:"-" param:"project"`
// The right this list is shared with. 0 = Read only, 1 = Read & Write, 2 = Admin. See the docs for more details. // The right this project is shared with. 0 = Read only, 1 = Read & Write, 2 = Admin. See the docs for more details.
Right Right `xorm:"bigint INDEX not null default 0" json:"right" valid:"length(0|2)" maximum:"2" default:"0"` Right Right `xorm:"bigint INDEX not null default 0" json:"right" valid:"length(0|2)" maximum:"2" default:"0"`
// The kind of this link. 0 = undefined, 1 = without password, 2 = with password. // The kind of this link. 0 = undefined, 1 = without password, 2 = with password.
@ -61,11 +61,11 @@ type LinkSharing struct {
// The password of this link share. You can only set it, not retrieve it after the link share has been created. // The password of this link share. You can only set it, not retrieve it after the link share has been created.
Password string `xorm:"text null" json:"password"` Password string `xorm:"text null" json:"password"`
// The user who shared this list // The user who shared this project
SharedBy *user.User `xorm:"-" json:"shared_by"` SharedBy *user.User `xorm:"-" json:"shared_by"`
SharedByID int64 `xorm:"bigint INDEX not null" json:"-"` SharedByID int64 `xorm:"bigint INDEX not null" json:"-"`
// A timestamp when this list was shared. You cannot change this value. // A timestamp when this project was shared. You cannot change this value.
Created time.Time `xorm:"created not null" json:"created"` Created time.Time `xorm:"created not null" json:"created"`
// A timestamp when this share was last updated. You cannot change this value. // A timestamp when this share was last updated. You cannot change this value.
Updated time.Time `xorm:"updated not null" json:"updated"` Updated time.Time `xorm:"updated not null" json:"updated"`
@ -89,7 +89,7 @@ func GetLinkShareFromClaims(claims jwt.MapClaims) (share *LinkSharing, err error
share = &LinkSharing{} share = &LinkSharing{}
share.ID = int64(claims["id"].(float64)) share.ID = int64(claims["id"].(float64))
share.Hash = claims["hash"].(string) share.Hash = claims["hash"].(string)
share.ListID = int64(claims["list_id"].(float64)) share.ProjectID = int64(claims["project_id"].(float64))
share.Right = Right(claims["right"].(float64)) share.Right = Right(claims["right"].(float64))
share.SharedByID = int64(claims["sharedByID"].(float64)) share.SharedByID = int64(claims["sharedByID"].(float64))
return return
@ -114,21 +114,21 @@ func (share *LinkSharing) toUser() *user.User {
} }
} }
// Create creates a new link share for a given list // Create creates a new link share for a given project
// @Summary Share a list via link // @Summary Share a project via link
// @Description Share a list via link. The user needs to have write-access to the list to be able do this. // @Description Share a project via link. The user needs to have write-access to the project to be able do this.
// @tags sharing // @tags sharing
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Security JWTKeyAuth // @Security JWTKeyAuth
// @Param list path int true "List ID" // @Param project path int true "Project ID"
// @Param label body models.LinkSharing true "The new link share object" // @Param label body models.LinkSharing true "The new link share object"
// @Success 201 {object} models.LinkSharing "The created link share object." // @Success 201 {object} models.LinkSharing "The created link share object."
// @Failure 400 {object} web.HTTPError "Invalid link share object provided." // @Failure 400 {object} web.HTTPError "Invalid link share object provided."
// @Failure 403 {object} web.HTTPError "Not allowed to add the list share." // @Failure 403 {object} web.HTTPError "Not allowed to add the project share."
// @Failure 404 {object} web.HTTPError "The list does not exist." // @Failure 404 {object} web.HTTPError "The project does not exist."
// @Failure 500 {object} models.Message "Internal error" // @Failure 500 {object} models.Message "Internal error"
// @Router /lists/{list}/shares [put] // @Router /projects/{project}/shares [put]
func (share *LinkSharing) Create(s *xorm.Session, a web.Auth) (err error) { func (share *LinkSharing) Create(s *xorm.Session, a web.Auth) (err error) {
err = share.Right.isValid() err = share.Right.isValid()
@ -156,48 +156,48 @@ func (share *LinkSharing) Create(s *xorm.Session, a web.Auth) (err error) {
} }
// ReadOne returns one share // ReadOne returns one share
// @Summary Get one link shares for a list // @Summary Get one link shares for a project
// @Description Returns one link share by its ID. // @Description Returns one link share by its ID.
// @tags sharing // @tags sharing
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param list path int true "List ID" // @Param project path int true "Project ID"
// @Param share path int true "Share ID" // @Param share path int true "Share ID"
// @Security JWTKeyAuth // @Security JWTKeyAuth
// @Success 200 {object} models.LinkSharing "The share links" // @Success 200 {object} models.LinkSharing "The share links"
// @Failure 403 {object} web.HTTPError "No access to the list" // @Failure 403 {object} web.HTTPError "No access to the project"
// @Failure 404 {object} web.HTTPError "Share Link not found." // @Failure 404 {object} web.HTTPError "Share Link not found."
// @Failure 500 {object} models.Message "Internal error" // @Failure 500 {object} models.Message "Internal error"
// @Router /lists/{list}/shares/{share} [get] // @Router /projects/{project}/shares/{share} [get]
func (share *LinkSharing) ReadOne(s *xorm.Session, a web.Auth) (err error) { func (share *LinkSharing) ReadOne(s *xorm.Session, a web.Auth) (err error) {
exists, err := s.Where("id = ?", share.ID).Get(share) exists, err := s.Where("id = ?", share.ID).Get(share)
if err != nil { if err != nil {
return err return err
} }
if !exists { if !exists {
return ErrListShareDoesNotExist{ID: share.ID, Hash: share.Hash} return ErrProjectShareDoesNotExist{ID: share.ID, Hash: share.Hash}
} }
share.Password = "" share.Password = ""
return return
} }
// ReadAll returns all shares for a given list // ReadAll returns all shares for a given project
// @Summary Get all link shares for a list // @Summary Get all link shares for a project
// @Description Returns all link shares which exist for a given list // @Description Returns all link shares which exist for a given project
// @tags sharing // @tags sharing
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param list path int true "List ID" // @Param project path int true "Project ID"
// @Param page query int false "The page number. Used for pagination. If not provided, the first page of results is returned." // @Param page query int false "The page number. Used for pagination. If not provided, the first page of results is returned."
// @Param per_page query int false "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page." // @Param per_page query int false "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page."
// @Param s query string false "Search shares by hash." // @Param s query string false "Search shares by hash."
// @Security JWTKeyAuth // @Security JWTKeyAuth
// @Success 200 {array} models.LinkSharing "The share links" // @Success 200 {array} models.LinkSharing "The share links"
// @Failure 500 {object} models.Message "Internal error" // @Failure 500 {object} models.Message "Internal error"
// @Router /lists/{list}/shares [get] // @Router /projects/{project}/shares [get]
func (share *LinkSharing) ReadAll(s *xorm.Session, a web.Auth, search string, page int, perPage int) (result interface{}, resultCount int, totalItems int64, err error) { func (share *LinkSharing) ReadAll(s *xorm.Session, a web.Auth, search string, page int, perPage int) (result interface{}, resultCount int, totalItems int64, err error) {
list := &List{ID: share.ListID} project := &Project{ID: share.ProjectID}
can, _, err := list.CanRead(s, a) can, _, err := project.CanRead(s, a)
if err != nil { if err != nil {
return nil, 0, 0, err return nil, 0, 0, err
} }
@ -210,7 +210,7 @@ func (share *LinkSharing) ReadAll(s *xorm.Session, a web.Auth, search string, pa
var shares []*LinkSharing var shares []*LinkSharing
query := s. query := s.
Where(builder.And( Where(builder.And(
builder.Eq{"list_id": share.ListID}, builder.Eq{"project_id": share.ProjectID},
builder.Or( builder.Or(
db.ILIKE("hash", search), db.ILIKE("hash", search),
db.ILIKE("name", search), db.ILIKE("name", search),
@ -246,7 +246,7 @@ func (share *LinkSharing) ReadAll(s *xorm.Session, a web.Auth, search string, pa
// Total count // Total count
totalItems, err = s. totalItems, err = s.
Where("list_id = ? AND hash LIKE ?", share.ListID, "%"+search+"%"). Where("project_id = ? AND hash LIKE ?", share.ProjectID, "%"+search+"%").
Count(&LinkSharing{}) Count(&LinkSharing{})
if err != nil { if err != nil {
return nil, 0, 0, err return nil, 0, 0, err
@ -257,18 +257,18 @@ func (share *LinkSharing) ReadAll(s *xorm.Session, a web.Auth, search string, pa
// Delete removes a link share // Delete removes a link share
// @Summary Remove a link share // @Summary Remove a link share
// @Description Remove a link share. The user needs to have write-access to the list to be able do this. // @Description Remove a link share. The user needs to have write-access to the project to be able do this.
// @tags sharing // @tags sharing
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Security JWTKeyAuth // @Security JWTKeyAuth
// @Param list path int true "List ID" // @Param project path int true "Project ID"
// @Param share path int true "Share Link ID" // @Param share path int true "Share Link ID"
// @Success 200 {object} models.Message "The link was successfully removed." // @Success 200 {object} models.Message "The link was successfully removed."
// @Failure 403 {object} web.HTTPError "Not allowed to remove the link." // @Failure 403 {object} web.HTTPError "Not allowed to remove the link."
// @Failure 404 {object} web.HTTPError "Share Link not found." // @Failure 404 {object} web.HTTPError "Share Link not found."
// @Failure 500 {object} models.Message "Internal error" // @Failure 500 {object} models.Message "Internal error"
// @Router /lists/{list}/shares/{share} [delete] // @Router /projects/{project}/shares/{share} [delete]
func (share *LinkSharing) Delete(s *xorm.Session, a web.Auth) (err error) { func (share *LinkSharing) Delete(s *xorm.Session, a web.Auth) (err error) {
_, err = s.Where("id = ?", share.ID).Delete(share) _, err = s.Where("id = ?", share.ID).Delete(share)
return return
@ -282,19 +282,19 @@ func GetLinkShareByHash(s *xorm.Session, hash string) (share *LinkSharing, err e
return return
} }
if !has { if !has {
return share, ErrListShareDoesNotExist{Hash: hash} return share, ErrProjectShareDoesNotExist{Hash: hash}
} }
return return
} }
// GetListByShareHash returns a link share by its hash // GetProjectByShareHash returns a link share by its hash
func GetListByShareHash(s *xorm.Session, hash string) (list *List, err error) { func GetProjectByShareHash(s *xorm.Session, hash string) (project *Project, err error) {
share, err := GetLinkShareByHash(s, hash) share, err := GetLinkShareByHash(s, hash)
if err != nil { if err != nil {
return return
} }
list, err = GetListSimpleByID(s, share.ListID) project, err = GetProjectSimpleByID(s, share.ProjectID)
return return
} }
@ -306,7 +306,7 @@ func GetLinkShareByID(s *xorm.Session, id int64) (share *LinkSharing, err error)
return return
} }
if !has { if !has {
return share, ErrListShareDoesNotExist{ID: id} return share, ErrProjectShareDoesNotExist{ID: id}
} }
return return
} }

View File

@ -28,7 +28,7 @@ func (share *LinkSharing) CanRead(s *xorm.Session, a web.Auth) (bool, int, error
return false, 0, nil return false, 0, nil
} }
l, err := GetListByShareHash(s, share.Hash) l, err := GetProjectByShareHash(s, share.Hash)
if err != nil { if err != nil {
return false, 0, err return false, 0, err
} }
@ -56,7 +56,7 @@ func (share *LinkSharing) canDoLinkShare(s *xorm.Session, a web.Auth) (bool, err
return false, nil return false, nil
} }
l, err := GetListSimpleByID(s, share.ListID) l, err := GetProjectSimpleByID(s, share.ProjectID)
if err != nil { if err != nil {
return false, err return false, err
} }

View File

@ -33,8 +33,8 @@ func TestLinkSharing_Create(t *testing.T) {
defer s.Close() defer s.Close()
share := &LinkSharing{ share := &LinkSharing{
ListID: 1, ProjectID: 1,
Right: RightRead, Right: RightRead,
} }
err := share.Create(s, doer) err := share.Create(s, doer)
@ -52,8 +52,8 @@ func TestLinkSharing_Create(t *testing.T) {
defer s.Close() defer s.Close()
share := &LinkSharing{ share := &LinkSharing{
ListID: 1, ProjectID: 1,
Right: Right(123), Right: Right(123),
} }
err := share.Create(s, doer) err := share.Create(s, doer)
@ -66,9 +66,9 @@ func TestLinkSharing_Create(t *testing.T) {
defer s.Close() defer s.Close()
share := &LinkSharing{ share := &LinkSharing{
ListID: 1, ProjectID: 1,
Right: RightRead, Right: RightRead,
Password: "somePassword", Password: "somePassword",
} }
err := share.Create(s, doer) err := share.Create(s, doer)
@ -92,7 +92,7 @@ func TestLinkSharing_ReadAll(t *testing.T) {
defer s.Close() defer s.Close()
share := &LinkSharing{ share := &LinkSharing{
ListID: 1, ProjectID: 1,
} }
all, _, _, err := share.ReadAll(s, doer, "", 1, -1) all, _, _, err := share.ReadAll(s, doer, "", 1, -1)
shares := all.([]*LinkSharing) shares := all.([]*LinkSharing)
@ -109,7 +109,7 @@ func TestLinkSharing_ReadAll(t *testing.T) {
defer s.Close() defer s.Close()
share := &LinkSharing{ share := &LinkSharing{
ListID: 1, ProjectID: 1,
} }
all, _, _, err := share.ReadAll(s, doer, "wITHPASS", 1, -1) all, _, _, err := share.ReadAll(s, doer, "wITHPASS", 1, -1)
shares := all.([]*LinkSharing) shares := all.([]*LinkSharing)

View File

@ -33,123 +33,123 @@ import (
"xorm.io/xorm" "xorm.io/xorm"
) )
// List represents a list of tasks // Project represents a project of tasks
type List struct { type Project struct {
// The unique, numeric id of this list. // The unique, numeric id of this project.
ID int64 `xorm:"bigint autoincr not null unique pk" json:"id" param:"list"` ID int64 `xorm:"bigint autoincr not null unique pk" json:"id" param:"project"`
// The title of the list. You'll see this in the namespace overview. // The title of the project. You'll see this in the namespace overview.
Title string `xorm:"varchar(250) not null" json:"title" valid:"required,runelength(1|250)" minLength:"1" maxLength:"250"` Title string `xorm:"varchar(250) not null" json:"title" valid:"required,runelength(1|250)" minLength:"1" maxLength:"250"`
// The description of the list. // The description of the project.
Description string `xorm:"longtext null" json:"description"` Description string `xorm:"longtext null" json:"description"`
// The unique list short identifier. Used to build task identifiers. // The unique project short identifier. Used to build task identifiers.
Identifier string `xorm:"varchar(10) null" json:"identifier" valid:"runelength(0|10)" minLength:"0" maxLength:"10"` Identifier string `xorm:"varchar(10) null" json:"identifier" valid:"runelength(0|10)" minLength:"0" maxLength:"10"`
// The hex color of this list // The hex color of this project
HexColor string `xorm:"varchar(6) null" json:"hex_color" valid:"runelength(0|6)" maxLength:"6"` HexColor string `xorm:"varchar(6) null" json:"hex_color" valid:"runelength(0|6)" maxLength:"6"`
OwnerID int64 `xorm:"bigint INDEX not null" json:"-"` OwnerID int64 `xorm:"bigint INDEX not null" json:"-"`
NamespaceID int64 `xorm:"bigint INDEX not null" json:"namespace_id" param:"namespace"` NamespaceID int64 `xorm:"bigint INDEX not null" json:"namespace_id" param:"namespace"`
// The user who created this list. // The user who created this project.
Owner *user.User `xorm:"-" json:"owner" valid:"-"` Owner *user.User `xorm:"-" json:"owner" valid:"-"`
// Whether or not a list is archived. // Whether or not a project is archived.
IsArchived bool `xorm:"not null default false" json:"is_archived" query:"is_archived"` IsArchived bool `xorm:"not null default false" json:"is_archived" query:"is_archived"`
// The id of the file this list has set as background // The id of the file this project has set as background
BackgroundFileID int64 `xorm:"null" json:"-"` BackgroundFileID int64 `xorm:"null" json:"-"`
// Holds extra information about the background set since some background providers require attribution or similar. If not null, the background can be accessed at /lists/{listID}/background // Holds extra information about the background set since some background providers require attribution or similar. If not null, the background can be accessed at /projects/{projectID}/background
BackgroundInformation interface{} `xorm:"-" json:"background_information"` BackgroundInformation interface{} `xorm:"-" json:"background_information"`
// Contains a very small version of the list background to use as a blurry preview until the actual background is loaded. Check out https://blurha.sh/ to learn how it works. // Contains a very small version of the project background to use as a blurry preview until the actual background is loaded. Check out https://blurha.sh/ to learn how it works.
BackgroundBlurHash string `xorm:"varchar(50) null" json:"background_blur_hash"` BackgroundBlurHash string `xorm:"varchar(50) null" json:"background_blur_hash"`
// True if a list is a favorite. Favorite lists show up in a separate namespace. This value depends on the user making the call to the api. // True if a project is a favorite. Favorite projects show up in a separate namespace. This value depends on the user making the call to the api.
IsFavorite bool `xorm:"-" json:"is_favorite"` IsFavorite bool `xorm:"-" json:"is_favorite"`
// The subscription status for the user reading this list. You can only read this property, use the subscription endpoints to modify it. // The subscription status for the user reading this project. You can only read this property, use the subscription endpoints to modify it.
// Will only returned when retreiving one list. // Will only returned when retreiving one project.
Subscription *Subscription `xorm:"-" json:"subscription,omitempty"` Subscription *Subscription `xorm:"-" json:"subscription,omitempty"`
// The position this list has when querying all lists. See the tasks.position property on how to use this. // The position this project has when querying all projects. See the tasks.position property on how to use this.
Position float64 `xorm:"double null" json:"position"` Position float64 `xorm:"double null" json:"position"`
// A timestamp when this list was created. You cannot change this value. // A timestamp when this project was created. You cannot change this value.
Created time.Time `xorm:"created not null" json:"created"` Created time.Time `xorm:"created not null" json:"created"`
// A timestamp when this list was last updated. You cannot change this value. // A timestamp when this project was last updated. You cannot change this value.
Updated time.Time `xorm:"updated not null" json:"updated"` Updated time.Time `xorm:"updated not null" json:"updated"`
web.CRUDable `xorm:"-" json:"-"` web.CRUDable `xorm:"-" json:"-"`
web.Rights `xorm:"-" json:"-"` web.Rights `xorm:"-" json:"-"`
} }
type ListWithTasksAndBuckets struct { type ProjectWithTasksAndBuckets struct {
List Project
// An array of tasks which belong to the list. // An array of tasks which belong to the project.
Tasks []*TaskWithComments `xorm:"-" json:"tasks"` Tasks []*TaskWithComments `xorm:"-" json:"tasks"`
// Only used for migration. // Only used for migration.
Buckets []*Bucket `xorm:"-" json:"buckets"` Buckets []*Bucket `xorm:"-" json:"buckets"`
BackgroundFileID int64 `xorm:"null" json:"background_file_id"` BackgroundFileID int64 `xorm:"null" json:"background_file_id"`
} }
// TableName returns a better name for the lists table // TableName returns a better name for the projects table
func (l *List) TableName() string { func (l *Project) TableName() string {
return "lists" return "projects"
} }
// ListBackgroundType holds a list background type // ProjectBackgroundType holds a project background type
type ListBackgroundType struct { type ProjectBackgroundType struct {
Type string Type string
} }
// ListBackgroundUpload represents the list upload background type // ProjectBackgroundUpload represents the project upload background type
const ListBackgroundUpload string = "upload" const ProjectBackgroundUpload string = "upload"
// FavoritesPseudoList holds all tasks marked as favorites // FavoritesPseudoProject holds all tasks marked as favorites
var FavoritesPseudoList = List{ var FavoritesPseudoProject = Project{
ID: -1, ID: -1,
Title: "Favorites", Title: "Favorites",
Description: "This list has all tasks marked as favorites.", Description: "This project has all tasks marked as favorites.",
NamespaceID: FavoritesPseudoNamespace.ID, NamespaceID: FavoritesPseudoNamespace.ID,
IsFavorite: true, IsFavorite: true,
Created: time.Now(), Created: time.Now(),
Updated: time.Now(), Updated: time.Now(),
} }
// GetListsByNamespaceID gets all lists in a namespace // GetProjectsByNamespaceID gets all projects in a namespace
func GetListsByNamespaceID(s *xorm.Session, nID int64, doer *user.User) (lists []*List, err error) { func GetProjectsByNamespaceID(s *xorm.Session, nID int64, doer *user.User) (projects []*Project, err error) {
switch nID { switch nID {
case SharedListsPseudoNamespace.ID: case SharedProjectsPseudoNamespace.ID:
nnn, err := getSharedListsInNamespace(s, false, doer) nnn, err := getSharedProjectsInNamespace(s, false, doer)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if nnn != nil && nnn.Lists != nil { if nnn != nil && nnn.Projects != nil {
lists = nnn.Lists projects = nnn.Projects
} }
case FavoritesPseudoNamespace.ID: case FavoritesPseudoNamespace.ID:
namespaces := make(map[int64]*NamespaceWithLists) namespaces := make(map[int64]*NamespaceWithProjects)
_, err := getNamespacesWithLists(s, &namespaces, "", false, 0, -1, doer.ID) _, err := getNamespacesWithProjects(s, &namespaces, "", false, 0, -1, doer.ID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
namespaceIDs, _ := getNamespaceOwnerIDs(namespaces) namespaceIDs, _ := getNamespaceOwnerIDs(namespaces)
ls, err := getListsForNamespaces(s, namespaceIDs, false) ls, err := getProjectsForNamespaces(s, namespaceIDs, false)
if err != nil { if err != nil {
return nil, err return nil, err
} }
nnn, err := getFavoriteLists(s, ls, namespaceIDs, doer) nnn, err := getFavoriteProjects(s, ls, namespaceIDs, doer)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if nnn != nil && nnn.Lists != nil { if nnn != nil && nnn.Projects != nil {
lists = nnn.Lists projects = nnn.Projects
} }
case SavedFiltersPseudoNamespace.ID: case SavedFiltersPseudoNamespace.ID:
nnn, err := getSavedFilters(s, doer) nnn, err := getSavedFilters(s, doer)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if nnn != nil && nnn.Lists != nil { if nnn != nil && nnn.Projects != nil {
lists = nnn.Lists projects = nnn.Projects
} }
default: default:
err = s.Select("l.*"). err = s.Select("l.*").
@ -158,48 +158,48 @@ func GetListsByNamespaceID(s *xorm.Session, nID int64, doer *user.User) (lists [
Where("l.is_archived = false"). Where("l.is_archived = false").
Where("n.is_archived = false OR n.is_archived IS NULL"). Where("n.is_archived = false OR n.is_archived IS NULL").
Where("namespace_id = ?", nID). Where("namespace_id = ?", nID).
Find(&lists) Find(&projects)
} }
if err != nil { if err != nil {
return nil, err return nil, err
} }
// get more list details // get more project details
err = addListDetails(s, lists, doer) err = addProjectDetails(s, projects, doer)
return lists, err return projects, err
} }
// ReadAll gets all lists a user has access to // ReadAll gets all projects a user has access to
// @Summary Get all lists a user has access to // @Summary Get all projects a user has access to
// @Description Returns all lists a user has access to. // @Description Returns all projects a user has access to.
// @tags list // @tags project
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param page query int false "The page number. Used for pagination. If not provided, the first page of results is returned." // @Param page query int false "The page number. Used for pagination. If not provided, the first page of results is returned."
// @Param per_page query int false "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page." // @Param per_page query int false "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page."
// @Param s query string false "Search lists by title." // @Param s query string false "Search projects by title."
// @Param is_archived query bool false "If true, also returns all archived lists." // @Param is_archived query bool false "If true, also returns all archived projects."
// @Security JWTKeyAuth // @Security JWTKeyAuth
// @Success 200 {array} models.List "The lists" // @Success 200 {array} models.Project "The projects"
// @Failure 403 {object} web.HTTPError "The user does not have access to the list" // @Failure 403 {object} web.HTTPError "The user does not have access to the project"
// @Failure 500 {object} models.Message "Internal error" // @Failure 500 {object} models.Message "Internal error"
// @Router /lists [get] // @Router /projects [get]
func (l *List) ReadAll(s *xorm.Session, a web.Auth, search string, page int, perPage int) (result interface{}, resultCount int, totalItems int64, err error) { func (l *Project) ReadAll(s *xorm.Session, a web.Auth, search string, page int, perPage int) (result interface{}, resultCount int, totalItems int64, err error) {
// Check if we're dealing with a share auth // Check if we're dealing with a share auth
shareAuth, ok := a.(*LinkSharing) shareAuth, ok := a.(*LinkSharing)
if ok { if ok {
list, err := GetListSimpleByID(s, shareAuth.ListID) project, err := GetProjectSimpleByID(s, shareAuth.ProjectID)
if err != nil { if err != nil {
return nil, 0, 0, err return nil, 0, 0, err
} }
lists := []*List{list} projects := []*Project{project}
err = addListDetails(s, lists, a) err = addProjectDetails(s, projects, a)
return lists, 0, 0, err return projects, 0, 0, err
} }
lists, resultCount, totalItems, err := getRawListsForUser( projects, resultCount, totalItems, err := getRawProjectsForUser(
s, s,
&listOptions{ &projectOptions{
search: search, search: search,
user: &user.User{ID: a.GetID()}, user: &user.User{ID: a.GetID()},
page: page, page: page,
@ -210,33 +210,33 @@ func (l *List) ReadAll(s *xorm.Session, a web.Auth, search string, page int, per
return nil, 0, 0, err return nil, 0, 0, err
} }
// Add more list details // Add more project details
err = addListDetails(s, lists, a) err = addProjectDetails(s, projects, a)
return lists, resultCount, totalItems, err return projects, resultCount, totalItems, err
} }
// ReadOne gets one list by its ID // ReadOne gets one project by its ID
// @Summary Gets one list // @Summary Gets one project
// @Description Returns a list by its ID. // @Description Returns a project by its ID.
// @tags list // @tags project
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Security JWTKeyAuth // @Security JWTKeyAuth
// @Param id path int true "List ID" // @Param id path int true "Project ID"
// @Success 200 {object} models.List "The list" // @Success 200 {object} models.Project "The project"
// @Failure 403 {object} web.HTTPError "The user does not have access to the list" // @Failure 403 {object} web.HTTPError "The user does not have access to the project"
// @Failure 500 {object} models.Message "Internal error" // @Failure 500 {object} models.Message "Internal error"
// @Router /lists/{id} [get] // @Router /projects/{id} [get]
func (l *List) ReadOne(s *xorm.Session, a web.Auth) (err error) { func (l *Project) ReadOne(s *xorm.Session, a web.Auth) (err error) {
if l.ID == FavoritesPseudoList.ID { if l.ID == FavoritesPseudoProject.ID {
// Already "built" the list in CanRead // Already "built" the project in CanRead
return nil return nil
} }
// Check for saved filters // Check for saved filters
if getSavedFilterIDFromListID(l.ID) > 0 { if getSavedFilterIDFromProjectID(l.ID) > 0 {
sf, err := getSavedFilterSimpleByID(s, getSavedFilterIDFromListID(l.ID)) sf, err := getSavedFilterSimpleByID(s, getSavedFilterIDFromProjectID(l.ID))
if err != nil { if err != nil {
return err return err
} }
@ -247,7 +247,7 @@ func (l *List) ReadOne(s *xorm.Session, a web.Auth) (err error) {
l.OwnerID = sf.OwnerID l.OwnerID = sf.OwnerID
} }
// Get list owner // Get project owner
l.Owner, err = user.GetUserByID(s, l.OwnerID) l.Owner, err = user.GetUserByID(s, l.OwnerID)
if err != nil { if err != nil {
return err return err
@ -256,7 +256,7 @@ func (l *List) ReadOne(s *xorm.Session, a web.Auth) (err error) {
if !l.IsArchived { if !l.IsArchived {
err = l.CheckIsArchived(s) err = l.CheckIsArchived(s)
if err != nil { if err != nil {
if !IsErrNamespaceIsArchived(err) && !IsErrListIsArchived(err) { if !IsErrNamespaceIsArchived(err) && !IsErrProjectIsArchived(err) {
return return
} }
l.IsArchived = true l.IsArchived = true
@ -272,78 +272,78 @@ func (l *List) ReadOne(s *xorm.Session, a web.Auth) (err error) {
} }
if err != nil && files.IsErrFileIsNotUnsplashFile(err) { if err != nil && files.IsErrFileIsNotUnsplashFile(err) {
l.BackgroundInformation = &ListBackgroundType{Type: ListBackgroundUpload} l.BackgroundInformation = &ProjectBackgroundType{Type: ProjectBackgroundUpload}
} }
} }
l.IsFavorite, err = isFavorite(s, l.ID, a, FavoriteKindList) l.IsFavorite, err = isFavorite(s, l.ID, a, FavoriteKindProject)
if err != nil { if err != nil {
return return
} }
l.Subscription, err = GetSubscription(s, SubscriptionEntityList, l.ID, a) l.Subscription, err = GetSubscription(s, SubscriptionEntityProject, l.ID, a)
return return
} }
// GetListSimpleByID gets a list with only the basic items, aka no tasks or user objects. Returns an error if the list does not exist. // GetProjectSimpleByID gets a project with only the basic items, aka no tasks or user objects. Returns an error if the project does not exist.
func GetListSimpleByID(s *xorm.Session, listID int64) (list *List, err error) { func GetProjectSimpleByID(s *xorm.Session, projectID int64) (project *Project, err error) {
list = &List{} project = &Project{}
if listID < 1 { if projectID < 1 {
return nil, ErrListDoesNotExist{ID: listID} return nil, ErrProjectDoesNotExist{ID: projectID}
} }
exists, err := s. exists, err := s.
Where("id = ?", listID). Where("id = ?", projectID).
OrderBy("position"). OrderBy("position").
Get(list) Get(project)
if err != nil { if err != nil {
return return
} }
if !exists { if !exists {
return nil, ErrListDoesNotExist{ID: listID} return nil, ErrProjectDoesNotExist{ID: projectID}
} }
return return
} }
// GetListSimplByTaskID gets a list by a task id // GetProjectSimplByTaskID gets a project by a task id
func GetListSimplByTaskID(s *xorm.Session, taskID int64) (l *List, err error) { func GetProjectSimplByTaskID(s *xorm.Session, taskID int64) (l *Project, err error) {
// We need to re-init our list object, because otherwise xorm creates a "where for every item in that list object, // We need to re-init our project object, because otherwise xorm creates a "where for every item in that project object,
// leading to not finding anything if the id is good, but for example the title is different. // leading to not finding anything if the id is good, but for example the title is different.
var list List var project Project
exists, err := s. exists, err := s.
Select("lists.*"). Select("projects.*").
Table(List{}). Table(Project{}).
Join("INNER", "tasks", "lists.id = tasks.list_id"). Join("INNER", "tasks", "projects.id = tasks.project_id").
Where("tasks.id = ?", taskID). Where("tasks.id = ?", taskID).
Get(&list) Get(&project)
if err != nil { if err != nil {
return return
} }
if !exists { if !exists {
return &List{}, ErrListDoesNotExist{ID: l.ID} return &Project{}, ErrProjectDoesNotExist{ID: l.ID}
} }
return &list, nil return &project, nil
} }
// GetListsByIDs returns a map of lists from a slice with list ids // GetProjectsByIDs returns a map of projects from a slice with project ids
func GetListsByIDs(s *xorm.Session, listIDs []int64) (lists map[int64]*List, err error) { func GetProjectsByIDs(s *xorm.Session, projectIDs []int64) (projects map[int64]*Project, err error) {
lists = make(map[int64]*List, len(listIDs)) projects = make(map[int64]*Project, len(projectIDs))
if len(listIDs) == 0 { if len(projectIDs) == 0 {
return return
} }
err = s.In("id", listIDs).Find(&lists) err = s.In("id", projectIDs).Find(&projects)
return return
} }
type listOptions struct { type projectOptions struct {
search string search string
user *user.User user *user.User
page int page int
@ -351,7 +351,7 @@ type listOptions struct {
isArchived bool isArchived bool
} }
func getUserListsStatement(userID int64) *builder.Builder { func getUserProjectsStatement(userID int64) *builder.Builder {
dialect := config.DatabaseType.GetString() dialect := config.DatabaseType.GetString()
if dialect == "sqlite" { if dialect == "sqlite" {
dialect = builder.SQLITE dialect = builder.SQLITE
@ -359,13 +359,13 @@ func getUserListsStatement(userID int64) *builder.Builder {
return builder.Dialect(dialect). return builder.Dialect(dialect).
Select("l.*"). Select("l.*").
From("lists", "l"). From("projects", "l").
Join("INNER", "namespaces n", "l.namespace_id = n.id"). Join("INNER", "namespaces n", "l.namespace_id = n.id").
Join("LEFT", "team_namespaces tn", "tn.namespace_id = n.id"). Join("LEFT", "team_namespaces tn", "tn.namespace_id = n.id").
Join("LEFT", "team_members tm", "tm.team_id = tn.team_id"). Join("LEFT", "team_members tm", "tm.team_id = tn.team_id").
Join("LEFT", "team_lists tl", "l.id = tl.list_id"). Join("LEFT", "team_projects tl", "l.id = tl.project_id").
Join("LEFT", "team_members tm2", "tm2.team_id = tl.team_id"). Join("LEFT", "team_members tm2", "tm2.team_id = tl.team_id").
Join("LEFT", "users_lists ul", "ul.list_id = l.id"). Join("LEFT", "users_projects ul", "ul.project_id = l.id").
Join("LEFT", "users_namespaces un", "un.namespace_id = l.namespace_id"). Join("LEFT", "users_namespaces un", "un.namespace_id = l.namespace_id").
Where(builder.Or( Where(builder.Or(
builder.Eq{"tm.user_id": userID}, builder.Eq{"tm.user_id": userID},
@ -378,8 +378,8 @@ func getUserListsStatement(userID int64) *builder.Builder {
GroupBy("l.id") GroupBy("l.id")
} }
// Gets the lists only, without any tasks or so // Gets the projects only, without any tasks or so
func getRawListsForUser(s *xorm.Session, opts *listOptions) (lists []*List, resultCount int, totalItems int64, err error) { func getRawProjectsForUser(s *xorm.Session, opts *projectOptions) (projects []*Project, resultCount int, totalItems int64, err error) {
fullUser, err := user.GetUserByID(s, opts.user.ID) fullUser, err := user.GetUserByID(s, opts.user.ID)
if err != nil { if err != nil {
return nil, 0, 0, err return nil, 0, 0, err
@ -403,7 +403,7 @@ func getRawListsForUser(s *xorm.Session, opts *listOptions) (lists []*List, resu
for _, val := range vals { for _, val := range vals {
v, err := strconv.ParseInt(val, 10, 64) v, err := strconv.ParseInt(val, 10, 64)
if err != nil { if err != nil {
log.Debugf("List search string part '%s' is not a number: %s", val, err) log.Debugf("Project search string part '%s' is not a number: %s", val, err)
continue continue
} }
ids = append(ids, v) ids = append(ids, v)
@ -415,41 +415,41 @@ func getRawListsForUser(s *xorm.Session, opts *listOptions) (lists []*List, resu
filterCond = builder.In("l.id", ids) filterCond = builder.In("l.id", ids)
} }
// Gets all Lists where the user is either owner or in a team which has access to the list // Gets all Projects where the user is either owner or in a team which has access to the project
// Or in a team which has namespace read access // Or in a team which has namespace read access
query := getUserListsStatement(fullUser.ID). query := getUserProjectsStatement(fullUser.ID).
Where(filterCond). Where(filterCond).
Where(isArchivedCond) Where(isArchivedCond)
if limit > 0 { if limit > 0 {
query = query.Limit(limit, start) query = query.Limit(limit, start)
} }
err = s.SQL(query).Find(&lists) err = s.SQL(query).Find(&projects)
if err != nil { if err != nil {
return nil, 0, 0, err return nil, 0, 0, err
} }
query = getUserListsStatement(fullUser.ID). query = getUserProjectsStatement(fullUser.ID).
Where(filterCond). Where(filterCond).
Where(isArchivedCond) Where(isArchivedCond)
totalItems, err = s. totalItems, err = s.
SQL(query.Select("count(*)")). SQL(query.Select("count(*)")).
Count(&List{}) Count(&Project{})
return lists, len(lists), totalItems, err return projects, len(projects), totalItems, err
} }
// addListDetails adds owner user objects and list tasks to all lists in the slice // addProjectDetails adds owner user objects and project tasks to all projects in the slice
func addListDetails(s *xorm.Session, lists []*List, a web.Auth) (err error) { func addProjectDetails(s *xorm.Session, projects []*Project, a web.Auth) (err error) {
if len(lists) == 0 { if len(projects) == 0 {
return return
} }
var ownerIDs []int64 var ownerIDs []int64
for _, l := range lists { for _, l := range projects {
ownerIDs = append(ownerIDs, l.OwnerID) ownerIDs = append(ownerIDs, l.OwnerID)
} }
// Get all list owners // Get all project owners
owners := map[int64]*user.User{} owners := map[int64]*user.User{}
if len(ownerIDs) > 0 { if len(ownerIDs) > 0 {
err = s.In("id", ownerIDs).Find(&owners) err = s.In("id", ownerIDs).Find(&owners)
@ -459,38 +459,38 @@ func addListDetails(s *xorm.Session, lists []*List, a web.Auth) (err error) {
} }
var fileIDs []int64 var fileIDs []int64
var listIDs []int64 var projectIDs []int64
for _, l := range lists { for _, l := range projects {
listIDs = append(listIDs, l.ID) projectIDs = append(projectIDs, l.ID)
if o, exists := owners[l.OwnerID]; exists { if o, exists := owners[l.OwnerID]; exists {
l.Owner = o l.Owner = o
} }
if l.BackgroundFileID != 0 { if l.BackgroundFileID != 0 {
l.BackgroundInformation = &ListBackgroundType{Type: ListBackgroundUpload} l.BackgroundInformation = &ProjectBackgroundType{Type: ProjectBackgroundUpload}
} }
fileIDs = append(fileIDs, l.BackgroundFileID) fileIDs = append(fileIDs, l.BackgroundFileID)
} }
favs, err := getFavorites(s, listIDs, a, FavoriteKindList) favs, err := getFavorites(s, projectIDs, a, FavoriteKindProject)
if err != nil { if err != nil {
return err return err
} }
subscriptions, err := GetSubscriptions(s, SubscriptionEntityList, listIDs, a) subscriptions, err := GetSubscriptions(s, SubscriptionEntityProject, projectIDs, a)
if err != nil { if err != nil {
log.Errorf("An error occurred while getting list subscriptions for a namespace item: %s", err.Error()) log.Errorf("An error occurred while getting project subscriptions for a namespace item: %s", err.Error())
subscriptions = make(map[int64]*Subscription) subscriptions = make(map[int64]*Subscription)
} }
for _, list := range lists { for _, project := range projects {
// Don't override the favorite state if it was already set from before (favorite saved filters do this) // Don't override the favorite state if it was already set from before (favorite saved filters do this)
if list.IsFavorite { if project.IsFavorite {
continue continue
} }
list.IsFavorite = favs[list.ID] project.IsFavorite = favs[project.ID]
if subscription, exists := subscriptions[list.ID]; exists { if subscription, exists := subscriptions[project.ID]; exists {
list.Subscription = subscription project.Subscription = subscription
} }
} }
@ -509,8 +509,8 @@ func addListDetails(s *xorm.Session, lists []*List, a web.Auth) (err error) {
unsplashPhotos[u.FileID] = u unsplashPhotos[u.FileID] = u
} }
// Build it all into the lists slice // Build it all into the projects slice
for _, l := range lists { for _, l := range projects {
// Only override the file info if we have info for unsplash backgrounds // Only override the file info if we have info for unsplash backgrounds
if _, exists := unsplashPhotos[l.BackgroundFileID]; exists { if _, exists := unsplashPhotos[l.BackgroundFileID]; exists {
l.BackgroundInformation = unsplashPhotos[l.BackgroundFileID] l.BackgroundInformation = unsplashPhotos[l.BackgroundFileID]
@ -520,31 +520,31 @@ func addListDetails(s *xorm.Session, lists []*List, a web.Auth) (err error) {
return return
} }
// NamespaceList is a meta type to be able to join a list with its namespace // NamespaceProject is a meta type to be able to join a project with its namespace
type NamespaceList struct { type NamespaceProject struct {
List List `xorm:"extends"` Project Project `xorm:"extends"`
Namespace Namespace `xorm:"extends"` Namespace Namespace `xorm:"extends"`
} }
// CheckIsArchived returns an ErrListIsArchived or ErrNamespaceIsArchived if the list or its namespace is archived. // CheckIsArchived returns an ErrProjectIsArchived or ErrNamespaceIsArchived if the project or its namespace is archived.
func (l *List) CheckIsArchived(s *xorm.Session) (err error) { func (l *Project) CheckIsArchived(s *xorm.Session) (err error) {
// When creating a new list, we check if the namespace is archived // When creating a new project, we check if the namespace is archived
if l.ID == 0 { if l.ID == 0 {
n := &Namespace{ID: l.NamespaceID} n := &Namespace{ID: l.NamespaceID}
return n.CheckIsArchived(s) return n.CheckIsArchived(s)
} }
nl := &NamespaceList{} nl := &NamespaceProject{}
exists, err := s. exists, err := s.
Table("lists"). Table("projects").
Join("LEFT", "namespaces", "lists.namespace_id = namespaces.id"). Join("LEFT", "namespaces", "projects.namespace_id = namespaces.id").
Where("lists.id = ? AND (lists.is_archived = true OR namespaces.is_archived = true)", l.ID). Where("projects.id = ? AND (projects.is_archived = true OR namespaces.is_archived = true)", l.ID).
Get(nl) Get(nl)
if err != nil { if err != nil {
return return
} }
if exists && nl.List.ID != 0 && nl.List.IsArchived { if exists && nl.Project.ID != 0 && nl.Project.IsArchived {
return ErrListIsArchived{ListID: l.ID} return ErrProjectIsArchived{ProjectID: l.ID}
} }
if exists && nl.Namespace.ID != 0 && nl.Namespace.IsArchived { if exists && nl.Namespace.ID != 0 && nl.Namespace.IsArchived {
return ErrNamespaceIsArchived{NamespaceID: nl.Namespace.ID} return ErrNamespaceIsArchived{NamespaceID: nl.Namespace.ID}
@ -552,38 +552,38 @@ func (l *List) CheckIsArchived(s *xorm.Session) (err error) {
return nil return nil
} }
func checkListBeforeUpdateOrDelete(s *xorm.Session, list *List) error { func checkProjectBeforeUpdateOrDelete(s *xorm.Session, project *Project) error {
if list.NamespaceID < 0 { if project.NamespaceID < 0 {
return &ErrListCannotBelongToAPseudoNamespace{ListID: list.ID, NamespaceID: list.NamespaceID} return &ErrProjectCannotBelongToAPseudoNamespace{ProjectID: project.ID, NamespaceID: project.NamespaceID}
} }
// Check if the namespace exists // Check if the namespace exists
if list.NamespaceID > 0 { if project.NamespaceID > 0 {
_, err := GetNamespaceByID(s, list.NamespaceID) _, err := GetNamespaceByID(s, project.NamespaceID)
if err != nil { if err != nil {
return err return err
} }
} }
// Check if the identifier is unique and not empty // Check if the identifier is unique and not empty
if list.Identifier != "" { if project.Identifier != "" {
exists, err := s. exists, err := s.
Where("identifier = ?", list.Identifier). Where("identifier = ?", project.Identifier).
And("id != ?", list.ID). And("id != ?", project.ID).
Exist(&List{}) Exist(&Project{})
if err != nil { if err != nil {
return err return err
} }
if exists { if exists {
return ErrListIdentifierIsNotUnique{Identifier: list.Identifier} return ErrProjectIdentifierIsNotUnique{Identifier: project.Identifier}
} }
} }
return nil return nil
} }
func CreateList(s *xorm.Session, list *List, auth web.Auth) (err error) { func CreateProject(s *xorm.Session, project *Project, auth web.Auth) (err error) {
err = list.CheckIsArchived(s) err = project.CheckIsArchived(s)
if err != nil { if err != nil {
return err return err
} }
@ -593,61 +593,61 @@ func CreateList(s *xorm.Session, list *List, auth web.Auth) (err error) {
return err return err
} }
list.OwnerID = doer.ID project.OwnerID = doer.ID
list.Owner = doer project.Owner = doer
list.ID = 0 // Otherwise only the first time a new list would be created project.ID = 0 // Otherwise only the first time a new project would be created
err = checkListBeforeUpdateOrDelete(s, list) err = checkProjectBeforeUpdateOrDelete(s, project)
if err != nil { if err != nil {
return return
} }
_, err = s.Insert(list) _, err = s.Insert(project)
if err != nil { if err != nil {
return return
} }
list.Position = calculateDefaultPosition(list.ID, list.Position) project.Position = calculateDefaultPosition(project.ID, project.Position)
_, err = s.Where("id = ?", list.ID).Update(list) _, err = s.Where("id = ?", project.ID).Update(project)
if err != nil { if err != nil {
return return
} }
if list.IsFavorite { if project.IsFavorite {
if err := addToFavorites(s, list.ID, auth, FavoriteKindList); err != nil { if err := addToFavorites(s, project.ID, auth, FavoriteKindProject); err != nil {
return err return err
} }
} }
// Create a new first bucket for this list // Create a new first bucket for this project
b := &Bucket{ b := &Bucket{
ListID: list.ID, ProjectID: project.ID,
Title: "Backlog", Title: "Backlog",
} }
err = b.Create(s, auth) err = b.Create(s, auth)
if err != nil { if err != nil {
return return
} }
return events.Dispatch(&ListCreatedEvent{ return events.Dispatch(&ProjectCreatedEvent{
List: list, Project: project,
Doer: doer, Doer: doer,
}) })
} }
func UpdateList(s *xorm.Session, list *List, auth web.Auth, updateListBackground bool) (err error) { func UpdateProject(s *xorm.Session, project *Project, auth web.Auth, updateProjectBackground bool) (err error) {
err = checkListBeforeUpdateOrDelete(s, list) err = checkProjectBeforeUpdateOrDelete(s, project)
if err != nil { if err != nil {
return return
} }
if list.NamespaceID == 0 { if project.NamespaceID == 0 {
return &ErrListMustBelongToANamespace{ return &ErrProjectMustBelongToANamespace{
ListID: list.ID, ProjectID: project.ID,
NamespaceID: list.NamespaceID, NamespaceID: project.NamespaceID,
} }
} }
// We need to specify the cols we want to update here to be able to un-archive lists // We need to specify the cols we want to update here to be able to un-archive projects
colsToUpdate := []string{ colsToUpdate := []string{
"title", "title",
"is_archived", "is_archived",
@ -656,72 +656,72 @@ func UpdateList(s *xorm.Session, list *List, auth web.Auth, updateListBackground
"namespace_id", "namespace_id",
"position", "position",
} }
if list.Description != "" { if project.Description != "" {
colsToUpdate = append(colsToUpdate, "description") colsToUpdate = append(colsToUpdate, "description")
} }
if updateListBackground { if updateProjectBackground {
colsToUpdate = append(colsToUpdate, "background_file_id", "background_blur_hash") colsToUpdate = append(colsToUpdate, "background_file_id", "background_blur_hash")
} }
wasFavorite, err := isFavorite(s, list.ID, auth, FavoriteKindList) wasFavorite, err := isFavorite(s, project.ID, auth, FavoriteKindProject)
if err != nil { if err != nil {
return err return err
} }
if list.IsFavorite && !wasFavorite { if project.IsFavorite && !wasFavorite {
if err := addToFavorites(s, list.ID, auth, FavoriteKindList); err != nil { if err := addToFavorites(s, project.ID, auth, FavoriteKindProject); err != nil {
return err return err
} }
} }
if !list.IsFavorite && wasFavorite { if !project.IsFavorite && wasFavorite {
if err := removeFromFavorite(s, list.ID, auth, FavoriteKindList); err != nil { if err := removeFromFavorite(s, project.ID, auth, FavoriteKindProject); err != nil {
return err return err
} }
} }
_, err = s. _, err = s.
ID(list.ID). ID(project.ID).
Cols(colsToUpdate...). Cols(colsToUpdate...).
Update(list) Update(project)
if err != nil { if err != nil {
return err return err
} }
err = events.Dispatch(&ListUpdatedEvent{ err = events.Dispatch(&ProjectUpdatedEvent{
List: list, Project: project,
Doer: auth, Doer: auth,
}) })
if err != nil { if err != nil {
return err return err
} }
l, err := GetListSimpleByID(s, list.ID) l, err := GetProjectSimpleByID(s, project.ID)
if err != nil { if err != nil {
return err return err
} }
*list = *l *project = *l
err = list.ReadOne(s, auth) err = project.ReadOne(s, auth)
return return
} }
// Update implements the update method of CRUDable // Update implements the update method of CRUDable
// @Summary Updates a list // @Summary Updates a project
// @Description Updates a list. This does not include adding a task (see below). // @Description Updates a project. This does not include adding a task (see below).
// @tags list // @tags project
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Security JWTKeyAuth // @Security JWTKeyAuth
// @Param id path int true "List ID" // @Param id path int true "Project ID"
// @Param list body models.List true "The list with updated values you want to update." // @Param project body models.Project true "The project with updated values you want to update."
// @Success 200 {object} models.List "The updated list." // @Success 200 {object} models.Project "The updated project."
// @Failure 400 {object} web.HTTPError "Invalid list object provided." // @Failure 400 {object} web.HTTPError "Invalid project object provided."
// @Failure 403 {object} web.HTTPError "The user does not have access to the list" // @Failure 403 {object} web.HTTPError "The user does not have access to the project"
// @Failure 500 {object} models.Message "Internal error" // @Failure 500 {object} models.Message "Internal error"
// @Router /lists/{id} [post] // @Router /projects/{id} [post]
func (l *List) Update(s *xorm.Session, a web.Auth) (err error) { func (l *Project) Update(s *xorm.Session, a web.Auth) (err error) {
fid := getSavedFilterIDFromListID(l.ID) fid := getSavedFilterIDFromProjectID(l.ID)
if fid > 0 { if fid > 0 {
f, err := getSavedFilterSimpleByID(s, fid) f, err := getSavedFilterSimpleByID(s, fid)
if err != nil { if err != nil {
@ -736,44 +736,44 @@ func (l *List) Update(s *xorm.Session, a web.Auth) (err error) {
return err return err
} }
*l = *f.toList() *l = *f.toProject()
return nil return nil
} }
return UpdateList(s, l, a, false) return UpdateProject(s, l, a, false)
} }
func updateListLastUpdated(s *xorm.Session, list *List) error { func updateProjectLastUpdated(s *xorm.Session, project *Project) error {
_, err := s.ID(list.ID).Cols("updated").Update(list) _, err := s.ID(project.ID).Cols("updated").Update(project)
return err return err
} }
func updateListByTaskID(s *xorm.Session, taskID int64) (err error) { func updateProjectByTaskID(s *xorm.Session, taskID int64) (err error) {
// need to get the task to update the list last updated timestamp // need to get the task to update the project last updated timestamp
task, err := GetTaskByIDSimple(s, taskID) task, err := GetTaskByIDSimple(s, taskID)
if err != nil { if err != nil {
return err return err
} }
return updateListLastUpdated(s, &List{ID: task.ListID}) return updateProjectLastUpdated(s, &Project{ID: task.ProjectID})
} }
// Create implements the create method of CRUDable // Create implements the create method of CRUDable
// @Summary Creates a new list // @Summary Creates a new project
// @Description Creates a new list in a given namespace. The user needs write-access to the namespace. // @Description Creates a new project in a given namespace. The user needs write-access to the namespace.
// @tags list // @tags project
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Security JWTKeyAuth // @Security JWTKeyAuth
// @Param namespaceID path int true "Namespace ID" // @Param namespaceID path int true "Namespace ID"
// @Param list body models.List true "The list you want to create." // @Param project body models.Project true "The project you want to create."
// @Success 201 {object} models.List "The created list." // @Success 201 {object} models.Project "The created project."
// @Failure 400 {object} web.HTTPError "Invalid list object provided." // @Failure 400 {object} web.HTTPError "Invalid project object provided."
// @Failure 403 {object} web.HTTPError "The user does not have access to the list" // @Failure 403 {object} web.HTTPError "The user does not have access to the project"
// @Failure 500 {object} models.Message "Internal error" // @Failure 500 {object} models.Message "Internal error"
// @Router /namespaces/{namespaceID}/lists [put] // @Router /namespaces/{namespaceID}/projects [put]
func (l *List) Create(s *xorm.Session, a web.Auth) (err error) { func (l *Project) Create(s *xorm.Session, a web.Auth) (err error) {
err = CreateList(s, l, a) err = CreateProject(s, l, a)
if err != nil { if err != nil {
return return
} }
@ -782,33 +782,33 @@ func (l *List) Create(s *xorm.Session, a web.Auth) (err error) {
} }
// Delete implements the delete method of CRUDable // Delete implements the delete method of CRUDable
// @Summary Deletes a list // @Summary Deletes a project
// @Description Delets a list // @Description Delets a project
// @tags list // @tags project
// @Produce json // @Produce json
// @Security JWTKeyAuth // @Security JWTKeyAuth
// @Param id path int true "List ID" // @Param id path int true "Project ID"
// @Success 200 {object} models.Message "The list was successfully deleted." // @Success 200 {object} models.Message "The project was successfully deleted."
// @Failure 400 {object} web.HTTPError "Invalid list object provided." // @Failure 400 {object} web.HTTPError "Invalid project object provided."
// @Failure 403 {object} web.HTTPError "The user does not have access to the list" // @Failure 403 {object} web.HTTPError "The user does not have access to the project"
// @Failure 500 {object} models.Message "Internal error" // @Failure 500 {object} models.Message "Internal error"
// @Router /lists/{id} [delete] // @Router /projects/{id} [delete]
func (l *List) Delete(s *xorm.Session, a web.Auth) (err error) { func (l *Project) Delete(s *xorm.Session, a web.Auth) (err error) {
fullList, err := GetListSimpleByID(s, l.ID) fullList, err := GetProjectSimpleByID(s, l.ID)
if err != nil { if err != nil {
return return
} }
// Delete the list // Delete the project
_, err = s.ID(l.ID).Delete(&List{}) _, err = s.ID(l.ID).Delete(&Project{})
if err != nil { if err != nil {
return return
} }
// Delete all tasks on that list // Delete all tasks on that project
// Using the loop to make sure all related entities to all tasks are properly deleted as well. // Using the loop to make sure all related entities to all tasks are properly deleted as well.
tasks, _, _, err := getRawTasksForLists(s, []*List{l}, a, &taskOptions{}) tasks, _, _, err := getRawTasksForProjects(s, []*Project{l}, a, &taskOptions{})
if err != nil { if err != nil {
return return
} }
@ -825,15 +825,15 @@ func (l *List) Delete(s *xorm.Session, a web.Auth) (err error) {
return return
} }
return events.Dispatch(&ListDeletedEvent{ return events.Dispatch(&ProjectDeletedEvent{
List: l, Project: l,
Doer: a, Doer: a,
}) })
} }
// DeleteBackgroundFileIfExists deletes the list's background file from the db and the filesystem, // DeleteBackgroundFileIfExists deletes the list's background file from the db and the filesystem,
// if one exists // if one exists
func (l *List) DeleteBackgroundFileIfExists() (err error) { func (l *Project) DeleteBackgroundFileIfExists() (err error) {
if l.BackgroundFileID == 0 { if l.BackgroundFileID == 0 {
return return
} }
@ -842,10 +842,10 @@ func (l *List) DeleteBackgroundFileIfExists() (err error) {
return file.Delete() return file.Delete()
} }
// SetListBackground sets a background file as list background in the db // SetProjectBackground sets a background file as project background in the db
func SetListBackground(s *xorm.Session, listID int64, background *files.File, blurHash string) (err error) { func SetProjectBackground(s *xorm.Session, projectID int64, background *files.File, blurHash string) (err error) {
l := &List{ l := &Project{
ID: listID, ID: projectID,
BackgroundFileID: background.ID, BackgroundFileID: background.ID,
BackgroundBlurHash: blurHash, BackgroundBlurHash: blurHash,
} }

View File

@ -24,89 +24,89 @@ import (
"xorm.io/xorm" "xorm.io/xorm"
) )
// ListDuplicate holds everything needed to duplicate a list // ProjectDuplicate holds everything needed to duplicate a project
type ListDuplicate struct { type ProjectDuplicate struct {
// The list id of the list to duplicate // The project id of the project to duplicate
ListID int64 `json:"-" param:"listid"` ProjectID int64 `json:"-" param:"projectid"`
// The target namespace ID // The target namespace ID
NamespaceID int64 `json:"namespace_id,omitempty"` NamespaceID int64 `json:"namespace_id,omitempty"`
// The copied list // The copied project
List *List `json:",omitempty"` Project *Project `json:",omitempty"`
web.Rights `json:"-"` web.Rights `json:"-"`
web.CRUDable `json:"-"` web.CRUDable `json:"-"`
} }
// CanCreate checks if a user has the right to duplicate a list // CanCreate checks if a user has the right to duplicate a project
func (ld *ListDuplicate) CanCreate(s *xorm.Session, a web.Auth) (canCreate bool, err error) { func (ld *ProjectDuplicate) CanCreate(s *xorm.Session, a web.Auth) (canCreate bool, err error) {
// List Exists + user has read access to list // Project Exists + user has read access to project
ld.List = &List{ID: ld.ListID} ld.Project = &Project{ID: ld.ProjectID}
canRead, _, err := ld.List.CanRead(s, a) canRead, _, err := ld.Project.CanRead(s, a)
if err != nil || !canRead { if err != nil || !canRead {
return canRead, err return canRead, err
} }
// Namespace exists + user has write access to is (-> can create new lists) // Namespace exists + user has write access to is (-> can create new projects)
ld.List.NamespaceID = ld.NamespaceID ld.Project.NamespaceID = ld.NamespaceID
return ld.List.CanCreate(s, a) return ld.Project.CanCreate(s, a)
} }
// Create duplicates a list // Create duplicates a project
// @Summary Duplicate an existing list // @Summary Duplicate an existing project
// @Description Copies the list, tasks, files, kanban data, assignees, comments, attachments, lables, relations, backgrounds, user/team rights and link shares from one list to a new namespace. The user needs read access in the list and write access in the namespace of the new list. // @Description Copies the project, tasks, files, kanban data, assignees, comments, attachments, lables, relations, backgrounds, user/team rights and link shares from one project to a new namespace. The user needs read access in the project and write access in the namespace of the new project.
// @tags list // @tags project
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Security JWTKeyAuth // @Security JWTKeyAuth
// @Param listID path int true "The list ID to duplicate" // @Param projectID path int true "The project ID to duplicate"
// @Param list body models.ListDuplicate true "The target namespace which should hold the copied list." // @Param project body models.ProjectDuplicate true "The target namespace which should hold the copied project."
// @Success 201 {object} models.ListDuplicate "The created list." // @Success 201 {object} models.ProjectDuplicate "The created project."
// @Failure 400 {object} web.HTTPError "Invalid list duplicate object provided." // @Failure 400 {object} web.HTTPError "Invalid project duplicate object provided."
// @Failure 403 {object} web.HTTPError "The user does not have access to the list or namespace" // @Failure 403 {object} web.HTTPError "The user does not have access to the project or namespace"
// @Failure 500 {object} models.Message "Internal error" // @Failure 500 {object} models.Message "Internal error"
// @Router /lists/{listID}/duplicate [put] // @Router /projects/{projectID}/duplicate [put]
// //
//nolint:gocyclo //nolint:gocyclo
func (ld *ListDuplicate) Create(s *xorm.Session, doer web.Auth) (err error) { func (ld *ProjectDuplicate) Create(s *xorm.Session, doer web.Auth) (err error) {
log.Debugf("Duplicating list %d", ld.ListID) log.Debugf("Duplicating project %d", ld.ProjectID)
ld.List.ID = 0 ld.Project.ID = 0
ld.List.Identifier = "" // Reset the identifier to trigger regenerating a new one ld.Project.Identifier = "" // Reset the identifier to trigger regenerating a new one
// Set the owner to the current user // Set the owner to the current user
ld.List.OwnerID = doer.GetID() ld.Project.OwnerID = doer.GetID()
if err := CreateList(s, ld.List, doer); err != nil { if err := CreateProject(s, ld.Project, doer); err != nil {
// If there is no available unique list identifier, just reset it. // If there is no available unique project identifier, just reset it.
if IsErrListIdentifierIsNotUnique(err) { if IsErrProjectIdentifierIsNotUnique(err) {
ld.List.Identifier = "" ld.Project.Identifier = ""
} else { } else {
return err return err
} }
} }
log.Debugf("Duplicated list %d into new list %d", ld.ListID, ld.List.ID) log.Debugf("Duplicated project %d into new project %d", ld.ProjectID, ld.Project.ID)
// Duplicate kanban buckets // Duplicate kanban buckets
// Old bucket ID as key, new id as value // Old bucket ID as key, new id as value
// Used to map the newly created tasks to their new buckets // Used to map the newly created tasks to their new buckets
bucketMap := make(map[int64]int64) bucketMap := make(map[int64]int64)
buckets := []*Bucket{} buckets := []*Bucket{}
err = s.Where("list_id = ?", ld.ListID).Find(&buckets) err = s.Where("project_id = ?", ld.ProjectID).Find(&buckets)
if err != nil { if err != nil {
return return
} }
for _, b := range buckets { for _, b := range buckets {
oldID := b.ID oldID := b.ID
b.ID = 0 b.ID = 0
b.ListID = ld.List.ID b.ProjectID = ld.Project.ID
if err := b.Create(s, doer); err != nil { if err := b.Create(s, doer); err != nil {
return err return err
} }
bucketMap[oldID] = b.ID bucketMap[oldID] = b.ID
} }
log.Debugf("Duplicated all buckets from list %d into %d", ld.ListID, ld.List.ID) log.Debugf("Duplicated all buckets from project %d into %d", ld.ProjectID, ld.Project.ID)
err = duplicateTasks(s, doer, ld, bucketMap) err = duplicateTasks(s, doer, ld, bucketMap)
if err != nil { if err != nil {
@ -114,11 +114,11 @@ func (ld *ListDuplicate) Create(s *xorm.Session, doer web.Auth) (err error) {
} }
// Background files + unsplash info // Background files + unsplash info
if ld.List.BackgroundFileID != 0 { if ld.Project.BackgroundFileID != 0 {
log.Debugf("Duplicating background %d from list %d into %d", ld.List.BackgroundFileID, ld.ListID, ld.List.ID) log.Debugf("Duplicating background %d from project %d into %d", ld.Project.BackgroundFileID, ld.ProjectID, ld.Project.ID)
f := &files.File{ID: ld.List.BackgroundFileID} f := &files.File{ID: ld.Project.BackgroundFileID}
if err := f.LoadFileMetaByID(); err != nil { if err := f.LoadFileMetaByID(); err != nil {
return err return err
} }
@ -133,7 +133,7 @@ func (ld *ListDuplicate) Create(s *xorm.Session, doer web.Auth) (err error) {
} }
// Get unsplash info if applicable // Get unsplash info if applicable
up, err := GetUnsplashPhotoByFileID(s, ld.List.BackgroundFileID) up, err := GetUnsplashPhotoByFileID(s, ld.Project.BackgroundFileID)
if err != nil && files.IsErrFileIsNotUnsplashFile(err) { if err != nil && files.IsErrFileIsNotUnsplashFile(err) {
return err return err
} }
@ -145,38 +145,38 @@ func (ld *ListDuplicate) Create(s *xorm.Session, doer web.Auth) (err error) {
} }
} }
if err := SetListBackground(s, ld.List.ID, file, ld.List.BackgroundBlurHash); err != nil { if err := SetProjectBackground(s, ld.Project.ID, file, ld.Project.BackgroundBlurHash); err != nil {
return err return err
} }
log.Debugf("Duplicated list background from list %d into %d", ld.ListID, ld.List.ID) log.Debugf("Duplicated project background from project %d into %d", ld.ProjectID, ld.Project.ID)
} }
// Rights / Shares // Rights / Shares
// To keep it simple(r) we will only copy rights which are directly used with the list, no namespace changes. // To keep it simple(r) we will only copy rights which are directly used with the project, no namespace changes.
users := []*ListUser{} users := []*ProjectUser{}
err = s.Where("list_id = ?", ld.ListID).Find(&users) err = s.Where("project_id = ?", ld.ProjectID).Find(&users)
if err != nil { if err != nil {
return return
} }
for _, u := range users { for _, u := range users {
u.ID = 0 u.ID = 0
u.ListID = ld.List.ID u.ProjectID = ld.Project.ID
if _, err := s.Insert(u); err != nil { if _, err := s.Insert(u); err != nil {
return err return err
} }
} }
log.Debugf("Duplicated user shares from list %d into %d", ld.ListID, ld.List.ID) log.Debugf("Duplicated user shares from project %d into %d", ld.ProjectID, ld.Project.ID)
teams := []*TeamList{} teams := []*TeamProject{}
err = s.Where("list_id = ?", ld.ListID).Find(&teams) err = s.Where("project_id = ?", ld.ProjectID).Find(&teams)
if err != nil { if err != nil {
return return
} }
for _, t := range teams { for _, t := range teams {
t.ID = 0 t.ID = 0
t.ListID = ld.List.ID t.ProjectID = ld.Project.ID
if _, err := s.Insert(t); err != nil { if _, err := s.Insert(t); err != nil {
return err return err
} }
@ -184,27 +184,27 @@ func (ld *ListDuplicate) Create(s *xorm.Session, doer web.Auth) (err error) {
// Generate new link shares if any are available // Generate new link shares if any are available
linkShares := []*LinkSharing{} linkShares := []*LinkSharing{}
err = s.Where("list_id = ?", ld.ListID).Find(&linkShares) err = s.Where("project_id = ?", ld.ProjectID).Find(&linkShares)
if err != nil { if err != nil {
return return
} }
for _, share := range linkShares { for _, share := range linkShares {
share.ID = 0 share.ID = 0
share.ListID = ld.List.ID share.ProjectID = ld.Project.ID
share.Hash = utils.MakeRandomString(40) share.Hash = utils.MakeRandomString(40)
if _, err := s.Insert(share); err != nil { if _, err := s.Insert(share); err != nil {
return err return err
} }
} }
log.Debugf("Duplicated all link shares from list %d into %d", ld.ListID, ld.List.ID) log.Debugf("Duplicated all link shares from project %d into %d", ld.ProjectID, ld.Project.ID)
return return
} }
func duplicateTasks(s *xorm.Session, doer web.Auth, ld *ListDuplicate, bucketMap map[int64]int64) (err error) { func duplicateTasks(s *xorm.Session, doer web.Auth, ld *ProjectDuplicate, bucketMap map[int64]int64) (err error) {
// Get all tasks + all task details // Get all tasks + all task details
tasks, _, _, err := getTasksForLists(s, []*List{{ID: ld.ListID}}, doer, &taskOptions{}) tasks, _, _, err := getTasksForProjects(s, []*Project{{ID: ld.ProjectID}}, doer, &taskOptions{})
if err != nil { if err != nil {
return err return err
} }
@ -221,7 +221,7 @@ func duplicateTasks(s *xorm.Session, doer web.Auth, ld *ListDuplicate, bucketMap
for _, t := range tasks { for _, t := range tasks {
oldID := t.ID oldID := t.ID
t.ID = 0 t.ID = 0
t.ListID = ld.List.ID t.ProjectID = ld.Project.ID
t.BucketID = bucketMap[t.BucketID] t.BucketID = bucketMap[t.BucketID]
t.UID = "" t.UID = ""
err := createTask(s, t, doer, false) err := createTask(s, t, doer, false)
@ -232,11 +232,11 @@ func duplicateTasks(s *xorm.Session, doer web.Auth, ld *ListDuplicate, bucketMap
oldTaskIDs = append(oldTaskIDs, oldID) oldTaskIDs = append(oldTaskIDs, oldID)
} }
log.Debugf("Duplicated all tasks from list %d into %d", ld.ListID, ld.List.ID) log.Debugf("Duplicated all tasks from project %d into %d", ld.ProjectID, ld.Project.ID)
// Save all attachments // Save all attachments
// We also duplicate all underlying files since they could be modified in one list which would result in // We also duplicate all underlying files since they could be modified in one project which would result in
// file changes in the other list which is not something we want. // file changes in the other project which is not something we want.
attachments, err := getTaskAttachmentsByTaskIDs(s, oldTaskIDs) attachments, err := getTaskAttachmentsByTaskIDs(s, oldTaskIDs)
if err != nil { if err != nil {
return err return err
@ -254,7 +254,7 @@ func duplicateTasks(s *xorm.Session, doer web.Auth, ld *ListDuplicate, bucketMap
attachment.File = &files.File{ID: attachment.FileID} attachment.File = &files.File{ID: attachment.FileID}
if err := attachment.File.LoadFileMetaByID(); err != nil { if err := attachment.File.LoadFileMetaByID(); err != nil {
if files.IsErrFileDoesNotExist(err) { if files.IsErrFileDoesNotExist(err) {
log.Debugf("Not duplicating attachment %d (file %d) because it does not exist from list %d into %d", oldAttachmentID, attachment.FileID, ld.ListID, ld.List.ID) log.Debugf("Not duplicating attachment %d (file %d) because it does not exist from project %d into %d", oldAttachmentID, attachment.FileID, ld.ProjectID, ld.Project.ID)
continue continue
} }
return err return err
@ -272,10 +272,10 @@ func duplicateTasks(s *xorm.Session, doer web.Auth, ld *ListDuplicate, bucketMap
_ = attachment.File.File.Close() _ = attachment.File.File.Close()
} }
log.Debugf("Duplicated attachment %d into %d from list %d into %d", oldAttachmentID, attachment.ID, ld.ListID, ld.List.ID) log.Debugf("Duplicated attachment %d into %d from project %d into %d", oldAttachmentID, attachment.ID, ld.ProjectID, ld.Project.ID)
} }
log.Debugf("Duplicated all attachments from list %d into %d", ld.ListID, ld.List.ID) log.Debugf("Duplicated all attachments from project %d into %d", ld.ProjectID, ld.Project.ID)
// Copy label tasks (not the labels) // Copy label tasks (not the labels)
labelTasks := []*LabelTask{} labelTasks := []*LabelTask{}
@ -292,7 +292,7 @@ func duplicateTasks(s *xorm.Session, doer web.Auth, ld *ListDuplicate, bucketMap
} }
} }
log.Debugf("Duplicated all labels from list %d into %d", ld.ListID, ld.List.ID) log.Debugf("Duplicated all labels from project %d into %d", ld.ProjectID, ld.Project.ID)
// Assignees // Assignees
// Only copy those assignees who have access to the task // Only copy those assignees who have access to the task
@ -303,18 +303,18 @@ func duplicateTasks(s *xorm.Session, doer web.Auth, ld *ListDuplicate, bucketMap
} }
for _, a := range assignees { for _, a := range assignees {
t := &Task{ t := &Task{
ID: taskMap[a.TaskID], ID: taskMap[a.TaskID],
ListID: ld.List.ID, ProjectID: ld.Project.ID,
} }
if err := t.addNewAssigneeByID(s, a.UserID, ld.List, doer); err != nil { if err := t.addNewAssigneeByID(s, a.UserID, ld.Project, doer); err != nil {
if IsErrUserDoesNotHaveAccessToList(err) { if IsErrUserDoesNotHaveAccessToProject(err) {
continue continue
} }
return err return err
} }
} }
log.Debugf("Duplicated all assignees from list %d into %d", ld.ListID, ld.List.ID) log.Debugf("Duplicated all assignees from project %d into %d", ld.ProjectID, ld.Project.ID)
// Comments // Comments
comments := []*TaskComment{} comments := []*TaskComment{}
@ -330,10 +330,10 @@ func duplicateTasks(s *xorm.Session, doer web.Auth, ld *ListDuplicate, bucketMap
} }
} }
log.Debugf("Duplicated all comments from list %d into %d", ld.ListID, ld.List.ID) log.Debugf("Duplicated all comments from project %d into %d", ld.ProjectID, ld.Project.ID)
// Relations in that list // Relations in that project
// Low-Effort: Only copy those relations which are between tasks in the same list // Low-Effort: Only copy those relations which are between tasks in the same project
// because we can do that without a lot of hassle // because we can do that without a lot of hassle
relations := []*TaskRelation{} relations := []*TaskRelation{}
err = s.In("task_id", oldTaskIDs).Find(&relations) err = s.In("task_id", oldTaskIDs).Find(&relations)
@ -353,7 +353,7 @@ func duplicateTasks(s *xorm.Session, doer web.Auth, ld *ListDuplicate, bucketMap
} }
} }
log.Debugf("Duplicated all task relations from list %d into %d", ld.ListID, ld.List.ID) log.Debugf("Duplicated all task relations from project %d into %d", ld.ProjectID, ld.Project.ID)
return nil return nil
} }

View File

@ -25,7 +25,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestListDuplicate(t *testing.T) { func TestProjectDuplicate(t *testing.T) {
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
files.InitTestFileFixtures(t) files.InitTestFileFixtures(t)
@ -36,8 +36,8 @@ func TestListDuplicate(t *testing.T) {
ID: 1, ID: 1,
} }
l := &ListDuplicate{ l := &ProjectDuplicate{
ListID: 1, ProjectID: 1,
NamespaceID: 1, NamespaceID: 1,
} }
can, err := l.CanCreate(s, u) can, err := l.CanCreate(s, u)

View File

@ -23,35 +23,35 @@ import (
"xorm.io/xorm" "xorm.io/xorm"
) )
// CanWrite return whether the user can write on that list or not // CanWrite return whether the user can write on that project or not
func (l *List) CanWrite(s *xorm.Session, a web.Auth) (bool, error) { func (l *Project) CanWrite(s *xorm.Session, a web.Auth) (bool, error) {
// The favorite list can't be edited // The favorite project can't be edited
if l.ID == FavoritesPseudoList.ID { if l.ID == FavoritesPseudoProject.ID {
return false, nil return false, nil
} }
// Get the list and check the right // Get the project and check the right
originalList, err := GetListSimpleByID(s, l.ID) originalProject, err := GetProjectSimpleByID(s, l.ID)
if err != nil { if err != nil {
return false, err return false, err
} }
// We put the result of the is archived check in a separate variable to be able to return it later without // We put the result of the is archived check in a separate variable to be able to return it later without
// needing to recheck it again // needing to recheck it again
errIsArchived := originalList.CheckIsArchived(s) errIsArchived := originalProject.CheckIsArchived(s)
var canWrite bool var canWrite bool
// Check if we're dealing with a share auth // Check if we're dealing with a share auth
shareAuth, ok := a.(*LinkSharing) shareAuth, ok := a.(*LinkSharing)
if ok { if ok {
return originalList.ID == shareAuth.ListID && return originalProject.ID == shareAuth.ProjectID &&
(shareAuth.Right == RightWrite || shareAuth.Right == RightAdmin), errIsArchived (shareAuth.Right == RightWrite || shareAuth.Right == RightAdmin), errIsArchived
} }
// Check if the user is either owner or can write to the list // Check if the user is either owner or can write to the project
if originalList.isOwner(&user.User{ID: a.GetID()}) { if originalProject.isOwner(&user.User{ID: a.GetID()}) {
canWrite = true canWrite = true
} }
@ -59,47 +59,47 @@ func (l *List) CanWrite(s *xorm.Session, a web.Auth) (bool, error) {
return canWrite, errIsArchived return canWrite, errIsArchived
} }
canWrite, _, err = originalList.checkRight(s, a, RightWrite, RightAdmin) canWrite, _, err = originalProject.checkRight(s, a, RightWrite, RightAdmin)
if err != nil { if err != nil {
return false, err return false, err
} }
return canWrite, errIsArchived return canWrite, errIsArchived
} }
// CanRead checks if a user has read access to a list // CanRead checks if a user has read access to a project
func (l *List) CanRead(s *xorm.Session, a web.Auth) (bool, int, error) { func (l *Project) CanRead(s *xorm.Session, a web.Auth) (bool, int, error) {
// The favorite list needs a special treatment // The favorite project needs a special treatment
if l.ID == FavoritesPseudoList.ID { if l.ID == FavoritesPseudoProject.ID {
owner, err := user.GetFromAuth(a) owner, err := user.GetFromAuth(a)
if err != nil { if err != nil {
return false, 0, err return false, 0, err
} }
*l = FavoritesPseudoList *l = FavoritesPseudoProject
l.Owner = owner l.Owner = owner
return true, int(RightRead), nil return true, int(RightRead), nil
} }
// Saved Filter Lists need a special case // Saved Filter Projects need a special case
if getSavedFilterIDFromListID(l.ID) > 0 { if getSavedFilterIDFromProjectID(l.ID) > 0 {
sf := &SavedFilter{ID: getSavedFilterIDFromListID(l.ID)} sf := &SavedFilter{ID: getSavedFilterIDFromProjectID(l.ID)}
return sf.CanRead(s, a) return sf.CanRead(s, a)
} }
// Check if the user is either owner or can read // Check if the user is either owner or can read
var err error var err error
originalList, err := GetListSimpleByID(s, l.ID) originalProject, err := GetProjectSimpleByID(s, l.ID)
if err != nil { if err != nil {
return false, 0, err return false, 0, err
} }
*l = *originalList *l = *originalProject
// Check if we're dealing with a share auth // Check if we're dealing with a share auth
shareAuth, ok := a.(*LinkSharing) shareAuth, ok := a.(*LinkSharing)
if ok { if ok {
return l.ID == shareAuth.ListID && return l.ID == shareAuth.ProjectID &&
(shareAuth.Right == RightRead || shareAuth.Right == RightWrite || shareAuth.Right == RightAdmin), int(shareAuth.Right), nil (shareAuth.Right == RightRead || shareAuth.Right == RightWrite || shareAuth.Right == RightAdmin), int(shareAuth.Right), nil
} }
@ -109,20 +109,20 @@ func (l *List) CanRead(s *xorm.Session, a web.Auth) (bool, int, error) {
return l.checkRight(s, a, RightRead, RightWrite, RightAdmin) return l.checkRight(s, a, RightRead, RightWrite, RightAdmin)
} }
// CanUpdate checks if the user can update a list // CanUpdate checks if the user can update a project
func (l *List) CanUpdate(s *xorm.Session, a web.Auth) (canUpdate bool, err error) { func (l *Project) CanUpdate(s *xorm.Session, a web.Auth) (canUpdate bool, err error) {
// The favorite list can't be edited // The favorite project can't be edited
if l.ID == FavoritesPseudoList.ID { if l.ID == FavoritesPseudoProject.ID {
return false, nil return false, nil
} }
// Get the list // Get the project
ol, err := GetListSimpleByID(s, l.ID) ol, err := GetProjectSimpleByID(s, l.ID)
if err != nil { if err != nil {
return false, err return false, err
} }
// Check if we're moving the list into a different namespace. // Check if we're moving the project into a different namespace.
// If that is the case, we need to verify permissions to do so. // If that is the case, we need to verify permissions to do so.
if l.NamespaceID != 0 && l.NamespaceID != ol.NamespaceID { if l.NamespaceID != 0 && l.NamespaceID != ol.NamespaceID {
newNamespace := &Namespace{ID: l.NamespaceID} newNamespace := &Namespace{ID: l.NamespaceID}
@ -135,7 +135,7 @@ func (l *List) CanUpdate(s *xorm.Session, a web.Auth) (canUpdate bool, err error
} }
} }
fid := getSavedFilterIDFromListID(l.ID) fid := getSavedFilterIDFromProjectID(l.ID)
if fid > 0 { if fid > 0 {
sf, err := getSavedFilterSimpleByID(s, fid) sf, err := getSavedFilterSimpleByID(s, fid)
if err != nil { if err != nil {
@ -146,33 +146,33 @@ func (l *List) CanUpdate(s *xorm.Session, a web.Auth) (canUpdate bool, err error
} }
canUpdate, err = l.CanWrite(s, a) canUpdate, err = l.CanWrite(s, a)
// If the list is archived and the user tries to un-archive it, let the request through // If the project is archived and the user tries to un-archive it, let the request through
if IsErrListIsArchived(err) && !l.IsArchived { if IsErrProjectIsArchived(err) && !l.IsArchived {
err = nil err = nil
} }
return canUpdate, err return canUpdate, err
} }
// CanDelete checks if the user can delete a list // CanDelete checks if the user can delete a project
func (l *List) CanDelete(s *xorm.Session, a web.Auth) (bool, error) { func (l *Project) CanDelete(s *xorm.Session, a web.Auth) (bool, error) {
return l.IsAdmin(s, a) return l.IsAdmin(s, a)
} }
// CanCreate checks if the user can create a list // CanCreate checks if the user can create a project
func (l *List) CanCreate(s *xorm.Session, a web.Auth) (bool, error) { func (l *Project) CanCreate(s *xorm.Session, a web.Auth) (bool, error) {
// A user can create a list if they have write access to the namespace // A user can create a project if they have write access to the namespace
n := &Namespace{ID: l.NamespaceID} n := &Namespace{ID: l.NamespaceID}
return n.CanWrite(s, a) return n.CanWrite(s, a)
} }
// IsAdmin returns whether the user has admin rights on the list or not // IsAdmin returns whether the user has admin rights on the project or not
func (l *List) IsAdmin(s *xorm.Session, a web.Auth) (bool, error) { func (l *Project) IsAdmin(s *xorm.Session, a web.Auth) (bool, error) {
// The favorite list can't be edited // The favorite project can't be edited
if l.ID == FavoritesPseudoList.ID { if l.ID == FavoritesPseudoProject.ID {
return false, nil return false, nil
} }
originalList, err := GetListSimpleByID(s, l.ID) originalProject, err := GetProjectSimpleByID(s, l.ID)
if err != nil { if err != nil {
return false, err return false, err
} }
@ -180,26 +180,26 @@ func (l *List) IsAdmin(s *xorm.Session, a web.Auth) (bool, error) {
// Check if we're dealing with a share auth // Check if we're dealing with a share auth
shareAuth, ok := a.(*LinkSharing) shareAuth, ok := a.(*LinkSharing)
if ok { if ok {
return originalList.ID == shareAuth.ListID && shareAuth.Right == RightAdmin, nil return originalProject.ID == shareAuth.ProjectID && shareAuth.Right == RightAdmin, nil
} }
// Check all the things // Check all the things
// Check if the user is either owner or can write to the list // Check if the user is either owner or can write to the project
// Owners are always admins // Owners are always admins
if originalList.isOwner(&user.User{ID: a.GetID()}) { if originalProject.isOwner(&user.User{ID: a.GetID()}) {
return true, nil return true, nil
} }
is, _, err := originalList.checkRight(s, a, RightAdmin) is, _, err := originalProject.checkRight(s, a, RightAdmin)
return is, err return is, err
} }
// Little helper function to check if a user is list owner // Little helper function to check if a user is project owner
func (l *List) isOwner(u *user.User) bool { func (l *Project) isOwner(u *user.User) bool {
return l.OwnerID == u.ID return l.OwnerID == u.ID
} }
// Checks n different rights for any given user // Checks n different rights for any given user
func (l *List) checkRight(s *xorm.Session, a web.Auth, rights ...Right) (bool, int, error) { func (l *Project) checkRight(s *xorm.Session, a web.Auth, rights ...Right) (bool, int, error) {
/* /*
The following loop creates a sql condition like this one: The following loop creates a sql condition like this one:
@ -208,30 +208,30 @@ func (l *List) checkRight(s *xorm.Session, a web.Auth, rights ...Right) (bool, i
(tm.user_id = 1 AND tn.right = 1) OR (tm2.user_id = 1 AND tl.right = 1) OR (tm.user_id = 1 AND tn.right = 1) OR (tm2.user_id = 1 AND tl.right = 1) OR
for each passed right. That way, we can check with a single sql query (instead if 8) for each passed right. That way, we can check with a single sql query (instead if 8)
if the user has the right to see the list or not. if the user has the right to see the project or not.
*/ */
var conds []builder.Cond var conds []builder.Cond
for _, r := range rights { for _, r := range rights {
// User conditions // User conditions
// If the list was shared directly with the user and the user has the right // If the project was shared directly with the user and the user has the right
conds = append(conds, builder.And( conds = append(conds, builder.And(
builder.Eq{"ul.user_id": a.GetID()}, builder.Eq{"ul.user_id": a.GetID()},
builder.Eq{"ul.right": r}, builder.Eq{"ul.right": r},
)) ))
// If the namespace this list belongs to was shared directly with the user and the user has the right // If the namespace this project belongs to was shared directly with the user and the user has the right
conds = append(conds, builder.And( conds = append(conds, builder.And(
builder.Eq{"un.user_id": a.GetID()}, builder.Eq{"un.user_id": a.GetID()},
builder.Eq{"un.right": r}, builder.Eq{"un.right": r},
)) ))
// Team rights // Team rights
// If the list was shared directly with the team and the team has the right // If the project was shared directly with the team and the team has the right
conds = append(conds, builder.And( conds = append(conds, builder.And(
builder.Eq{"tm2.user_id": a.GetID()}, builder.Eq{"tm2.user_id": a.GetID()},
builder.Eq{"tl.right": r}, builder.Eq{"tl.right": r},
)) ))
// If the namespace this list belongs to was shared directly with the team and the team has the right // If the namespace this project belongs to was shared directly with the team and the team has the right
conds = append(conds, builder.And( conds = append(conds, builder.And(
builder.Eq{"tm.user_id": a.GetID()}, builder.Eq{"tm.user_id": a.GetID()},
builder.Eq{"tn.right": r}, builder.Eq{"tn.right": r},
@ -241,30 +241,30 @@ func (l *List) checkRight(s *xorm.Session, a web.Auth, rights ...Right) (bool, i
// If the user is the owner of a namespace, it has any right, all the time // If the user is the owner of a namespace, it has any right, all the time
conds = append(conds, builder.Eq{"n.owner_id": a.GetID()}) conds = append(conds, builder.Eq{"n.owner_id": a.GetID()})
type allListRights struct { type allProjectRights struct {
UserNamespace *NamespaceUser `xorm:"extends"` UserNamespace *NamespaceUser `xorm:"extends"`
UserList *ListUser `xorm:"extends"` UserProject *ProjectUser `xorm:"extends"`
TeamNamespace *TeamNamespace `xorm:"extends"` TeamNamespace *TeamNamespace `xorm:"extends"`
TeamList *TeamList `xorm:"extends"` TeamProject *TeamProject `xorm:"extends"`
NamespaceOwnerID int64 `xorm:"namespaces_owner_id"` NamespaceOwnerID int64 `xorm:"namespaces_owner_id"`
} }
r := &allListRights{} r := &allProjectRights{}
var maxRight = 0 var maxRight = 0
exists, err := s. exists, err := s.
Select("l.*, un.right, ul.right, tn.right, tl.right, n.owner_id as namespaces_owner_id"). Select("l.*, un.right, ul.right, tn.right, tl.right, n.owner_id as namespaces_owner_id").
Table("lists"). Table("projects").
Alias("l"). Alias("l").
// User stuff // User stuff
Join("LEFT", []string{"users_namespaces", "un"}, "un.namespace_id = l.namespace_id"). Join("LEFT", []string{"users_namespaces", "un"}, "un.namespace_id = l.namespace_id").
Join("LEFT", []string{"users_lists", "ul"}, "ul.list_id = l.id"). Join("LEFT", []string{"users_projects", "ul"}, "ul.project_id = l.id").
Join("LEFT", []string{"namespaces", "n"}, "n.id = l.namespace_id"). Join("LEFT", []string{"namespaces", "n"}, "n.id = l.namespace_id").
// Team stuff // Team stuff
Join("LEFT", []string{"team_namespaces", "tn"}, " l.namespace_id = tn.namespace_id"). Join("LEFT", []string{"team_namespaces", "tn"}, " l.namespace_id = tn.namespace_id").
Join("LEFT", []string{"team_members", "tm"}, "tm.team_id = tn.team_id"). Join("LEFT", []string{"team_members", "tm"}, "tm.team_id = tn.team_id").
Join("LEFT", []string{"team_lists", "tl"}, "l.id = tl.list_id"). Join("LEFT", []string{"team_projects", "tl"}, "l.id = tl.project_id").
Join("LEFT", []string{"team_members", "tm2"}, "tm2.team_id = tl.team_id"). Join("LEFT", []string{"team_members", "tm2"}, "tm2.team_id = tl.team_id").
// The actual condition // The actual condition
Where(builder.And( Where(builder.And(
@ -279,14 +279,14 @@ func (l *List) checkRight(s *xorm.Session, a web.Auth, rights ...Right) (bool, i
if int(r.UserNamespace.Right) > maxRight { if int(r.UserNamespace.Right) > maxRight {
maxRight = int(r.UserNamespace.Right) maxRight = int(r.UserNamespace.Right)
} }
if int(r.UserList.Right) > maxRight { if int(r.UserProject.Right) > maxRight {
maxRight = int(r.UserList.Right) maxRight = int(r.UserProject.Right)
} }
if int(r.TeamNamespace.Right) > maxRight { if int(r.TeamNamespace.Right) > maxRight {
maxRight = int(r.TeamNamespace.Right) maxRight = int(r.TeamNamespace.Right)
} }
if int(r.TeamList.Right) > maxRight { if int(r.TeamProject.Right) > maxRight {
maxRight = int(r.TeamList.Right) maxRight = int(r.TeamProject.Right)
} }
if r.NamespaceOwnerID == a.GetID() { if r.NamespaceOwnerID == a.GetID() {
maxRight = int(RightAdmin) maxRight = int(RightAdmin)

View File

@ -27,14 +27,14 @@ import (
"xorm.io/xorm" "xorm.io/xorm"
) )
// TeamList defines the relation between a team and a list // TeamProject defines the relation between a team and a project
type TeamList struct { type TeamProject struct {
// The unique, numeric id of this list <-> team relation. // The unique, numeric id of this project <-> team relation.
ID int64 `xorm:"bigint autoincr not null unique pk" json:"id"` ID int64 `xorm:"bigint autoincr not null unique pk" json:"id"`
// The team id. // The team id.
TeamID int64 `xorm:"bigint not null INDEX" json:"team_id" param:"team"` TeamID int64 `xorm:"bigint not null INDEX" json:"team_id" param:"team"`
// The list id. // The project id.
ListID int64 `xorm:"bigint not null INDEX" json:"-" param:"list"` ProjectID int64 `xorm:"bigint not null INDEX" json:"-" param:"project"`
// The right this team has. 0 = Read only, 1 = Read & Write, 2 = Admin. See the docs for more details. // The right this team has. 0 = Read only, 1 = Read & Write, 2 = Admin. See the docs for more details.
Right Right `xorm:"bigint INDEX not null default 0" json:"right" valid:"length(0|2)" maximum:"2" default:"0"` Right Right `xorm:"bigint INDEX not null default 0" json:"right" valid:"length(0|2)" maximum:"2" default:"0"`
@ -48,8 +48,8 @@ type TeamList struct {
} }
// TableName makes beautiful table names // TableName makes beautiful table names
func (TeamList) TableName() string { func (TeamProject) TableName() string {
return "team_lists" return "team_projects"
} }
// TeamWithRight represents a team, combined with rights. // TeamWithRight represents a team, combined with rights.
@ -58,22 +58,22 @@ type TeamWithRight struct {
Right Right `json:"right"` Right Right `json:"right"`
} }
// Create creates a new team <-> list relation // Create creates a new team <-> project relation
// @Summary Add a team to a list // @Summary Add a team to a project
// @Description Gives a team access to a list. // @Description Gives a team access to a project.
// @tags sharing // @tags sharing
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Security JWTKeyAuth // @Security JWTKeyAuth
// @Param id path int true "List ID" // @Param id path int true "Project ID"
// @Param list body models.TeamList true "The team you want to add to the list." // @Param project body models.TeamProject true "The team you want to add to the project."
// @Success 201 {object} models.TeamList "The created team<->list relation." // @Success 201 {object} models.TeamProject "The created team<->project relation."
// @Failure 400 {object} web.HTTPError "Invalid team list object provided." // @Failure 400 {object} web.HTTPError "Invalid team project object provided."
// @Failure 404 {object} web.HTTPError "The team does not exist." // @Failure 404 {object} web.HTTPError "The team does not exist."
// @Failure 403 {object} web.HTTPError "The user does not have access to the list" // @Failure 403 {object} web.HTTPError "The user does not have access to the project"
// @Failure 500 {object} models.Message "Internal error" // @Failure 500 {object} models.Message "Internal error"
// @Router /lists/{id}/teams [put] // @Router /projects/{id}/teams [put]
func (tl *TeamList) Create(s *xorm.Session, a web.Auth) (err error) { func (tl *TeamProject) Create(s *xorm.Session, a web.Auth) (err error) {
// Check if the rights are valid // Check if the rights are valid
if err = tl.Right.isValid(); err != nil { if err = tl.Right.isValid(); err != nil {
@ -86,21 +86,21 @@ func (tl *TeamList) Create(s *xorm.Session, a web.Auth) (err error) {
return err return err
} }
// Check if the list exists // Check if the project exists
l, err := GetListSimpleByID(s, tl.ListID) l, err := GetProjectSimpleByID(s, tl.ProjectID)
if err != nil { if err != nil {
return err return err
} }
// Check if the team is already on the list // Check if the team is already on the project
exists, err := s.Where("team_id = ?", tl.TeamID). exists, err := s.Where("team_id = ?", tl.TeamID).
And("list_id = ?", tl.ListID). And("project_id = ?", tl.ProjectID).
Get(&TeamList{}) Get(&TeamProject{})
if err != nil { if err != nil {
return return
} }
if exists { if exists {
return ErrTeamAlreadyHasAccess{tl.TeamID, tl.ListID} return ErrTeamAlreadyHasAccess{tl.TeamID, tl.ProjectID}
} }
// Insert the new team // Insert the new team
@ -109,33 +109,33 @@ func (tl *TeamList) Create(s *xorm.Session, a web.Auth) (err error) {
return err return err
} }
err = events.Dispatch(&ListSharedWithTeamEvent{ err = events.Dispatch(&ProjectSharedWithTeamEvent{
List: l, Project: l,
Team: team, Team: team,
Doer: a, Doer: a,
}) })
if err != nil { if err != nil {
return err return err
} }
err = updateListLastUpdated(s, l) err = updateProjectLastUpdated(s, l)
return return
} }
// Delete deletes a team <-> list relation based on the list & team id // Delete deletes a team <-> project relation based on the project & team id
// @Summary Delete a team from a list // @Summary Delete a team from a project
// @Description Delets a team from a list. The team won't have access to the list anymore. // @Description Delets a team from a project. The team won't have access to the project anymore.
// @tags sharing // @tags sharing
// @Produce json // @Produce json
// @Security JWTKeyAuth // @Security JWTKeyAuth
// @Param listID path int true "List ID" // @Param projectID path int true "Project ID"
// @Param teamID path int true "Team ID" // @Param teamID path int true "Team ID"
// @Success 200 {object} models.Message "The team was successfully deleted." // @Success 200 {object} models.Message "The team was successfully deleted."
// @Failure 403 {object} web.HTTPError "The user does not have access to the list" // @Failure 403 {object} web.HTTPError "The user does not have access to the project"
// @Failure 404 {object} web.HTTPError "Team or list does not exist." // @Failure 404 {object} web.HTTPError "Team or project does not exist."
// @Failure 500 {object} models.Message "Internal error" // @Failure 500 {object} models.Message "Internal error"
// @Router /lists/{listID}/teams/{teamID} [delete] // @Router /projects/{projectID}/teams/{teamID} [delete]
func (tl *TeamList) Delete(s *xorm.Session, a web.Auth) (err error) { func (tl *TeamProject) Delete(s *xorm.Session, a web.Auth) (err error) {
// Check if the team exists // Check if the team exists
_, err = GetTeamByID(s, tl.TeamID) _, err = GetTeamByID(s, tl.TeamID)
@ -143,53 +143,53 @@ func (tl *TeamList) Delete(s *xorm.Session, a web.Auth) (err error) {
return return
} }
// Check if the team has access to the list // Check if the team has access to the project
has, err := s. has, err := s.
Where("team_id = ? AND list_id = ?", tl.TeamID, tl.ListID). Where("team_id = ? AND project_id = ?", tl.TeamID, tl.ProjectID).
Get(&TeamList{}) Get(&TeamProject{})
if err != nil { if err != nil {
return return
} }
if !has { if !has {
return ErrTeamDoesNotHaveAccessToList{TeamID: tl.TeamID, ListID: tl.ListID} return ErrTeamDoesNotHaveAccessToProject{TeamID: tl.TeamID, ProjectID: tl.ProjectID}
} }
// Delete the relation // Delete the relation
_, err = s.Where("team_id = ?", tl.TeamID). _, err = s.Where("team_id = ?", tl.TeamID).
And("list_id = ?", tl.ListID). And("project_id = ?", tl.ProjectID).
Delete(TeamList{}) Delete(TeamProject{})
if err != nil { if err != nil {
return err return err
} }
err = updateListLastUpdated(s, &List{ID: tl.ListID}) err = updateProjectLastUpdated(s, &Project{ID: tl.ProjectID})
return return
} }
// ReadAll implements the method to read all teams of a list // ReadAll implements the method to read all teams of a project
// @Summary Get teams on a list // @Summary Get teams on a project
// @Description Returns a list with all teams which have access on a given list. // @Description Returns a project with all teams which have access on a given project.
// @tags sharing // @tags sharing
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param id path int true "List ID" // @Param id path int true "Project ID"
// @Param page query int false "The page number. Used for pagination. If not provided, the first page of results is returned." // @Param page query int false "The page number. Used for pagination. If not provided, the first page of results is returned."
// @Param per_page query int false "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page." // @Param per_page query int false "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page."
// @Param s query string false "Search teams by its name." // @Param s query string false "Search teams by its name."
// @Security JWTKeyAuth // @Security JWTKeyAuth
// @Success 200 {array} models.TeamWithRight "The teams with their right." // @Success 200 {array} models.TeamWithRight "The teams with their right."
// @Failure 403 {object} web.HTTPError "No right to see the list." // @Failure 403 {object} web.HTTPError "No right to see the project."
// @Failure 500 {object} models.Message "Internal error" // @Failure 500 {object} models.Message "Internal error"
// @Router /lists/{id}/teams [get] // @Router /projects/{id}/teams [get]
func (tl *TeamList) ReadAll(s *xorm.Session, a web.Auth, search string, page int, perPage int) (result interface{}, resultCount int, totalItems int64, err error) { func (tl *TeamProject) ReadAll(s *xorm.Session, a web.Auth, search string, page int, perPage int) (result interface{}, resultCount int, totalItems int64, err error) {
// Check if the user can read the namespace // Check if the user can read the namespace
l := &List{ID: tl.ListID} l := &Project{ID: tl.ProjectID}
canRead, _, err := l.CanRead(s, a) canRead, _, err := l.CanRead(s, a)
if err != nil { if err != nil {
return nil, 0, 0, err return nil, 0, 0, err
} }
if !canRead { if !canRead {
return nil, 0, 0, ErrNeedToHaveListReadAccess{ListID: tl.ListID, UserID: a.GetID()} return nil, 0, 0, ErrNeedToHaveProjectReadAccess{ProjectID: tl.ProjectID, UserID: a.GetID()}
} }
limit, start := getLimitFromPageIndex(page, perPage) limit, start := getLimitFromPageIndex(page, perPage)
@ -198,8 +198,8 @@ func (tl *TeamList) ReadAll(s *xorm.Session, a web.Auth, search string, page int
all := []*TeamWithRight{} all := []*TeamWithRight{}
query := s. query := s.
Table("teams"). Table("teams").
Join("INNER", "team_lists", "team_id = teams.id"). Join("INNER", "team_projects", "team_id = teams.id").
Where("team_lists.list_id = ?", tl.ListID). Where("team_projects.project_id = ?", tl.ProjectID).
Where(db.ILIKE("teams.name", search)) Where(db.ILIKE("teams.name", search))
if limit > 0 { if limit > 0 {
query = query.Limit(limit, start) query = query.Limit(limit, start)
@ -221,8 +221,8 @@ func (tl *TeamList) ReadAll(s *xorm.Session, a web.Auth, search string, page int
totalItems, err = s. totalItems, err = s.
Table("teams"). Table("teams").
Join("INNER", "team_lists", "team_id = teams.id"). Join("INNER", "team_projects", "team_id = teams.id").
Where("team_lists.list_id = ?", tl.ListID). Where("team_projects.project_id = ?", tl.ProjectID).
Where("teams.name LIKE ?", "%"+search+"%"). Where("teams.name LIKE ?", "%"+search+"%").
Count(&TeamWithRight{}) Count(&TeamWithRight{})
if err != nil { if err != nil {
@ -232,22 +232,22 @@ func (tl *TeamList) ReadAll(s *xorm.Session, a web.Auth, search string, page int
return all, len(all), totalItems, err return all, len(all), totalItems, err
} }
// Update updates a team <-> list relation // Update updates a team <-> project relation
// @Summary Update a team <-> list relation // @Summary Update a team <-> project relation
// @Description Update a team <-> list relation. Mostly used to update the right that team has. // @Description Update a team <-> project relation. Mostly used to update the right that team has.
// @tags sharing // @tags sharing
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param listID path int true "List ID" // @Param projectID path int true "Project ID"
// @Param teamID path int true "Team ID" // @Param teamID path int true "Team ID"
// @Param list body models.TeamList true "The team you want to update." // @Param project body models.TeamProject true "The team you want to update."
// @Security JWTKeyAuth // @Security JWTKeyAuth
// @Success 200 {object} models.TeamList "The updated team <-> list relation." // @Success 200 {object} models.TeamProject "The updated team <-> project relation."
// @Failure 403 {object} web.HTTPError "The user does not have admin-access to the list" // @Failure 403 {object} web.HTTPError "The user does not have admin-access to the project"
// @Failure 404 {object} web.HTTPError "Team or list does not exist." // @Failure 404 {object} web.HTTPError "Team or project does not exist."
// @Failure 500 {object} models.Message "Internal error" // @Failure 500 {object} models.Message "Internal error"
// @Router /lists/{listID}/teams/{teamID} [post] // @Router /projects/{projectID}/teams/{teamID} [post]
func (tl *TeamList) Update(s *xorm.Session, a web.Auth) (err error) { func (tl *TeamProject) Update(s *xorm.Session, a web.Auth) (err error) {
// Check if the right is valid // Check if the right is valid
if err := tl.Right.isValid(); err != nil { if err := tl.Right.isValid(); err != nil {
@ -255,13 +255,13 @@ func (tl *TeamList) Update(s *xorm.Session, a web.Auth) (err error) {
} }
_, err = s. _, err = s.
Where("list_id = ? AND team_id = ?", tl.ListID, tl.TeamID). Where("project_id = ? AND team_id = ?", tl.ProjectID, tl.TeamID).
Cols("right"). Cols("right").
Update(tl) Update(tl)
if err != nil { if err != nil {
return err return err
} }
err = updateListLastUpdated(s, &List{ID: tl.ListID}) err = updateProjectLastUpdated(s, &Project{ID: tl.ProjectID})
return return
} }

View File

@ -21,27 +21,27 @@ import (
"xorm.io/xorm" "xorm.io/xorm"
) )
// CanCreate checks if the user can create a team <-> list relation // CanCreate checks if the user can create a team <-> project relation
func (tl *TeamList) CanCreate(s *xorm.Session, a web.Auth) (bool, error) { func (tl *TeamProject) CanCreate(s *xorm.Session, a web.Auth) (bool, error) {
return tl.canDoTeamList(s, a) return tl.canDoTeamProject(s, a)
} }
// CanDelete checks if the user can delete a team <-> list relation // CanDelete checks if the user can delete a team <-> project relation
func (tl *TeamList) CanDelete(s *xorm.Session, a web.Auth) (bool, error) { func (tl *TeamProject) CanDelete(s *xorm.Session, a web.Auth) (bool, error) {
return tl.canDoTeamList(s, a) return tl.canDoTeamProject(s, a)
} }
// CanUpdate checks if the user can update a team <-> list relation // CanUpdate checks if the user can update a team <-> project relation
func (tl *TeamList) CanUpdate(s *xorm.Session, a web.Auth) (bool, error) { func (tl *TeamProject) CanUpdate(s *xorm.Session, a web.Auth) (bool, error) {
return tl.canDoTeamList(s, a) return tl.canDoTeamProject(s, a)
} }
func (tl *TeamList) canDoTeamList(s *xorm.Session, a web.Auth) (bool, error) { func (tl *TeamProject) canDoTeamProject(s *xorm.Session, a web.Auth) (bool, error) {
// Link shares aren't allowed to do anything // Link shares aren't allowed to do anything
if _, is := a.(*LinkSharing); is { if _, is := a.(*LinkSharing); is {
return false, nil return false, nil
} }
l := List{ID: tl.ListID} l := Project{ID: tl.ProjectID}
return l.IsAdmin(s, a) return l.IsAdmin(s, a)
} }

View File

@ -28,13 +28,13 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestTeamList_ReadAll(t *testing.T) { func TestTeamProject_ReadAll(t *testing.T) {
u := &user.User{ID: 1} u := &user.User{ID: 1}
t.Run("normal", func(t *testing.T) { t.Run("normal", func(t *testing.T) {
tl := TeamList{ tl := TeamProject{
TeamID: 1, TeamID: 1,
ListID: 3, ProjectID: 3,
} }
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
s := db.NewSession() s := db.NewSession()
@ -45,22 +45,22 @@ func TestTeamList_ReadAll(t *testing.T) {
assert.Equal(t, ts.Len(), 1) assert.Equal(t, ts.Len(), 1)
_ = s.Close() _ = s.Close()
}) })
t.Run("nonexistant list", func(t *testing.T) { t.Run("nonexistant project", func(t *testing.T) {
tl := TeamList{ tl := TeamProject{
ListID: 99999, ProjectID: 99999,
} }
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
s := db.NewSession() s := db.NewSession()
_, _, _, err := tl.ReadAll(s, u, "", 1, 50) _, _, _, err := tl.ReadAll(s, u, "", 1, 50)
assert.Error(t, err) assert.Error(t, err)
assert.True(t, IsErrListDoesNotExist(err)) assert.True(t, IsErrProjectDoesNotExist(err))
_ = s.Close() _ = s.Close()
}) })
t.Run("namespace owner", func(t *testing.T) { t.Run("namespace owner", func(t *testing.T) {
tl := TeamList{ tl := TeamProject{
TeamID: 1, TeamID: 1,
ListID: 2, ProjectID: 2,
Right: RightAdmin, Right: RightAdmin,
} }
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
s := db.NewSession() s := db.NewSession()
@ -69,21 +69,21 @@ func TestTeamList_ReadAll(t *testing.T) {
_ = s.Close() _ = s.Close()
}) })
t.Run("no access", func(t *testing.T) { t.Run("no access", func(t *testing.T) {
tl := TeamList{ tl := TeamProject{
TeamID: 1, TeamID: 1,
ListID: 5, ProjectID: 5,
Right: RightAdmin, Right: RightAdmin,
} }
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
s := db.NewSession() s := db.NewSession()
_, _, _, err := tl.ReadAll(s, u, "", 1, 50) _, _, _, err := tl.ReadAll(s, u, "", 1, 50)
assert.Error(t, err) assert.Error(t, err)
assert.True(t, IsErrNeedToHaveListReadAccess(err)) assert.True(t, IsErrNeedToHaveProjectReadAccess(err))
_ = s.Close() _ = s.Close()
}) })
t.Run("search", func(t *testing.T) { t.Run("search", func(t *testing.T) {
tl := TeamList{ tl := TeamProject{
ListID: 19, ProjectID: 19,
} }
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
s := db.NewSession() s := db.NewSession()
@ -97,15 +97,15 @@ func TestTeamList_ReadAll(t *testing.T) {
}) })
} }
func TestTeamList_Create(t *testing.T) { func TestTeamProject_Create(t *testing.T) {
u := &user.User{ID: 1} u := &user.User{ID: 1}
t.Run("normal", func(t *testing.T) { t.Run("normal", func(t *testing.T) {
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
s := db.NewSession() s := db.NewSession()
tl := TeamList{ tl := TeamProject{
TeamID: 1, TeamID: 1,
ListID: 1, ProjectID: 1,
Right: RightAdmin, Right: RightAdmin,
} }
allowed, _ := tl.CanCreate(s, u) allowed, _ := tl.CanCreate(s, u)
assert.True(t, allowed) assert.True(t, allowed)
@ -113,19 +113,19 @@ func TestTeamList_Create(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
err = s.Commit() err = s.Commit()
assert.NoError(t, err) assert.NoError(t, err)
db.AssertExists(t, "team_lists", map[string]interface{}{ db.AssertExists(t, "team_projects", map[string]interface{}{
"team_id": 1, "team_id": 1,
"list_id": 1, "project_id": 1,
"right": RightAdmin, "right": RightAdmin,
}, false) }, false)
}) })
t.Run("team already has access", func(t *testing.T) { t.Run("team already has access", func(t *testing.T) {
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
s := db.NewSession() s := db.NewSession()
tl := TeamList{ tl := TeamProject{
TeamID: 1, TeamID: 1,
ListID: 3, ProjectID: 3,
Right: RightAdmin, Right: RightAdmin,
} }
err := tl.Create(s, u) err := tl.Create(s, u)
assert.Error(t, err) assert.Error(t, err)
@ -135,10 +135,10 @@ func TestTeamList_Create(t *testing.T) {
t.Run("wrong rights", func(t *testing.T) { t.Run("wrong rights", func(t *testing.T) {
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
s := db.NewSession() s := db.NewSession()
tl := TeamList{ tl := TeamProject{
TeamID: 1, TeamID: 1,
ListID: 1, ProjectID: 1,
Right: RightUnknown, Right: RightUnknown,
} }
err := tl.Create(s, u) err := tl.Create(s, u)
assert.Error(t, err) assert.Error(t, err)
@ -148,84 +148,84 @@ func TestTeamList_Create(t *testing.T) {
t.Run("nonexistant team", func(t *testing.T) { t.Run("nonexistant team", func(t *testing.T) {
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
s := db.NewSession() s := db.NewSession()
tl := TeamList{ tl := TeamProject{
TeamID: 9999, TeamID: 9999,
ListID: 1, ProjectID: 1,
} }
err := tl.Create(s, u) err := tl.Create(s, u)
assert.Error(t, err) assert.Error(t, err)
assert.True(t, IsErrTeamDoesNotExist(err)) assert.True(t, IsErrTeamDoesNotExist(err))
_ = s.Close() _ = s.Close()
}) })
t.Run("nonexistant list", func(t *testing.T) { t.Run("nonexistant project", func(t *testing.T) {
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
s := db.NewSession() s := db.NewSession()
tl := TeamList{ tl := TeamProject{
TeamID: 1, TeamID: 1,
ListID: 9999, ProjectID: 9999,
} }
err := tl.Create(s, u) err := tl.Create(s, u)
assert.Error(t, err) assert.Error(t, err)
assert.True(t, IsErrListDoesNotExist(err)) assert.True(t, IsErrProjectDoesNotExist(err))
_ = s.Close() _ = s.Close()
}) })
} }
func TestTeamList_Delete(t *testing.T) { func TestTeamProject_Delete(t *testing.T) {
user := &user.User{ID: 1} user := &user.User{ID: 1}
t.Run("normal", func(t *testing.T) { t.Run("normal", func(t *testing.T) {
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
s := db.NewSession() s := db.NewSession()
tl := TeamList{ tl := TeamProject{
TeamID: 1, TeamID: 1,
ListID: 3, ProjectID: 3,
} }
err := tl.Delete(s, user) err := tl.Delete(s, user)
assert.NoError(t, err) assert.NoError(t, err)
err = s.Commit() err = s.Commit()
assert.NoError(t, err) assert.NoError(t, err)
db.AssertMissing(t, "team_lists", map[string]interface{}{ db.AssertMissing(t, "team_projects", map[string]interface{}{
"team_id": 1, "team_id": 1,
"list_id": 3, "project_id": 3,
}) })
}) })
t.Run("nonexistant team", func(t *testing.T) { t.Run("nonexistant team", func(t *testing.T) {
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
s := db.NewSession() s := db.NewSession()
tl := TeamList{ tl := TeamProject{
TeamID: 9999, TeamID: 9999,
ListID: 1, ProjectID: 1,
} }
err := tl.Delete(s, user) err := tl.Delete(s, user)
assert.Error(t, err) assert.Error(t, err)
assert.True(t, IsErrTeamDoesNotExist(err)) assert.True(t, IsErrTeamDoesNotExist(err))
_ = s.Close() _ = s.Close()
}) })
t.Run("nonexistant list", func(t *testing.T) { t.Run("nonexistant project", func(t *testing.T) {
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
s := db.NewSession() s := db.NewSession()
tl := TeamList{ tl := TeamProject{
TeamID: 1, TeamID: 1,
ListID: 9999, ProjectID: 9999,
} }
err := tl.Delete(s, user) err := tl.Delete(s, user)
assert.Error(t, err) assert.Error(t, err)
assert.True(t, IsErrTeamDoesNotHaveAccessToList(err)) assert.True(t, IsErrTeamDoesNotHaveAccessToProject(err))
_ = s.Close() _ = s.Close()
}) })
} }
func TestTeamList_Update(t *testing.T) { func TestTeamProject_Update(t *testing.T) {
type fields struct { type fields struct {
ID int64 ID int64
TeamID int64 TeamID int64
ListID int64 ProjectID int64
Right Right Right Right
Created time.Time Created time.Time
Updated time.Time Updated time.Time
CRUDable web.CRUDable CRUDable web.CRUDable
Rights web.Rights Rights web.Rights
} }
tests := []struct { tests := []struct {
name string name string
@ -236,33 +236,33 @@ func TestTeamList_Update(t *testing.T) {
{ {
name: "Test Update Normally", name: "Test Update Normally",
fields: fields{ fields: fields{
ListID: 3, ProjectID: 3,
TeamID: 1, TeamID: 1,
Right: RightAdmin, Right: RightAdmin,
}, },
}, },
{ {
name: "Test Update to write", name: "Test Update to write",
fields: fields{ fields: fields{
ListID: 3, ProjectID: 3,
TeamID: 1, TeamID: 1,
Right: RightWrite, Right: RightWrite,
}, },
}, },
{ {
name: "Test Update to Read", name: "Test Update to Read",
fields: fields{ fields: fields{
ListID: 3, ProjectID: 3,
TeamID: 1, TeamID: 1,
Right: RightRead, Right: RightRead,
}, },
}, },
{ {
name: "Test Update with invalid right", name: "Test Update with invalid right",
fields: fields{ fields: fields{
ListID: 3, ProjectID: 3,
TeamID: 1, TeamID: 1,
Right: 500, Right: 500,
}, },
wantErr: true, wantErr: true,
errType: IsErrInvalidRight, errType: IsErrInvalidRight,
@ -273,30 +273,30 @@ func TestTeamList_Update(t *testing.T) {
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
s := db.NewSession() s := db.NewSession()
tl := &TeamList{ tl := &TeamProject{
ID: tt.fields.ID, ID: tt.fields.ID,
TeamID: tt.fields.TeamID, TeamID: tt.fields.TeamID,
ListID: tt.fields.ListID, ProjectID: tt.fields.ProjectID,
Right: tt.fields.Right, Right: tt.fields.Right,
Created: tt.fields.Created, Created: tt.fields.Created,
Updated: tt.fields.Updated, Updated: tt.fields.Updated,
CRUDable: tt.fields.CRUDable, CRUDable: tt.fields.CRUDable,
Rights: tt.fields.Rights, Rights: tt.fields.Rights,
} }
err := tl.Update(s, &user.User{ID: 1}) err := tl.Update(s, &user.User{ID: 1})
if (err != nil) != tt.wantErr { if (err != nil) != tt.wantErr {
t.Errorf("TeamList.Update() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("TeamProject.Update() error = %v, wantErr %v", err, tt.wantErr)
} }
if (err != nil) && tt.wantErr && !tt.errType(err) { if (err != nil) && tt.wantErr && !tt.errType(err) {
t.Errorf("TeamList.Update() Wrong error type! Error = %v, want = %v", err, runtime.FuncForPC(reflect.ValueOf(tt.errType).Pointer()).Name()) t.Errorf("TeamProject.Update() Wrong error type! Error = %v, want = %v", err, runtime.FuncForPC(reflect.ValueOf(tt.errType).Pointer()).Name())
} }
err = s.Commit() err = s.Commit()
assert.NoError(t, err) assert.NoError(t, err)
if !tt.wantErr { if !tt.wantErr {
db.AssertExists(t, "team_lists", map[string]interface{}{ db.AssertExists(t, "team_projects", map[string]interface{}{
"list_id": tt.fields.ListID, "project_id": tt.fields.ProjectID,
"team_id": tt.fields.TeamID, "team_id": tt.fields.TeamID,
"right": tt.fields.Right, "right": tt.fields.Right,
}, false) }, false)
} }
}) })

View File

@ -1,4 +1,4 @@
// Vikunja is a to-do list application to facilitate your life. // Vikunja is a to-do project application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved. // Copyright 2018-2021 Vikunja and contributors. All rights reserved.
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
@ -26,7 +26,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestList_CreateOrUpdate(t *testing.T) { func TestProject_CreateOrUpdate(t *testing.T) {
usr := &user.User{ usr := &user.User{
ID: 1, ID: 1,
Username: "user1", Username: "user1",
@ -37,31 +37,31 @@ func TestList_CreateOrUpdate(t *testing.T) {
t.Run("normal", func(t *testing.T) { t.Run("normal", func(t *testing.T) {
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
s := db.NewSession() s := db.NewSession()
list := List{ project := Project{
Title: "test", Title: "test",
Description: "Lorem Ipsum", Description: "Lorem Ipsum",
NamespaceID: 1, NamespaceID: 1,
} }
err := list.Create(s, usr) err := project.Create(s, usr)
assert.NoError(t, err) assert.NoError(t, err)
err = s.Commit() err = s.Commit()
assert.NoError(t, err) assert.NoError(t, err)
db.AssertExists(t, "lists", map[string]interface{}{ db.AssertExists(t, "projects", map[string]interface{}{
"id": list.ID, "id": project.ID,
"title": list.Title, "title": project.Title,
"description": list.Description, "description": project.Description,
"namespace_id": list.NamespaceID, "namespace_id": project.NamespaceID,
}, false) }, false)
}) })
t.Run("nonexistant namespace", func(t *testing.T) { t.Run("nonexistant namespace", func(t *testing.T) {
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
s := db.NewSession() s := db.NewSession()
list := List{ project := Project{
Title: "test", Title: "test",
Description: "Lorem Ipsum", Description: "Lorem Ipsum",
NamespaceID: 999999, NamespaceID: 999999,
} }
err := list.Create(s, usr) err := project.Create(s, usr)
assert.Error(t, err) assert.Error(t, err)
assert.True(t, IsErrNamespaceDoesNotExist(err)) assert.True(t, IsErrNamespaceDoesNotExist(err))
_ = s.Close() _ = s.Close()
@ -70,12 +70,12 @@ func TestList_CreateOrUpdate(t *testing.T) {
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
s := db.NewSession() s := db.NewSession()
usr := &user.User{ID: 9482385} usr := &user.User{ID: 9482385}
list := List{ project := Project{
Title: "test", Title: "test",
Description: "Lorem Ipsum", Description: "Lorem Ipsum",
NamespaceID: 1, NamespaceID: 1,
} }
err := list.Create(s, usr) err := project.Create(s, usr)
assert.Error(t, err) assert.Error(t, err)
assert.True(t, user.IsErrUserDoesNotExist(err)) assert.True(t, user.IsErrUserDoesNotExist(err))
_ = s.Close() _ = s.Close()
@ -83,34 +83,34 @@ func TestList_CreateOrUpdate(t *testing.T) {
t.Run("existing identifier", func(t *testing.T) { t.Run("existing identifier", func(t *testing.T) {
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
s := db.NewSession() s := db.NewSession()
list := List{ project := Project{
Title: "test", Title: "test",
Description: "Lorem Ipsum", Description: "Lorem Ipsum",
Identifier: "test1", Identifier: "test1",
NamespaceID: 1, NamespaceID: 1,
} }
err := list.Create(s, usr) err := project.Create(s, usr)
assert.Error(t, err) assert.Error(t, err)
assert.True(t, IsErrListIdentifierIsNotUnique(err)) assert.True(t, IsErrProjectIdentifierIsNotUnique(err))
_ = s.Close() _ = s.Close()
}) })
t.Run("non ascii characters", func(t *testing.T) { t.Run("non ascii characters", func(t *testing.T) {
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
s := db.NewSession() s := db.NewSession()
list := List{ project := Project{
Title: "приффки фсем", Title: "приффки фсем",
Description: "Lorem Ipsum", Description: "Lorem Ipsum",
NamespaceID: 1, NamespaceID: 1,
} }
err := list.Create(s, usr) err := project.Create(s, usr)
assert.NoError(t, err) assert.NoError(t, err)
err = s.Commit() err = s.Commit()
assert.NoError(t, err) assert.NoError(t, err)
db.AssertExists(t, "lists", map[string]interface{}{ db.AssertExists(t, "projects", map[string]interface{}{
"id": list.ID, "id": project.ID,
"title": list.Title, "title": project.Title,
"description": list.Description, "description": project.Description,
"namespace_id": list.NamespaceID, "namespace_id": project.NamespaceID,
}, false) }, false)
}) })
}) })
@ -119,50 +119,50 @@ func TestList_CreateOrUpdate(t *testing.T) {
t.Run("normal", func(t *testing.T) { t.Run("normal", func(t *testing.T) {
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
s := db.NewSession() s := db.NewSession()
list := List{ project := Project{
ID: 1, ID: 1,
Title: "test", Title: "test",
Description: "Lorem Ipsum", Description: "Lorem Ipsum",
NamespaceID: 1, NamespaceID: 1,
} }
list.Description = "Lorem Ipsum dolor sit amet." project.Description = "Lorem Ipsum dolor sit amet."
err := list.Update(s, usr) err := project.Update(s, usr)
assert.NoError(t, err) assert.NoError(t, err)
err = s.Commit() err = s.Commit()
assert.NoError(t, err) assert.NoError(t, err)
db.AssertExists(t, "lists", map[string]interface{}{ db.AssertExists(t, "projects", map[string]interface{}{
"id": list.ID, "id": project.ID,
"title": list.Title, "title": project.Title,
"description": list.Description, "description": project.Description,
"namespace_id": list.NamespaceID, "namespace_id": project.NamespaceID,
}, false) }, false)
}) })
t.Run("nonexistant", func(t *testing.T) { t.Run("nonexistant", func(t *testing.T) {
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
s := db.NewSession() s := db.NewSession()
list := List{ project := Project{
ID: 99999999, ID: 99999999,
Title: "test", Title: "test",
NamespaceID: 1, NamespaceID: 1,
} }
err := list.Update(s, usr) err := project.Update(s, usr)
assert.Error(t, err) assert.Error(t, err)
assert.True(t, IsErrListDoesNotExist(err)) assert.True(t, IsErrProjectDoesNotExist(err))
_ = s.Close() _ = s.Close()
}) })
t.Run("existing identifier", func(t *testing.T) { t.Run("existing identifier", func(t *testing.T) {
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
s := db.NewSession() s := db.NewSession()
list := List{ project := Project{
Title: "test", Title: "test",
Description: "Lorem Ipsum", Description: "Lorem Ipsum",
Identifier: "test1", Identifier: "test1",
NamespaceID: 1, NamespaceID: 1,
} }
err := list.Create(s, usr) err := project.Create(s, usr)
assert.Error(t, err) assert.Error(t, err)
assert.True(t, IsErrListIdentifierIsNotUnique(err)) assert.True(t, IsErrProjectIdentifierIsNotUnique(err))
_ = s.Close() _ = s.Close()
}) })
t.Run("change namespace", func(t *testing.T) { t.Run("change namespace", func(t *testing.T) {
@ -175,37 +175,37 @@ func TestList_CreateOrUpdate(t *testing.T) {
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
s := db.NewSession() s := db.NewSession()
list := List{ project := Project{
ID: 6, ID: 6,
Title: "Test6", Title: "Test6",
Description: "Lorem Ipsum", Description: "Lorem Ipsum",
NamespaceID: 7, // from 6 NamespaceID: 7, // from 6
} }
can, err := list.CanUpdate(s, usr) can, err := project.CanUpdate(s, usr)
assert.NoError(t, err) assert.NoError(t, err)
assert.True(t, can) assert.True(t, can)
err = list.Update(s, usr) err = project.Update(s, usr)
assert.NoError(t, err) assert.NoError(t, err)
err = s.Commit() err = s.Commit()
assert.NoError(t, err) assert.NoError(t, err)
db.AssertExists(t, "lists", map[string]interface{}{ db.AssertExists(t, "projects", map[string]interface{}{
"id": list.ID, "id": project.ID,
"title": list.Title, "title": project.Title,
"description": list.Description, "description": project.Description,
"namespace_id": list.NamespaceID, "namespace_id": project.NamespaceID,
}, false) }, false)
}) })
// FIXME: The check for whether the namespace is archived is missing in namespace.CanWrite // FIXME: The check for whether the namespace is archived is missing in namespace.CanWrite
// t.Run("archived own", func(t *testing.T) { // t.Run("archived own", func(t *testing.T) {
// db.LoadAndAssertFixtures(t) // db.LoadAndAssertFixtures(t)
// s := db.NewSession() // s := db.NewSession()
// list := List{ // project := Project{
// ID: 1, // ID: 1,
// Title: "Test1", // Title: "Test1",
// Description: "Lorem Ipsum", // Description: "Lorem Ipsum",
// NamespaceID: 16, // from 1 // NamespaceID: 16, // from 1
// } // }
// can, err := list.CanUpdate(s, usr) // can, err := project.CanUpdate(s, usr)
// assert.NoError(t, err) // assert.NoError(t, err)
// assert.False(t, can) // namespace is archived and thus not writeable // assert.False(t, can) // namespace is archived and thus not writeable
// _ = s.Close() // _ = s.Close()
@ -213,13 +213,13 @@ func TestList_CreateOrUpdate(t *testing.T) {
t.Run("others", func(t *testing.T) { t.Run("others", func(t *testing.T) {
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
s := db.NewSession() s := db.NewSession()
list := List{ project := Project{
ID: 1, ID: 1,
Title: "Test1", Title: "Test1",
Description: "Lorem Ipsum", Description: "Lorem Ipsum",
NamespaceID: 2, // from 1 NamespaceID: 2, // from 1
} }
can, _ := list.CanUpdate(s, usr) can, _ := project.CanUpdate(s, usr)
assert.False(t, can) // namespace is not writeable by us assert.False(t, can) // namespace is not writeable by us
_ = s.Close() _ = s.Close()
}) })
@ -232,32 +232,32 @@ func TestList_CreateOrUpdate(t *testing.T) {
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
s := db.NewSession() s := db.NewSession()
list := List{ project := Project{
ID: 6, ID: 6,
Title: "Test6", Title: "Test6",
Description: "Lorem Ipsum", Description: "Lorem Ipsum",
NamespaceID: -1, NamespaceID: -1,
} }
err := list.Update(s, usr) err := project.Update(s, usr)
assert.Error(t, err) assert.Error(t, err)
assert.True(t, IsErrListCannotBelongToAPseudoNamespace(err)) assert.True(t, IsErrProjectCannotBelongToAPseudoNamespace(err))
}) })
}) })
}) })
} }
func TestList_Delete(t *testing.T) { func TestProject_Delete(t *testing.T) {
t.Run("normal", func(t *testing.T) { t.Run("normal", func(t *testing.T) {
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
s := db.NewSession() s := db.NewSession()
list := List{ project := Project{
ID: 1, ID: 1,
} }
err := list.Delete(s, &user.User{ID: 1}) err := project.Delete(s, &user.User{ID: 1})
assert.NoError(t, err) assert.NoError(t, err)
err = s.Commit() err = s.Commit()
assert.NoError(t, err) assert.NoError(t, err)
db.AssertMissing(t, "lists", map[string]interface{}{ db.AssertMissing(t, "projects", map[string]interface{}{
"id": 1, "id": 1,
}) })
}) })
@ -265,14 +265,14 @@ func TestList_Delete(t *testing.T) {
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
files.InitTestFileFixtures(t) files.InitTestFileFixtures(t)
s := db.NewSession() s := db.NewSession()
list := List{ project := Project{
ID: 25, ID: 25,
} }
err := list.Delete(s, &user.User{ID: 6}) err := project.Delete(s, &user.User{ID: 6})
assert.NoError(t, err) assert.NoError(t, err)
err = s.Commit() err = s.Commit()
assert.NoError(t, err) assert.NoError(t, err)
db.AssertMissing(t, "lists", map[string]interface{}{ db.AssertMissing(t, "projects", map[string]interface{}{
"id": 25, "id": 25,
}) })
db.AssertMissing(t, "files", map[string]interface{}{ db.AssertMissing(t, "files", map[string]interface{}{
@ -281,77 +281,77 @@ func TestList_Delete(t *testing.T) {
}) })
} }
func TestList_DeleteBackgroundFileIfExists(t *testing.T) { func TestProject_DeleteBackgroundFileIfExists(t *testing.T) {
t.Run("list with background", func(t *testing.T) { t.Run("project with background", func(t *testing.T) {
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
files.InitTestFileFixtures(t) files.InitTestFileFixtures(t)
s := db.NewSession() s := db.NewSession()
file := &files.File{ID: 1} file := &files.File{ID: 1}
list := List{ project := Project{
ID: 1, ID: 1,
BackgroundFileID: file.ID, BackgroundFileID: file.ID,
} }
err := SetListBackground(s, list.ID, file, "") err := SetProjectBackground(s, project.ID, file, "")
assert.NoError(t, err) assert.NoError(t, err)
err = list.DeleteBackgroundFileIfExists() err = project.DeleteBackgroundFileIfExists()
assert.NoError(t, err) assert.NoError(t, err)
}) })
t.Run("list with invalid background", func(t *testing.T) { t.Run("project with invalid background", func(t *testing.T) {
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
files.InitTestFileFixtures(t) files.InitTestFileFixtures(t)
s := db.NewSession() s := db.NewSession()
file := &files.File{ID: 9999} file := &files.File{ID: 9999}
list := List{ project := Project{
ID: 1, ID: 1,
BackgroundFileID: file.ID, BackgroundFileID: file.ID,
} }
err := SetListBackground(s, list.ID, file, "") err := SetProjectBackground(s, project.ID, file, "")
assert.NoError(t, err) assert.NoError(t, err)
err = list.DeleteBackgroundFileIfExists() err = project.DeleteBackgroundFileIfExists()
assert.Error(t, err) assert.Error(t, err)
assert.True(t, files.IsErrFileDoesNotExist(err)) assert.True(t, files.IsErrFileDoesNotExist(err))
}) })
t.Run("list without background", func(t *testing.T) { t.Run("project without background", func(t *testing.T) {
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
files.InitTestFileFixtures(t) files.InitTestFileFixtures(t)
list := List{ID: 1} project := Project{ID: 1}
err := list.DeleteBackgroundFileIfExists() err := project.DeleteBackgroundFileIfExists()
assert.NoError(t, err) assert.NoError(t, err)
}) })
} }
func TestList_ReadAll(t *testing.T) { func TestProject_ReadAll(t *testing.T) {
t.Run("all in namespace", func(t *testing.T) { t.Run("all in namespace", func(t *testing.T) {
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
s := db.NewSession() s := db.NewSession()
// Get all lists for our namespace // Get all projects for our namespace
lists, err := GetListsByNamespaceID(s, 1, &user.User{}) projects, err := GetProjectsByNamespaceID(s, 1, &user.User{})
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, len(lists), 2) assert.Equal(t, len(projects), 2)
_ = s.Close() _ = s.Close()
}) })
t.Run("all lists for user", func(t *testing.T) { t.Run("all projects for user", func(t *testing.T) {
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
s := db.NewSession() s := db.NewSession()
u := &user.User{ID: 1} u := &user.User{ID: 1}
list := List{} project := Project{}
lists3, _, _, err := list.ReadAll(s, u, "", 1, 50) projects3, _, _, err := project.ReadAll(s, u, "", 1, 50)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, reflect.TypeOf(lists3).Kind(), reflect.Slice) assert.Equal(t, reflect.TypeOf(projects3).Kind(), reflect.Slice)
ls := lists3.([]*List) ls := projects3.([]*Project)
assert.Equal(t, 16, len(ls)) assert.Equal(t, 16, len(ls))
assert.Equal(t, int64(3), ls[0].ID) // List 3 has a position of 1 and should be sorted first assert.Equal(t, int64(3), ls[0].ID) // Project 3 has a position of 1 and should be sorted first
assert.Equal(t, int64(1), ls[1].ID) assert.Equal(t, int64(1), ls[1].ID)
assert.Equal(t, int64(4), ls[2].ID) assert.Equal(t, int64(4), ls[2].ID)
_ = s.Close() _ = s.Close()
}) })
t.Run("lists for nonexistant user", func(t *testing.T) { t.Run("projects for nonexistant user", func(t *testing.T) {
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
s := db.NewSession() s := db.NewSession()
usr := &user.User{ID: 999999} usr := &user.User{ID: 999999}
list := List{} project := Project{}
_, _, _, err := list.ReadAll(s, usr, "", 1, 50) _, _, _, err := project.ReadAll(s, usr, "", 1, 50)
assert.Error(t, err) assert.Error(t, err)
assert.True(t, user.IsErrUserDoesNotExist(err)) assert.True(t, user.IsErrUserDoesNotExist(err))
_ = s.Close() _ = s.Close()
@ -360,25 +360,25 @@ func TestList_ReadAll(t *testing.T) {
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
s := db.NewSession() s := db.NewSession()
u := &user.User{ID: 1} u := &user.User{ID: 1}
list := List{} project := Project{}
lists3, _, _, err := list.ReadAll(s, u, "TEST10", 1, 50) projects3, _, _, err := project.ReadAll(s, u, "TEST10", 1, 50)
assert.NoError(t, err) assert.NoError(t, err)
ls := lists3.([]*List) ls := projects3.([]*Project)
assert.Equal(t, 1, len(ls)) assert.Equal(t, 1, len(ls))
assert.Equal(t, int64(10), ls[0].ID) assert.Equal(t, int64(10), ls[0].ID)
_ = s.Close() _ = s.Close()
}) })
} }
func TestList_ReadOne(t *testing.T) { func TestProject_ReadOne(t *testing.T) {
t.Run("normal", func(t *testing.T) { t.Run("normal", func(t *testing.T) {
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
s := db.NewSession() s := db.NewSession()
defer s.Close() defer s.Close()
u := &user.User{ID: 1} u := &user.User{ID: 1}
l := &List{ID: 1} l := &Project{ID: 1}
can, _, err := l.CanRead(s, u) can, _, err := l.CanRead(s, u)
assert.NoError(t, err) assert.NoError(t, err)
assert.True(t, can) assert.True(t, can)
@ -392,7 +392,7 @@ func TestList_ReadOne(t *testing.T) {
defer s.Close() defer s.Close()
u := &user.User{ID: 6} u := &user.User{ID: 6}
l := &List{ID: 12} l := &Project{ID: 12}
can, _, err := l.CanRead(s, u) can, _, err := l.CanRead(s, u)
assert.NoError(t, err) assert.NoError(t, err)
assert.True(t, can) assert.True(t, can)

View File

@ -28,16 +28,16 @@ import (
"xorm.io/xorm" "xorm.io/xorm"
) )
// ListUser represents a list <-> user relation // ProjectUser represents a project <-> user relation
type ListUser struct { type ProjectUser struct {
// The unique, numeric id of this list <-> user relation. // The unique, numeric id of this project <-> user relation.
ID int64 `xorm:"bigint autoincr not null unique pk" json:"id" param:"namespace"` ID int64 `xorm:"bigint autoincr not null unique pk" json:"id" param:"namespace"`
// The username. // The username.
Username string `xorm:"-" json:"user_id" param:"user"` Username string `xorm:"-" json:"user_id" param:"user"`
// Used internally to reference the user // Used internally to reference the user
UserID int64 `xorm:"bigint not null INDEX" json:"-"` UserID int64 `xorm:"bigint not null INDEX" json:"-"`
// The list id. // The project id.
ListID int64 `xorm:"bigint not null INDEX" json:"-" param:"list"` ProjectID int64 `xorm:"bigint not null INDEX" json:"-" param:"project"`
// The right this user has. 0 = Read only, 1 = Read & Write, 2 = Admin. See the docs for more details. // The right this user has. 0 = Read only, 1 = Read & Write, 2 = Admin. See the docs for more details.
Right Right `xorm:"bigint INDEX not null default 0" json:"right" valid:"length(0|2)" maximum:"2" default:"0"` Right Right `xorm:"bigint INDEX not null default 0" json:"right" valid:"length(0|2)" maximum:"2" default:"0"`
@ -50,41 +50,41 @@ type ListUser struct {
web.Rights `xorm:"-" json:"-"` web.Rights `xorm:"-" json:"-"`
} }
// TableName is the table name for ListUser // TableName is the table name for ProjectUser
func (ListUser) TableName() string { func (ProjectUser) TableName() string {
return "users_lists" return "users_projects"
} }
// UserWithRight represents a user in combination with the right it can have on a list/namespace // UserWithRight represents a user in combination with the right it can have on a project/namespace
type UserWithRight struct { type UserWithRight struct {
user.User `xorm:"extends"` user.User `xorm:"extends"`
Right Right `json:"right"` Right Right `json:"right"`
} }
// Create creates a new list <-> user relation // Create creates a new project <-> user relation
// @Summary Add a user to a list // @Summary Add a user to a project
// @Description Gives a user access to a list. // @Description Gives a user access to a project.
// @tags sharing // @tags sharing
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Security JWTKeyAuth // @Security JWTKeyAuth
// @Param id path int true "List ID" // @Param id path int true "Project ID"
// @Param list body models.ListUser true "The user you want to add to the list." // @Param project body models.ProjectUser true "The user you want to add to the project."
// @Success 201 {object} models.ListUser "The created user<->list relation." // @Success 201 {object} models.ProjectUser "The created user<->project relation."
// @Failure 400 {object} web.HTTPError "Invalid user list object provided." // @Failure 400 {object} web.HTTPError "Invalid user project object provided."
// @Failure 404 {object} web.HTTPError "The user does not exist." // @Failure 404 {object} web.HTTPError "The user does not exist."
// @Failure 403 {object} web.HTTPError "The user does not have access to the list" // @Failure 403 {object} web.HTTPError "The user does not have access to the project"
// @Failure 500 {object} models.Message "Internal error" // @Failure 500 {object} models.Message "Internal error"
// @Router /lists/{id}/users [put] // @Router /projects/{id}/users [put]
func (lu *ListUser) Create(s *xorm.Session, a web.Auth) (err error) { func (lu *ProjectUser) Create(s *xorm.Session, a web.Auth) (err error) {
// Check if the right is valid // Check if the right is valid
if err := lu.Right.isValid(); err != nil { if err := lu.Right.isValid(); err != nil {
return err return err
} }
// Check if the list exists // Check if the project exists
l, err := GetListSimpleByID(s, lu.ListID) l, err := GetProjectSimpleByID(s, lu.ProjectID)
if err != nil { if err != nil {
return return
} }
@ -96,53 +96,53 @@ func (lu *ListUser) Create(s *xorm.Session, a web.Auth) (err error) {
} }
lu.UserID = u.ID lu.UserID = u.ID
// Check if the user already has access or is owner of that list // Check if the user already has access or is owner of that project
// We explicitly DONT check for teams here // We explicitly DONT check for teams here
if l.OwnerID == lu.UserID { if l.OwnerID == lu.UserID {
return ErrUserAlreadyHasAccess{UserID: lu.UserID, ListID: lu.ListID} return ErrUserAlreadyHasAccess{UserID: lu.UserID, ProjectID: lu.ProjectID}
} }
exist, err := s.Where("list_id = ? AND user_id = ?", lu.ListID, lu.UserID).Get(&ListUser{}) exist, err := s.Where("project_id = ? AND user_id = ?", lu.ProjectID, lu.UserID).Get(&ProjectUser{})
if err != nil { if err != nil {
return return
} }
if exist { if exist {
return ErrUserAlreadyHasAccess{UserID: lu.UserID, ListID: lu.ListID} return ErrUserAlreadyHasAccess{UserID: lu.UserID, ProjectID: lu.ProjectID}
} }
// Insert user <-> list relation // Insert user <-> project relation
_, err = s.Insert(lu) _, err = s.Insert(lu)
if err != nil { if err != nil {
return err return err
} }
err = events.Dispatch(&ListSharedWithUserEvent{ err = events.Dispatch(&ProjectSharedWithUserEvent{
List: l, Project: l,
User: u, User: u,
Doer: a, Doer: a,
}) })
if err != nil { if err != nil {
return err return err
} }
err = updateListLastUpdated(s, l) err = updateProjectLastUpdated(s, l)
return return
} }
// Delete deletes a list <-> user relation // Delete deletes a project <-> user relation
// @Summary Delete a user from a list // @Summary Delete a user from a project
// @Description Delets a user from a list. The user won't have access to the list anymore. // @Description Delets a user from a project. The user won't have access to the project anymore.
// @tags sharing // @tags sharing
// @Produce json // @Produce json
// @Security JWTKeyAuth // @Security JWTKeyAuth
// @Param listID path int true "List ID" // @Param projectID path int true "Project ID"
// @Param userID path int true "User ID" // @Param userID path int true "User ID"
// @Success 200 {object} models.Message "The user was successfully removed from the list." // @Success 200 {object} models.Message "The user was successfully removed from the project."
// @Failure 403 {object} web.HTTPError "The user does not have access to the list" // @Failure 403 {object} web.HTTPError "The user does not have access to the project"
// @Failure 404 {object} web.HTTPError "user or list does not exist." // @Failure 404 {object} web.HTTPError "user or project does not exist."
// @Failure 500 {object} models.Message "Internal error" // @Failure 500 {object} models.Message "Internal error"
// @Router /lists/{listID}/users/{userID} [delete] // @Router /projects/{projectID}/users/{userID} [delete]
func (lu *ListUser) Delete(s *xorm.Session, a web.Auth) (err error) { func (lu *ProjectUser) Delete(s *xorm.Session, a web.Auth) (err error) {
// Check if the user exists // Check if the user exists
u, err := user.GetUserByUsername(s, lu.Username) u, err := user.GetUserByUsername(s, lu.Username)
@ -151,52 +151,52 @@ func (lu *ListUser) Delete(s *xorm.Session, a web.Auth) (err error) {
} }
lu.UserID = u.ID lu.UserID = u.ID
// Check if the user has access to the list // Check if the user has access to the project
has, err := s. has, err := s.
Where("user_id = ? AND list_id = ?", lu.UserID, lu.ListID). Where("user_id = ? AND project_id = ?", lu.UserID, lu.ProjectID).
Get(&ListUser{}) Get(&ProjectUser{})
if err != nil { if err != nil {
return return
} }
if !has { if !has {
return ErrUserDoesNotHaveAccessToList{ListID: lu.ListID, UserID: lu.UserID} return ErrUserDoesNotHaveAccessToProject{ProjectID: lu.ProjectID, UserID: lu.UserID}
} }
_, err = s. _, err = s.
Where("user_id = ? AND list_id = ?", lu.UserID, lu.ListID). Where("user_id = ? AND project_id = ?", lu.UserID, lu.ProjectID).
Delete(&ListUser{}) Delete(&ProjectUser{})
if err != nil { if err != nil {
return err return err
} }
err = updateListLastUpdated(s, &List{ID: lu.ListID}) err = updateProjectLastUpdated(s, &Project{ID: lu.ProjectID})
return return
} }
// ReadAll gets all users who have access to a list // ReadAll gets all users who have access to a project
// @Summary Get users on a list // @Summary Get users on a project
// @Description Returns a list with all users which have access on a given list. // @Description Returns a project with all users which have access on a given project.
// @tags sharing // @tags sharing
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param id path int true "List ID" // @Param id path int true "Project ID"
// @Param page query int false "The page number. Used for pagination. If not provided, the first page of results is returned." // @Param page query int false "The page number. Used for pagination. If not provided, the first page of results is returned."
// @Param per_page query int false "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page." // @Param per_page query int false "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page."
// @Param s query string false "Search users by its name." // @Param s query string false "Search users by its name."
// @Security JWTKeyAuth // @Security JWTKeyAuth
// @Success 200 {array} models.UserWithRight "The users with the right they have." // @Success 200 {array} models.UserWithRight "The users with the right they have."
// @Failure 403 {object} web.HTTPError "No right to see the list." // @Failure 403 {object} web.HTTPError "No right to see the project."
// @Failure 500 {object} models.Message "Internal error" // @Failure 500 {object} models.Message "Internal error"
// @Router /lists/{id}/users [get] // @Router /projects/{id}/users [get]
func (lu *ListUser) ReadAll(s *xorm.Session, a web.Auth, search string, page int, perPage int) (result interface{}, resultCount int, numberOfTotalItems int64, err error) { func (lu *ProjectUser) ReadAll(s *xorm.Session, a web.Auth, search string, page int, perPage int) (result interface{}, resultCount int, numberOfTotalItems int64, err error) {
// Check if the user has access to the list // Check if the user has access to the project
l := &List{ID: lu.ListID} l := &Project{ID: lu.ProjectID}
canRead, _, err := l.CanRead(s, a) canRead, _, err := l.CanRead(s, a)
if err != nil { if err != nil {
return nil, 0, 0, err return nil, 0, 0, err
} }
if !canRead { if !canRead {
return nil, 0, 0, ErrNeedToHaveListReadAccess{UserID: a.GetID(), ListID: lu.ListID} return nil, 0, 0, ErrNeedToHaveProjectReadAccess{UserID: a.GetID(), ProjectID: lu.ProjectID}
} }
limit, start := getLimitFromPageIndex(page, perPage) limit, start := getLimitFromPageIndex(page, perPage)
@ -204,8 +204,8 @@ func (lu *ListUser) ReadAll(s *xorm.Session, a web.Auth, search string, page int
// Get all users // Get all users
all := []*UserWithRight{} all := []*UserWithRight{}
query := s. query := s.
Join("INNER", "users_lists", "user_id = users.id"). Join("INNER", "users_projects", "user_id = users.id").
Where("users_lists.list_id = ?", lu.ListID). Where("users_projects.project_id = ?", lu.ProjectID).
Where(db.ILIKE("users.username", search)) Where(db.ILIKE("users.username", search))
if limit > 0 { if limit > 0 {
query = query.Limit(limit, start) query = query.Limit(limit, start)
@ -221,30 +221,30 @@ func (lu *ListUser) ReadAll(s *xorm.Session, a web.Auth, search string, page int
} }
numberOfTotalItems, err = s. numberOfTotalItems, err = s.
Join("INNER", "users_lists", "user_id = users.id"). Join("INNER", "users_projects", "user_id = users.id").
Where("users_lists.list_id = ?", lu.ListID). Where("users_projects.project_id = ?", lu.ProjectID).
Where("users.username LIKE ?", "%"+search+"%"). Where("users.username LIKE ?", "%"+search+"%").
Count(&UserWithRight{}) Count(&UserWithRight{})
return all, len(all), numberOfTotalItems, err return all, len(all), numberOfTotalItems, err
} }
// Update updates a user <-> list relation // Update updates a user <-> project relation
// @Summary Update a user <-> list relation // @Summary Update a user <-> project relation
// @Description Update a user <-> list relation. Mostly used to update the right that user has. // @Description Update a user <-> project relation. Mostly used to update the right that user has.
// @tags sharing // @tags sharing
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param listID path int true "List ID" // @Param projectID path int true "Project ID"
// @Param userID path int true "User ID" // @Param userID path int true "User ID"
// @Param list body models.ListUser true "The user you want to update." // @Param project body models.ProjectUser true "The user you want to update."
// @Security JWTKeyAuth // @Security JWTKeyAuth
// @Success 200 {object} models.ListUser "The updated user <-> list relation." // @Success 200 {object} models.ProjectUser "The updated user <-> project relation."
// @Failure 403 {object} web.HTTPError "The user does not have admin-access to the list" // @Failure 403 {object} web.HTTPError "The user does not have admin-access to the project"
// @Failure 404 {object} web.HTTPError "User or list does not exist." // @Failure 404 {object} web.HTTPError "User or project does not exist."
// @Failure 500 {object} models.Message "Internal error" // @Failure 500 {object} models.Message "Internal error"
// @Router /lists/{listID}/users/{userID} [post] // @Router /projects/{projectID}/users/{userID} [post]
func (lu *ListUser) Update(s *xorm.Session, a web.Auth) (err error) { func (lu *ProjectUser) Update(s *xorm.Session, a web.Auth) (err error) {
// Check if the right is valid // Check if the right is valid
if err := lu.Right.isValid(); err != nil { if err := lu.Right.isValid(); err != nil {
@ -259,13 +259,13 @@ func (lu *ListUser) Update(s *xorm.Session, a web.Auth) (err error) {
lu.UserID = u.ID lu.UserID = u.ID
_, err = s. _, err = s.
Where("list_id = ? AND user_id = ?", lu.ListID, lu.UserID). Where("project_id = ? AND user_id = ?", lu.ProjectID, lu.UserID).
Cols("right"). Cols("right").
Update(lu) Update(lu)
if err != nil { if err != nil {
return err return err
} }
err = updateListLastUpdated(s, &List{ID: lu.ListID}) err = updateProjectLastUpdated(s, &Project{ID: lu.ProjectID})
return return
} }

View File

@ -21,28 +21,28 @@ import (
"xorm.io/xorm" "xorm.io/xorm"
) )
// CanCreate checks if the user can create a new user <-> list relation // CanCreate checks if the user can create a new user <-> project relation
func (lu *ListUser) CanCreate(s *xorm.Session, a web.Auth) (bool, error) { func (lu *ProjectUser) CanCreate(s *xorm.Session, a web.Auth) (bool, error) {
return lu.canDoListUser(s, a) return lu.canDoProjectUser(s, a)
} }
// CanDelete checks if the user can delete a user <-> list relation // CanDelete checks if the user can delete a user <-> project relation
func (lu *ListUser) CanDelete(s *xorm.Session, a web.Auth) (bool, error) { func (lu *ProjectUser) CanDelete(s *xorm.Session, a web.Auth) (bool, error) {
return lu.canDoListUser(s, a) return lu.canDoProjectUser(s, a)
} }
// CanUpdate checks if the user can update a user <-> list relation // CanUpdate checks if the user can update a user <-> project relation
func (lu *ListUser) CanUpdate(s *xorm.Session, a web.Auth) (bool, error) { func (lu *ProjectUser) CanUpdate(s *xorm.Session, a web.Auth) (bool, error) {
return lu.canDoListUser(s, a) return lu.canDoProjectUser(s, a)
} }
func (lu *ListUser) canDoListUser(s *xorm.Session, a web.Auth) (bool, error) { func (lu *ProjectUser) canDoProjectUser(s *xorm.Session, a web.Auth) (bool, error) {
// Link shares aren't allowed to do anything // Link shares aren't allowed to do anything
if _, is := a.(*LinkSharing); is { if _, is := a.(*LinkSharing); is {
return false, nil return false, nil
} }
// Get the list and check if the user has write access on it // Get the project and check if the user has write access on it
l := List{ID: lu.ListID} l := Project{ID: lu.ProjectID}
return l.IsAdmin(s, a) return l.IsAdmin(s, a)
} }

View File

@ -26,16 +26,16 @@ import (
"code.vikunja.io/web" "code.vikunja.io/web"
) )
func TestListUser_CanDoSomething(t *testing.T) { func TestProjectUser_CanDoSomething(t *testing.T) {
type fields struct { type fields struct {
ID int64 ID int64
UserID int64 UserID int64
ListID int64 ProjectID int64
Right Right Right Right
Created time.Time Created time.Time
Updated time.Time Updated time.Time
CRUDable web.CRUDable CRUDable web.CRUDable
Rights web.Rights Rights web.Rights
} }
type args struct { type args struct {
a web.Auth a web.Auth
@ -49,7 +49,7 @@ func TestListUser_CanDoSomething(t *testing.T) {
{ {
name: "CanDoSomething Normally", name: "CanDoSomething Normally",
fields: fields{ fields: fields{
ListID: 3, ProjectID: 3,
}, },
args: args{ args: args{
a: &user.User{ID: 3}, a: &user.User{ID: 3},
@ -57,9 +57,9 @@ func TestListUser_CanDoSomething(t *testing.T) {
want: map[string]bool{"CanCreate": true, "CanDelete": true, "CanUpdate": true}, want: map[string]bool{"CanCreate": true, "CanDelete": true, "CanUpdate": true},
}, },
{ {
name: "CanDoSomething for a nonexistant list", name: "CanDoSomething for a nonexistant project",
fields: fields{ fields: fields{
ListID: 300, ProjectID: 300,
}, },
args: args{ args: args{
a: &user.User{ID: 3}, a: &user.User{ID: 3},
@ -69,7 +69,7 @@ func TestListUser_CanDoSomething(t *testing.T) {
{ {
name: "CanDoSomething where the user does not have the rights", name: "CanDoSomething where the user does not have the rights",
fields: fields{ fields: fields{
ListID: 3, ProjectID: 3,
}, },
args: args{ args: args{
a: &user.User{ID: 4}, a: &user.User{ID: 4},
@ -82,24 +82,24 @@ func TestListUser_CanDoSomething(t *testing.T) {
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
s := db.NewSession() s := db.NewSession()
lu := &ListUser{ lu := &ProjectUser{
ID: tt.fields.ID, ID: tt.fields.ID,
UserID: tt.fields.UserID, UserID: tt.fields.UserID,
ListID: tt.fields.ListID, ProjectID: tt.fields.ProjectID,
Right: tt.fields.Right, Right: tt.fields.Right,
Created: tt.fields.Created, Created: tt.fields.Created,
Updated: tt.fields.Updated, Updated: tt.fields.Updated,
CRUDable: tt.fields.CRUDable, CRUDable: tt.fields.CRUDable,
Rights: tt.fields.Rights, Rights: tt.fields.Rights,
} }
if got, _ := lu.CanCreate(s, tt.args.a); got != tt.want["CanCreate"] { if got, _ := lu.CanCreate(s, tt.args.a); got != tt.want["CanCreate"] {
t.Errorf("ListUser.CanCreate() = %v, want %v", got, tt.want["CanCreate"]) t.Errorf("ProjectUser.CanCreate() = %v, want %v", got, tt.want["CanCreate"])
} }
if got, _ := lu.CanDelete(s, tt.args.a); got != tt.want["CanDelete"] { if got, _ := lu.CanDelete(s, tt.args.a); got != tt.want["CanDelete"] {
t.Errorf("ListUser.CanDelete() = %v, want %v", got, tt.want["CanDelete"]) t.Errorf("ProjectUser.CanDelete() = %v, want %v", got, tt.want["CanDelete"])
} }
if got, _ := lu.CanUpdate(s, tt.args.a); got != tt.want["CanUpdate"] { if got, _ := lu.CanUpdate(s, tt.args.a); got != tt.want["CanUpdate"] {
t.Errorf("ListUser.CanUpdate() = %v, want %v", got, tt.want["CanUpdate"]) t.Errorf("ProjectUser.CanUpdate() = %v, want %v", got, tt.want["CanUpdate"])
} }
_ = s.Close() _ = s.Close()
}) })

View File

@ -29,17 +29,17 @@ import (
"gopkg.in/d4l3k/messagediff.v1" "gopkg.in/d4l3k/messagediff.v1"
) )
func TestListUser_Create(t *testing.T) { func TestProjectUser_Create(t *testing.T) {
type fields struct { type fields struct {
ID int64 ID int64
UserID int64 UserID int64
Username string Username string
ListID int64 ProjectID int64
Right Right Right Right
Created time.Time Created time.Time
Updated time.Time Updated time.Time
CRUDable web.CRUDable CRUDable web.CRUDable
Rights web.Rights Rights web.Rights
} }
type args struct { type args struct {
a web.Auth a web.Auth
@ -52,54 +52,54 @@ func TestListUser_Create(t *testing.T) {
errType func(err error) bool errType func(err error) bool
}{ }{
{ {
name: "ListUsers Create normally", name: "ProjectUsers Create normally",
fields: fields{ fields: fields{
Username: "user1", Username: "user1",
ListID: 2, ProjectID: 2,
}, },
}, },
{ {
name: "ListUsers Create for duplicate", name: "ProjectUsers Create for duplicate",
fields: fields{ fields: fields{
Username: "user1", Username: "user1",
ListID: 3, ProjectID: 3,
}, },
wantErr: true, wantErr: true,
errType: IsErrUserAlreadyHasAccess, errType: IsErrUserAlreadyHasAccess,
}, },
{ {
name: "ListUsers Create with invalid right", name: "ProjectUsers Create with invalid right",
fields: fields{ fields: fields{
Username: "user1", Username: "user1",
ListID: 2, ProjectID: 2,
Right: 500, Right: 500,
}, },
wantErr: true, wantErr: true,
errType: IsErrInvalidRight, errType: IsErrInvalidRight,
}, },
{ {
name: "ListUsers Create with inexisting list", name: "ProjectUsers Create with inexisting project",
fields: fields{ fields: fields{
Username: "user1", Username: "user1",
ListID: 2000, ProjectID: 2000,
}, },
wantErr: true, wantErr: true,
errType: IsErrListDoesNotExist, errType: IsErrProjectDoesNotExist,
}, },
{ {
name: "ListUsers Create with inexisting user", name: "ProjectUsers Create with inexisting user",
fields: fields{ fields: fields{
Username: "user500", Username: "user500",
ListID: 2, ProjectID: 2,
}, },
wantErr: true, wantErr: true,
errType: user.IsErrUserDoesNotExist, errType: user.IsErrUserDoesNotExist,
}, },
{ {
name: "ListUsers Create with the owner as shared user", name: "ProjectUsers Create with the owner as shared user",
fields: fields{ fields: fields{
Username: "user1", Username: "user1",
ListID: 1, ProjectID: 1,
}, },
wantErr: true, wantErr: true,
errType: IsErrUserAlreadyHasAccess, errType: IsErrUserAlreadyHasAccess,
@ -110,39 +110,39 @@ func TestListUser_Create(t *testing.T) {
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
s := db.NewSession() s := db.NewSession()
ul := &ListUser{ ul := &ProjectUser{
ID: tt.fields.ID, ID: tt.fields.ID,
UserID: tt.fields.UserID, UserID: tt.fields.UserID,
Username: tt.fields.Username, Username: tt.fields.Username,
ListID: tt.fields.ListID, ProjectID: tt.fields.ProjectID,
Right: tt.fields.Right, Right: tt.fields.Right,
Created: tt.fields.Created, Created: tt.fields.Created,
Updated: tt.fields.Updated, Updated: tt.fields.Updated,
CRUDable: tt.fields.CRUDable, CRUDable: tt.fields.CRUDable,
Rights: tt.fields.Rights, Rights: tt.fields.Rights,
} }
err := ul.Create(s, tt.args.a) err := ul.Create(s, tt.args.a)
if (err != nil) != tt.wantErr { if (err != nil) != tt.wantErr {
t.Errorf("ListUser.Create() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("ProjectUser.Create() error = %v, wantErr %v", err, tt.wantErr)
} }
if (err != nil) && tt.wantErr && !tt.errType(err) { if (err != nil) && tt.wantErr && !tt.errType(err) {
t.Errorf("ListUser.Create() Wrong error type! Error = %v, want = %v", err, runtime.FuncForPC(reflect.ValueOf(tt.errType).Pointer()).Name()) t.Errorf("ProjectUser.Create() Wrong error type! Error = %v, want = %v", err, runtime.FuncForPC(reflect.ValueOf(tt.errType).Pointer()).Name())
} }
err = s.Commit() err = s.Commit()
assert.NoError(t, err) assert.NoError(t, err)
if !tt.wantErr { if !tt.wantErr {
db.AssertExists(t, "users_lists", map[string]interface{}{ db.AssertExists(t, "users_projects", map[string]interface{}{
"user_id": ul.UserID, "user_id": ul.UserID,
"list_id": tt.fields.ListID, "project_id": tt.fields.ProjectID,
}, false) }, false)
} }
}) })
} }
} }
func TestListUser_ReadAll(t *testing.T) { func TestProjectUser_ReadAll(t *testing.T) {
user1Read := &UserWithRight{ user1Read := &UserWithRight{
User: user.User{ User: user.User{
ID: 1, ID: 1,
@ -173,14 +173,14 @@ func TestListUser_ReadAll(t *testing.T) {
} }
type fields struct { type fields struct {
ID int64 ID int64
UserID int64 UserID int64
ListID int64 ProjectID int64
Right Right Right Right
Created time.Time Created time.Time
Updated time.Time Updated time.Time
CRUDable web.CRUDable CRUDable web.CRUDable
Rights web.Rights Rights web.Rights
} }
type args struct { type args struct {
search string search string
@ -198,7 +198,7 @@ func TestListUser_ReadAll(t *testing.T) {
{ {
name: "Test readall normal", name: "Test readall normal",
fields: fields{ fields: fields{
ListID: 3, ProjectID: 3,
}, },
args: args{ args: args{
a: &user.User{ID: 3}, a: &user.User{ID: 3},
@ -209,20 +209,20 @@ func TestListUser_ReadAll(t *testing.T) {
}, },
}, },
{ {
name: "Test ReadAll by a user who does not have access to the list", name: "Test ReadAll by a user who does not have access to the project",
fields: fields{ fields: fields{
ListID: 3, ProjectID: 3,
}, },
args: args{ args: args{
a: &user.User{ID: 4}, a: &user.User{ID: 4},
}, },
wantErr: true, wantErr: true,
errType: IsErrNeedToHaveListReadAccess, errType: IsErrNeedToHaveProjectReadAccess,
}, },
{ {
name: "Search", name: "Search",
fields: fields{ fields: fields{
ListID: 3, ProjectID: 3,
}, },
args: args{ args: args{
a: &user.User{ID: 3}, a: &user.User{ID: 3},
@ -238,41 +238,41 @@ func TestListUser_ReadAll(t *testing.T) {
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
s := db.NewSession() s := db.NewSession()
ul := &ListUser{ ul := &ProjectUser{
ID: tt.fields.ID, ID: tt.fields.ID,
UserID: tt.fields.UserID, UserID: tt.fields.UserID,
ListID: tt.fields.ListID, ProjectID: tt.fields.ProjectID,
Right: tt.fields.Right, Right: tt.fields.Right,
Created: tt.fields.Created, Created: tt.fields.Created,
Updated: tt.fields.Updated, Updated: tt.fields.Updated,
CRUDable: tt.fields.CRUDable, CRUDable: tt.fields.CRUDable,
Rights: tt.fields.Rights, Rights: tt.fields.Rights,
} }
got, _, _, err := ul.ReadAll(s, tt.args.a, tt.args.search, tt.args.page, 50) got, _, _, err := ul.ReadAll(s, tt.args.a, tt.args.search, tt.args.page, 50)
if (err != nil) != tt.wantErr { if (err != nil) != tt.wantErr {
t.Errorf("ListUser.ReadAll() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("ProjectUser.ReadAll() error = %v, wantErr %v", err, tt.wantErr)
} }
if (err != nil) && tt.wantErr && !tt.errType(err) { if (err != nil) && tt.wantErr && !tt.errType(err) {
t.Errorf("ListUser.ReadAll() Wrong error type! Error = %v, want = %v", err, runtime.FuncForPC(reflect.ValueOf(tt.errType).Pointer()).Name()) t.Errorf("ProjectUser.ReadAll() Wrong error type! Error = %v, want = %v", err, runtime.FuncForPC(reflect.ValueOf(tt.errType).Pointer()).Name())
} }
if diff, equal := messagediff.PrettyDiff(got, tt.want); !equal { if diff, equal := messagediff.PrettyDiff(got, tt.want); !equal {
t.Errorf("ListUser.ReadAll() = %v, want %v, diff: %v", got, tt.want, diff) t.Errorf("ProjectUser.ReadAll() = %v, want %v, diff: %v", got, tt.want, diff)
} }
_ = s.Close() _ = s.Close()
}) })
} }
} }
func TestListUser_Update(t *testing.T) { func TestProjectUser_Update(t *testing.T) {
type fields struct { type fields struct {
ID int64 ID int64
Username string Username string
ListID int64 ProjectID int64
Right Right Right Right
Created time.Time Created time.Time
Updated time.Time Updated time.Time
CRUDable web.CRUDable CRUDable web.CRUDable
Rights web.Rights Rights web.Rights
} }
tests := []struct { tests := []struct {
name string name string
@ -283,33 +283,33 @@ func TestListUser_Update(t *testing.T) {
{ {
name: "Test Update Normally", name: "Test Update Normally",
fields: fields{ fields: fields{
ListID: 3, ProjectID: 3,
Username: "user1", Username: "user1",
Right: RightAdmin, Right: RightAdmin,
}, },
}, },
{ {
name: "Test Update to write", name: "Test Update to write",
fields: fields{ fields: fields{
ListID: 3, ProjectID: 3,
Username: "user1", Username: "user1",
Right: RightWrite, Right: RightWrite,
}, },
}, },
{ {
name: "Test Update to Read", name: "Test Update to Read",
fields: fields{ fields: fields{
ListID: 3, ProjectID: 3,
Username: "user1", Username: "user1",
Right: RightRead, Right: RightRead,
}, },
}, },
{ {
name: "Test Update with invalid right", name: "Test Update with invalid right",
fields: fields{ fields: fields{
ListID: 3, ProjectID: 3,
Username: "user1", Username: "user1",
Right: 500, Right: 500,
}, },
wantErr: true, wantErr: true,
errType: IsErrInvalidRight, errType: IsErrInvalidRight,
@ -320,49 +320,49 @@ func TestListUser_Update(t *testing.T) {
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
s := db.NewSession() s := db.NewSession()
lu := &ListUser{ lu := &ProjectUser{
ID: tt.fields.ID, ID: tt.fields.ID,
Username: tt.fields.Username, Username: tt.fields.Username,
ListID: tt.fields.ListID, ProjectID: tt.fields.ProjectID,
Right: tt.fields.Right, Right: tt.fields.Right,
Created: tt.fields.Created, Created: tt.fields.Created,
Updated: tt.fields.Updated, Updated: tt.fields.Updated,
CRUDable: tt.fields.CRUDable, CRUDable: tt.fields.CRUDable,
Rights: tt.fields.Rights, Rights: tt.fields.Rights,
} }
err := lu.Update(s, &user.User{ID: 1}) err := lu.Update(s, &user.User{ID: 1})
if (err != nil) != tt.wantErr { if (err != nil) != tt.wantErr {
t.Errorf("ListUser.Update() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("ProjectUser.Update() error = %v, wantErr %v", err, tt.wantErr)
} }
if (err != nil) && tt.wantErr && !tt.errType(err) { if (err != nil) && tt.wantErr && !tt.errType(err) {
t.Errorf("ListUser.Update() Wrong error type! Error = %v, want = %v", err, runtime.FuncForPC(reflect.ValueOf(tt.errType).Pointer()).Name()) t.Errorf("ProjectUser.Update() Wrong error type! Error = %v, want = %v", err, runtime.FuncForPC(reflect.ValueOf(tt.errType).Pointer()).Name())
} }
err = s.Commit() err = s.Commit()
assert.NoError(t, err) assert.NoError(t, err)
if !tt.wantErr { if !tt.wantErr {
db.AssertExists(t, "users_lists", map[string]interface{}{ db.AssertExists(t, "users_projects", map[string]interface{}{
"list_id": tt.fields.ListID, "project_id": tt.fields.ProjectID,
"user_id": lu.UserID, "user_id": lu.UserID,
"right": tt.fields.Right, "right": tt.fields.Right,
}, false) }, false)
} }
}) })
} }
} }
func TestListUser_Delete(t *testing.T) { func TestProjectUser_Delete(t *testing.T) {
type fields struct { type fields struct {
ID int64 ID int64
Username string Username string
UserID int64 UserID int64
ListID int64 ProjectID int64
Right Right Right Right
Created time.Time Created time.Time
Updated time.Time Updated time.Time
CRUDable web.CRUDable CRUDable web.CRUDable
Rights web.Rights Rights web.Rights
} }
tests := []struct { tests := []struct {
name string name string
@ -373,8 +373,8 @@ func TestListUser_Delete(t *testing.T) {
{ {
name: "Try deleting some unexistant user", name: "Try deleting some unexistant user",
fields: fields{ fields: fields{
Username: "user1000", Username: "user1000",
ListID: 2, ProjectID: 2,
}, },
wantErr: true, wantErr: true,
errType: user.IsErrUserDoesNotExist, errType: user.IsErrUserDoesNotExist,
@ -382,18 +382,18 @@ func TestListUser_Delete(t *testing.T) {
{ {
name: "Try deleting a user which does not has access but exists", name: "Try deleting a user which does not has access but exists",
fields: fields{ fields: fields{
Username: "user1", Username: "user1",
ListID: 4, ProjectID: 4,
}, },
wantErr: true, wantErr: true,
errType: IsErrUserDoesNotHaveAccessToList, errType: IsErrUserDoesNotHaveAccessToProject,
}, },
{ {
name: "Try deleting normally", name: "Try deleting normally",
fields: fields{ fields: fields{
Username: "user1", Username: "user1",
UserID: 1, UserID: 1,
ListID: 3, ProjectID: 3,
}, },
}, },
} }
@ -402,31 +402,31 @@ func TestListUser_Delete(t *testing.T) {
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
s := db.NewSession() s := db.NewSession()
lu := &ListUser{ lu := &ProjectUser{
ID: tt.fields.ID, ID: tt.fields.ID,
Username: tt.fields.Username, Username: tt.fields.Username,
ListID: tt.fields.ListID, ProjectID: tt.fields.ProjectID,
Right: tt.fields.Right, Right: tt.fields.Right,
Created: tt.fields.Created, Created: tt.fields.Created,
Updated: tt.fields.Updated, Updated: tt.fields.Updated,
CRUDable: tt.fields.CRUDable, CRUDable: tt.fields.CRUDable,
Rights: tt.fields.Rights, Rights: tt.fields.Rights,
} }
err := lu.Delete(s, &user.User{ID: 1}) err := lu.Delete(s, &user.User{ID: 1})
if (err != nil) != tt.wantErr { if (err != nil) != tt.wantErr {
t.Errorf("ListUser.Delete() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("ProjectUser.Delete() error = %v, wantErr %v", err, tt.wantErr)
} }
if (err != nil) && tt.wantErr && !tt.errType(err) { if (err != nil) && tt.wantErr && !tt.errType(err) {
t.Errorf("ListUser.Delete() Wrong error type! Error = %v, want = %v", err, runtime.FuncForPC(reflect.ValueOf(tt.errType).Pointer()).Name()) t.Errorf("ProjectUser.Delete() Wrong error type! Error = %v, want = %v", err, runtime.FuncForPC(reflect.ValueOf(tt.errType).Pointer()).Name())
} }
err = s.Commit() err = s.Commit()
assert.NoError(t, err) assert.NoError(t, err)
if !tt.wantErr { if !tt.wantErr {
db.AssertMissing(t, "users_lists", map[string]interface{}{ db.AssertMissing(t, "users_projects", map[string]interface{}{
"user_id": tt.fields.UserID, "user_id": tt.fields.UserID,
"list_id": tt.fields.ListID, "project_id": tt.fields.ProjectID,
}) })
} }
}) })

View File

@ -33,8 +33,8 @@ import (
// RegisterListeners registers all event listeners // RegisterListeners registers all event listeners
func RegisterListeners() { func RegisterListeners() {
events.RegisterListener((&ListCreatedEvent{}).Name(), &IncreaseListCounter{}) events.RegisterListener((&ProjectCreatedEvent{}).Name(), &IncreaseProjectCounter{})
events.RegisterListener((&ListDeletedEvent{}).Name(), &DecreaseListCounter{}) events.RegisterListener((&ProjectDeletedEvent{}).Name(), &DecreaseProjectCounter{})
events.RegisterListener((&NamespaceCreatedEvent{}).Name(), &IncreaseNamespaceCounter{}) events.RegisterListener((&NamespaceCreatedEvent{}).Name(), &IncreaseNamespaceCounter{})
events.RegisterListener((&NamespaceDeletedEvent{}).Name(), &DecreaseNamespaceCounter{}) events.RegisterListener((&NamespaceDeletedEvent{}).Name(), &DecreaseNamespaceCounter{})
events.RegisterListener((&TaskCreatedEvent{}).Name(), &IncreaseTaskCounter{}) events.RegisterListener((&TaskCreatedEvent{}).Name(), &IncreaseTaskCounter{})
@ -44,7 +44,7 @@ func RegisterListeners() {
events.RegisterListener((&TaskCommentCreatedEvent{}).Name(), &SendTaskCommentNotification{}) events.RegisterListener((&TaskCommentCreatedEvent{}).Name(), &SendTaskCommentNotification{})
events.RegisterListener((&TaskAssigneeCreatedEvent{}).Name(), &SendTaskAssignedNotification{}) events.RegisterListener((&TaskAssigneeCreatedEvent{}).Name(), &SendTaskAssignedNotification{})
events.RegisterListener((&TaskDeletedEvent{}).Name(), &SendTaskDeletedNotification{}) events.RegisterListener((&TaskDeletedEvent{}).Name(), &SendTaskDeletedNotification{})
events.RegisterListener((&ListCreatedEvent{}).Name(), &SendListCreatedNotification{}) events.RegisterListener((&ProjectCreatedEvent{}).Name(), &SendProjectCreatedNotification{})
events.RegisterListener((&TaskAssigneeCreatedEvent{}).Name(), &SubscribeAssigneeToTask{}) events.RegisterListener((&TaskAssigneeCreatedEvent{}).Name(), &SubscribeAssigneeToTask{})
events.RegisterListener((&TeamMemberAddedEvent{}).Name(), &SendTeamMemberAddedNotification{}) events.RegisterListener((&TeamMemberAddedEvent{}).Name(), &SendTeamMemberAddedNotification{})
events.RegisterListener((&TaskCommentUpdatedEvent{}).Name(), &HandleTaskCommentEditMentions{}) events.RegisterListener((&TaskCommentUpdatedEvent{}).Name(), &HandleTaskCommentEditMentions{})
@ -471,42 +471,42 @@ func (s *HandleTaskUpdateLastUpdated) Handle(msg *message.Message) (err error) {
} }
/////// ///////
// List Event Listeners // Project Event Listeners
type IncreaseListCounter struct { type IncreaseProjectCounter struct {
} }
func (s *IncreaseListCounter) Name() string { func (s *IncreaseProjectCounter) Name() string {
return "list.counter.increase" return "project.counter.increase"
} }
func (s *IncreaseListCounter) Handle(msg *message.Message) (err error) { func (s *IncreaseProjectCounter) Handle(msg *message.Message) (err error) {
return keyvalue.IncrBy(metrics.ListCountKey, 1) return keyvalue.IncrBy(metrics.ProjectCountKey, 1)
} }
type DecreaseListCounter struct { type DecreaseProjectCounter struct {
} }
func (s *DecreaseListCounter) Name() string { func (s *DecreaseProjectCounter) Name() string {
return "list.counter.decrease" return "project.counter.decrease"
} }
func (s *DecreaseListCounter) Handle(msg *message.Message) (err error) { func (s *DecreaseProjectCounter) Handle(msg *message.Message) (err error) {
return keyvalue.DecrBy(metrics.ListCountKey, 1) return keyvalue.DecrBy(metrics.ProjectCountKey, 1)
} }
// SendListCreatedNotification represents a listener // SendProjectCreatedNotification represents a listener
type SendListCreatedNotification struct { type SendProjectCreatedNotification struct {
} }
// Name defines the name for the SendListCreatedNotification listener // Name defines the name for the SendProjectCreatedNotification listener
func (s *SendListCreatedNotification) Name() string { func (s *SendProjectCreatedNotification) Name() string {
return "send.list.created.notification" return "send.project.created.notification"
} }
// Handle is executed when the event SendListCreatedNotification listens on is fired // Handle is executed when the event SendProjectCreatedNotification listens on is fired
func (s *SendListCreatedNotification) Handle(msg *message.Message) (err error) { func (s *SendProjectCreatedNotification) Handle(msg *message.Message) (err error) {
event := &ListCreatedEvent{} event := &ProjectCreatedEvent{}
err = json.Unmarshal(msg.Payload, event) err = json.Unmarshal(msg.Payload, event)
if err != nil { if err != nil {
return err return err
@ -515,21 +515,21 @@ func (s *SendListCreatedNotification) Handle(msg *message.Message) (err error) {
sess := db.NewSession() sess := db.NewSession()
defer sess.Close() defer sess.Close()
subscribers, err := getSubscribersForEntity(sess, SubscriptionEntityList, event.List.ID) subscribers, err := getSubscribersForEntity(sess, SubscriptionEntityProject, event.Project.ID)
if err != nil { if err != nil {
return err return err
} }
log.Debugf("Sending list created notifications to %d subscribers for list %d", len(subscribers), event.List.ID) log.Debugf("Sending project created notifications to %d subscribers for project %d", len(subscribers), event.Project.ID)
for _, subscriber := range subscribers { for _, subscriber := range subscribers {
if subscriber.UserID == event.Doer.ID { if subscriber.UserID == event.Doer.ID {
continue continue
} }
n := &ListCreatedNotification{ n := &ProjectCreatedNotification{
Doer: event.Doer, Doer: event.Doer,
List: event.List, Project: event.Project,
} }
err = notifications.Notify(subscriber.User, n) err = notifications.Notify(subscriber.User, n)
if err != nil { if err != nil {

View File

@ -103,7 +103,7 @@ func TestSendingMentionNotification(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
tc := &TaskComment{ tc := &TaskComment{
Comment: "Lorem Ipsum @user1 @user2 @user3 @user4 @user5 @user6", Comment: "Lorem Ipsum @user1 @user2 @user3 @user4 @user5 @user6",
TaskID: 32, // user2 has access to the list that task belongs to TaskID: 32, // user2 has access to the project that task belongs to
} }
err = tc.Create(s, u) err = tc.Create(s, u)
assert.NoError(t, err) assert.NoError(t, err)
@ -156,7 +156,7 @@ func TestSendingMentionNotification(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
tc := &TaskComment{ tc := &TaskComment{
Comment: "Lorem Ipsum @user2", Comment: "Lorem Ipsum @user2",
TaskID: 32, // user2 has access to the list that task belongs to TaskID: 32, // user2 has access to the project that task belongs to
} }
err = tc.Create(s, u) err = tc.Create(s, u)
assert.NoError(t, err) assert.NoError(t, err)

View File

@ -39,14 +39,14 @@ var (
// GetTables returns all structs which are also a table. // GetTables returns all structs which are also a table.
func GetTables() []interface{} { func GetTables() []interface{} {
return []interface{}{ return []interface{}{
&List{}, &Project{},
&Task{}, &Task{},
&Team{}, &Team{},
&TeamMember{}, &TeamMember{},
&TeamList{}, &TeamProject{},
&TeamNamespace{}, &TeamNamespace{},
&Namespace{}, &Namespace{},
&ListUser{}, &ProjectUser{},
&NamespaceUser{}, &NamespaceUser{},
&TaskAssginee{}, &TaskAssginee{},
&Label{}, &Label{},

View File

@ -61,27 +61,27 @@ type Namespace struct {
// A timestamp when this namespace was last updated. You cannot change this value. // A timestamp when this namespace was last updated. You cannot change this value.
Updated time.Time `xorm:"updated not null" json:"updated"` Updated time.Time `xorm:"updated not null" json:"updated"`
// If set to true, will only return the namespaces, not their lists. // If set to true, will only return the namespaces, not their projects.
NamespacesOnly bool `xorm:"-" json:"-" query:"namespaces_only"` NamespacesOnly bool `xorm:"-" json:"-" query:"namespaces_only"`
web.CRUDable `xorm:"-" json:"-"` web.CRUDable `xorm:"-" json:"-"`
web.Rights `xorm:"-" json:"-"` web.Rights `xorm:"-" json:"-"`
} }
// SharedListsPseudoNamespace is a pseudo namespace used to hold shared lists // SharedProjectsPseudoNamespace is a pseudo namespace used to hold shared projects
var SharedListsPseudoNamespace = Namespace{ var SharedProjectsPseudoNamespace = Namespace{
ID: -1, ID: -1,
Title: "Shared Lists", Title: "Shared Projects",
Description: "Lists of other users shared with you via teams or directly.", Description: "Projects of other users shared with you via teams or directly.",
Created: time.Now(), Created: time.Now(),
Updated: time.Now(), Updated: time.Now(),
} }
// FavoritesPseudoNamespace is a pseudo namespace used to hold favorited lists and tasks // FavoritesPseudoNamespace is a pseudo namespace used to hold favorited projects and tasks
var FavoritesPseudoNamespace = Namespace{ var FavoritesPseudoNamespace = Namespace{
ID: -2, ID: -2,
Title: "Favorites", Title: "Favorites",
Description: "Favorite lists and tasks.", Description: "Favorite projects and tasks.",
Created: time.Now(), Created: time.Now(),
Updated: time.Now(), Updated: time.Now(),
} }
@ -106,9 +106,9 @@ func getNamespaceSimpleByID(s *xorm.Session, id int64) (namespace *Namespace, er
return nil, ErrNamespaceDoesNotExist{ID: id} return nil, ErrNamespaceDoesNotExist{ID: id}
} }
// Get the namesapce with shared lists // Get the namesapce with shared projects
if id == -1 { if id == -1 {
return &SharedListsPseudoNamespace, nil return &SharedProjectsPseudoNamespace, nil
} }
if id == FavoritesPseudoNamespace.ID { if id == FavoritesPseudoNamespace.ID {
@ -181,24 +181,24 @@ func (n *Namespace) ReadOne(s *xorm.Session, a web.Auth) (err error) {
return return
} }
// NamespaceWithLists represents a namespace with list meta informations // NamespaceWithProjects represents a namespace with project meta informations
type NamespaceWithLists struct { type NamespaceWithProjects struct {
Namespace `xorm:"extends"` Namespace `xorm:"extends"`
Lists []*List `xorm:"-" json:"lists"` Projects []*Project `xorm:"-" json:"projects"`
} }
type NamespaceWithListsAndTasks struct { type NamespaceWithProjectsAndTasks struct {
Namespace Namespace
Lists []*ListWithTasksAndBuckets `xorm:"-" json:"lists"` Projects []*ProjectWithTasksAndBuckets `xorm:"-" json:"projects"`
} }
func makeNamespaceSlice(namespaces map[int64]*NamespaceWithLists, userMap map[int64]*user.User, subscriptions map[int64]*Subscription) []*NamespaceWithLists { func makeNamespaceSlice(namespaces map[int64]*NamespaceWithProjects, userMap map[int64]*user.User, subscriptions map[int64]*Subscription) []*NamespaceWithProjects {
all := make([]*NamespaceWithLists, 0, len(namespaces)) all := make([]*NamespaceWithProjects, 0, len(namespaces))
for _, n := range namespaces { for _, n := range namespaces {
n.Owner = userMap[n.OwnerID] n.Owner = userMap[n.OwnerID]
n.Subscription = subscriptions[n.ID] n.Subscription = subscriptions[n.ID]
all = append(all, n) all = append(all, n)
for _, l := range n.Lists { for _, l := range n.Projects {
if n.Subscription != nil && l.Subscription == nil { if n.Subscription != nil && l.Subscription == nil {
l.Subscription = n.Subscription l.Subscription = n.Subscription
} }
@ -253,7 +253,7 @@ func getNamespaceArchivedCond(archived bool) builder.Cond {
return isArchivedCond return isArchivedCond
} }
func getNamespacesWithLists(s *xorm.Session, namespaces *map[int64]*NamespaceWithLists, search string, isArchived bool, page, perPage int, userID int64) (numberOfTotalItems int64, err error) { func getNamespacesWithProjects(s *xorm.Session, namespaces *map[int64]*NamespaceWithProjects, search string, isArchived bool, page, perPage int, userID int64) (numberOfTotalItems int64, err error) {
isArchivedCond := getNamespaceArchivedCond(isArchived) isArchivedCond := getNamespaceArchivedCond(isArchived)
filterCond := getNamespaceFilterCond(search) filterCond := getNamespaceFilterCond(search)
@ -289,11 +289,11 @@ func getNamespacesWithLists(s *xorm.Session, namespaces *map[int64]*NamespaceWit
GroupBy("namespaces.id"). GroupBy("namespaces.id").
Where(filterCond). Where(filterCond).
Where(isArchivedCond). Where(isArchivedCond).
Count(&NamespaceWithLists{}) Count(&NamespaceWithProjects{})
return numberOfTotalItems, err return numberOfTotalItems, err
} }
func getNamespaceOwnerIDs(namespaces map[int64]*NamespaceWithLists) (namespaceIDs, ownerIDs []int64) { func getNamespaceOwnerIDs(namespaces map[int64]*NamespaceWithProjects) (namespaceIDs, ownerIDs []int64) {
for _, nsp := range namespaces { for _, nsp := range namespaces {
namespaceIDs = append(namespaceIDs, nsp.ID) namespaceIDs = append(namespaceIDs, nsp.ID)
ownerIDs = append(ownerIDs, nsp.OwnerID) ownerIDs = append(ownerIDs, nsp.OwnerID)
@ -324,36 +324,36 @@ func getNamespaceSubscriptions(s *xorm.Session, namespaceIDs []int64, userID int
return subscriptionsMap, err return subscriptionsMap, err
} }
func getListsForNamespaces(s *xorm.Session, namespaceIDs []int64, archived bool) ([]*List, error) { func getProjectsForNamespaces(s *xorm.Session, namespaceIDs []int64, archived bool) ([]*Project, error) {
lists := []*List{} projects := []*Project{}
listQuery := s. projectQuery := s.
OrderBy("position"). OrderBy("position").
In("namespace_id", namespaceIDs) In("namespace_id", namespaceIDs)
if !archived { if !archived {
listQuery.And("is_archived = false") projectQuery.And("is_archived = false")
} }
err := listQuery.Find(&lists) err := projectQuery.Find(&projects)
return lists, err return projects, err
} }
func getSharedListsInNamespace(s *xorm.Session, archived bool, doer *user.User) (sharedListsNamespace *NamespaceWithLists, err error) { func getSharedProjectsInNamespace(s *xorm.Session, archived bool, doer *user.User) (sharedProjectsNamespace *NamespaceWithProjects, err error) {
// Create our pseudo namespace to hold the shared lists // Create our pseudo namespace to hold the shared projects
sharedListsPseudonamespace := SharedListsPseudoNamespace sharedProjectsPseudonamespace := SharedProjectsPseudoNamespace
sharedListsPseudonamespace.OwnerID = doer.ID sharedProjectsPseudonamespace.OwnerID = doer.ID
sharedListsNamespace = &NamespaceWithLists{ sharedProjectsNamespace = &NamespaceWithProjects{
sharedListsPseudonamespace, sharedProjectsPseudonamespace,
[]*List{}, []*Project{},
} }
// Get all lists individually shared with our user (not via a namespace) // Get all projects individually shared with our user (not via a namespace)
individualLists := []*List{} individualProjects := []*Project{}
iListQuery := s.Select("l.*"). iProjectQuery := s.Select("l.*").
Table("lists"). Table("projects").
Alias("l"). Alias("l").
Join("LEFT", []string{"team_lists", "tl"}, "l.id = tl.list_id"). Join("LEFT", []string{"team_projects", "tl"}, "l.id = tl.project_id").
Join("LEFT", []string{"team_members", "tm"}, "tm.team_id = tl.team_id"). Join("LEFT", []string{"team_members", "tm"}, "tm.team_id = tl.team_id").
Join("LEFT", []string{"users_lists", "ul"}, "ul.list_id = l.id"). Join("LEFT", []string{"users_projects", "ul"}, "ul.project_id = l.id").
Where(builder.And( Where(builder.And(
builder.Eq{"tm.user_id": doer.ID}, builder.Eq{"tm.user_id": doer.ID},
builder.Neq{"l.owner_id": doer.ID}, builder.Neq{"l.owner_id": doer.ID},
@ -364,53 +364,53 @@ func getSharedListsInNamespace(s *xorm.Session, archived bool, doer *user.User)
)). )).
GroupBy("l.id") GroupBy("l.id")
if !archived { if !archived {
iListQuery.And("l.is_archived = false") iProjectQuery.And("l.is_archived = false")
} }
err = iListQuery.Find(&individualLists) err = iProjectQuery.Find(&individualProjects)
if err != nil { if err != nil {
return return
} }
// Make the namespace -1 so we now later which one it was // Make the namespace -1 so we now later which one it was
// + Append it to all lists we already have // + Append it to all projects we already have
for _, l := range individualLists { for _, l := range individualProjects {
l.NamespaceID = sharedListsNamespace.ID l.NamespaceID = sharedProjectsNamespace.ID
} }
sharedListsNamespace.Lists = individualLists sharedProjectsNamespace.Projects = individualProjects
// Remove the sharedListsPseudonamespace if we don't have any shared lists // Remove the sharedProjectsPseudonamespace if we don't have any shared projects
if len(individualLists) == 0 { if len(individualProjects) == 0 {
sharedListsNamespace = nil sharedProjectsNamespace = nil
} }
return return
} }
func getFavoriteLists(s *xorm.Session, lists []*List, namespaceIDs []int64, doer *user.User) (favoriteNamespace *NamespaceWithLists, err error) { func getFavoriteProjects(s *xorm.Session, projects []*Project, namespaceIDs []int64, doer *user.User) (favoriteNamespace *NamespaceWithProjects, err error) {
// Create our pseudo namespace with favorite lists // Create our pseudo namespace with favorite projects
pseudoFavoriteNamespace := FavoritesPseudoNamespace pseudoFavoriteNamespace := FavoritesPseudoNamespace
pseudoFavoriteNamespace.OwnerID = doer.ID pseudoFavoriteNamespace.OwnerID = doer.ID
favoriteNamespace = &NamespaceWithLists{ favoriteNamespace = &NamespaceWithProjects{
Namespace: pseudoFavoriteNamespace, Namespace: pseudoFavoriteNamespace,
Lists: []*List{{}}, Projects: []*Project{{}},
} }
*favoriteNamespace.Lists[0] = FavoritesPseudoList // Copying the list to be able to modify it later *favoriteNamespace.Projects[0] = FavoritesPseudoProject // Copying the project to be able to modify it later
favoriteNamespace.Lists[0].Owner = doer favoriteNamespace.Projects[0].Owner = doer
for _, list := range lists { for _, project := range projects {
if !list.IsFavorite { if !project.IsFavorite {
continue continue
} }
favoriteNamespace.Lists = append(favoriteNamespace.Lists, list) favoriteNamespace.Projects = append(favoriteNamespace.Projects, project)
} }
// Check if we have any favorites or favorited lists and remove the favorites namespace from the list if not // Check if we have any favorites or favorited projects and remove the favorites namespace from the project if not
cond := builder. cond := builder.
Select("tasks.id"). Select("tasks.id").
From("tasks"). From("tasks").
Join("INNER", "lists", "tasks.list_id = lists.id"). Join("INNER", "projects", "tasks.project_id = projects.id").
Join("INNER", "namespaces", "lists.namespace_id = namespaces.id"). Join("INNER", "namespaces", "projects.namespace_id = namespaces.id").
Where(builder.In("namespaces.id", namespaceIDs)) Where(builder.In("namespaces.id", namespaceIDs))
var favoriteCount int64 var favoriteCount int64
@ -425,25 +425,25 @@ func getFavoriteLists(s *xorm.Session, lists []*List, namespaceIDs []int64, doer
return return
} }
// If we don't have any favorites in the favorites pseudo list, remove that pseudo list from the namespace // If we don't have any favorites in the favorites pseudo project, remove that pseudo project from the namespace
if favoriteCount == 0 { if favoriteCount == 0 {
for in, l := range favoriteNamespace.Lists { for in, l := range favoriteNamespace.Projects {
if l.ID == FavoritesPseudoList.ID { if l.ID == FavoritesPseudoProject.ID {
favoriteNamespace.Lists = append(favoriteNamespace.Lists[:in], favoriteNamespace.Lists[in+1:]...) favoriteNamespace.Projects = append(favoriteNamespace.Projects[:in], favoriteNamespace.Projects[in+1:]...)
break break
} }
} }
} }
// If we don't have any favorites in the namespace, remove it // If we don't have any favorites in the namespace, remove it
if len(favoriteNamespace.Lists) == 0 { if len(favoriteNamespace.Projects) == 0 {
return nil, nil return nil, nil
} }
return return
} }
func getSavedFilters(s *xorm.Session, doer *user.User) (savedFiltersNamespace *NamespaceWithLists, err error) { func getSavedFilters(s *xorm.Session, doer *user.User) (savedFiltersNamespace *NamespaceWithProjects, err error) {
savedFilters, err := getSavedFiltersForUser(s, doer) savedFilters, err := getSavedFiltersForUser(s, doer)
if err != nil { if err != nil {
return return
@ -455,16 +455,16 @@ func getSavedFilters(s *xorm.Session, doer *user.User) (savedFiltersNamespace *N
savedFiltersPseudoNamespace := SavedFiltersPseudoNamespace savedFiltersPseudoNamespace := SavedFiltersPseudoNamespace
savedFiltersPseudoNamespace.OwnerID = doer.ID savedFiltersPseudoNamespace.OwnerID = doer.ID
savedFiltersNamespace = &NamespaceWithLists{ savedFiltersNamespace = &NamespaceWithProjects{
Namespace: savedFiltersPseudoNamespace, Namespace: savedFiltersPseudoNamespace,
Lists: make([]*List, 0, len(savedFilters)), Projects: make([]*Project, 0, len(savedFilters)),
} }
for _, filter := range savedFilters { for _, filter := range savedFilters {
filterList := filter.toList() filterProject := filter.toProject()
filterList.NamespaceID = savedFiltersNamespace.ID filterProject.NamespaceID = savedFiltersNamespace.ID
filterList.Owner = doer filterProject.Owner = doer
savedFiltersNamespace.Lists = append(savedFiltersNamespace.Lists, filterList) savedFiltersNamespace.Projects = append(savedFiltersNamespace.Projects, filterProject)
} }
return return
@ -480,9 +480,9 @@ func getSavedFilters(s *xorm.Session, doer *user.User) (savedFiltersNamespace *N
// @Param per_page query int false "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page." // @Param per_page query int false "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page."
// @Param s query string false "Search namespaces by name." // @Param s query string false "Search namespaces by name."
// @Param is_archived query bool false "If true, also returns all archived namespaces." // @Param is_archived query bool false "If true, also returns all archived namespaces."
// @Param namespaces_only query bool false "If true, also returns only namespaces without their lists." // @Param namespaces_only query bool false "If true, also returns only namespaces without their projects."
// @Security JWTKeyAuth // @Security JWTKeyAuth
// @Success 200 {array} models.NamespaceWithLists "The Namespaces." // @Success 200 {array} models.NamespaceWithProjects "The Namespaces."
// @Failure 500 {object} models.Message "Internal error" // @Failure 500 {object} models.Message "Internal error"
// @Router /namespaces [get] // @Router /namespaces [get]
// //
@ -492,19 +492,19 @@ func (n *Namespace) ReadAll(s *xorm.Session, a web.Auth, search string, page int
return nil, 0, 0, ErrGenericForbidden{} return nil, 0, 0, ErrGenericForbidden{}
} }
// This map will hold all namespaces and their lists. The key is usually the id of the namespace. // This map will hold all namespaces and their projects. The key is usually the id of the namespace.
// We're using a map here because it makes a few things like adding lists or removing pseudo namespaces easier. // We're using a map here because it makes a few things like adding projects or removing pseudo namespaces easier.
namespaces := make(map[int64]*NamespaceWithLists) namespaces := make(map[int64]*NamespaceWithProjects)
////////////////////////////// //////////////////////////////
// Lists with their namespaces // Projects with their namespaces
doer, err := user.GetFromAuth(a) doer, err := user.GetFromAuth(a)
if err != nil { if err != nil {
return nil, 0, 0, err return nil, 0, 0, err
} }
numberOfTotalItems, err = getNamespacesWithLists(s, &namespaces, search, n.IsArchived, page, perPage, doer.ID) numberOfTotalItems, err = getNamespacesWithProjects(s, &namespaces, search, n.IsArchived, page, perPage, doer.ID)
if err != nil { if err != nil {
return nil, 0, 0, err return nil, 0, 0, err
} }
@ -531,23 +531,23 @@ func (n *Namespace) ReadAll(s *xorm.Session, a web.Auth, search string, page int
return all, len(all), numberOfTotalItems, nil return all, len(all), numberOfTotalItems, nil
} }
// Get all lists // Get all projects
lists, err := getListsForNamespaces(s, namespaceIDs, n.IsArchived) projects, err := getProjectsForNamespaces(s, namespaceIDs, n.IsArchived)
if err != nil { if err != nil {
return nil, 0, 0, err return nil, 0, 0, err
} }
/////////////// ///////////////
// Shared Lists // Shared Projects
sharedListsNamespace, err := getSharedListsInNamespace(s, n.IsArchived, doer) sharedProjectsNamespace, err := getSharedProjectsInNamespace(s, n.IsArchived, doer)
if err != nil { if err != nil {
return nil, 0, 0, err return nil, 0, 0, err
} }
if sharedListsNamespace != nil { if sharedProjectsNamespace != nil {
namespaces[sharedListsNamespace.ID] = sharedListsNamespace namespaces[sharedProjectsNamespace.ID] = sharedProjectsNamespace
lists = append(lists, sharedListsNamespace.Lists...) projects = append(projects, sharedProjectsNamespace.Projects...)
} }
///////////////// /////////////////
@ -560,20 +560,20 @@ func (n *Namespace) ReadAll(s *xorm.Session, a web.Auth, search string, page int
if savedFiltersNamespace != nil { if savedFiltersNamespace != nil {
namespaces[savedFiltersNamespace.ID] = savedFiltersNamespace namespaces[savedFiltersNamespace.ID] = savedFiltersNamespace
lists = append(lists, savedFiltersNamespace.Lists...) projects = append(projects, savedFiltersNamespace.Projects...)
} }
///////////////// /////////////////
// Add list details (favorite state, among other things) // Add project details (favorite state, among other things)
err = addListDetails(s, lists, a) err = addProjectDetails(s, projects, a)
if err != nil { if err != nil {
return return
} }
///////////////// /////////////////
// Favorite lists // Favorite projects
favoritesNamespace, err := getFavoriteLists(s, lists, namespaceIDs, doer) favoritesNamespace, err := getFavoriteProjects(s, projects, namespaceIDs, doer)
if err != nil { if err != nil {
return nil, 0, 0, err return nil, 0, 0, err
} }
@ -585,12 +585,12 @@ func (n *Namespace) ReadAll(s *xorm.Session, a web.Auth, search string, page int
////////////////////// //////////////////////
// Put it all together // Put it all together
for _, list := range lists { for _, project := range projects {
if list.NamespaceID == SharedListsPseudoNamespace.ID || list.NamespaceID == SavedFiltersPseudoNamespace.ID { if project.NamespaceID == SharedProjectsPseudoNamespace.ID || project.NamespaceID == SavedFiltersPseudoNamespace.ID {
// Shared lists and filtered lists are already in the namespace // Shared projects and filtered projects are already in the namespace
continue continue
} }
namespaces[list.NamespaceID].Lists = append(namespaces[list.NamespaceID].Lists, list) namespaces[project.NamespaceID].Projects = append(namespaces[project.NamespaceID].Projects, project)
} }
all := makeNamespaceSlice(namespaces, ownerMap, subscriptionsMap) all := makeNamespaceSlice(namespaces, ownerMap, subscriptionsMap)
@ -663,7 +663,7 @@ func (n *Namespace) Delete(s *xorm.Session, a web.Auth) (err error) {
return deleteNamespace(s, n, a, true) return deleteNamespace(s, n, a, true)
} }
func deleteNamespace(s *xorm.Session, n *Namespace, a web.Auth, withLists bool) (err error) { func deleteNamespace(s *xorm.Session, n *Namespace, a web.Auth, withProjects bool) (err error) {
// Check if the namespace exists // Check if the namespace exists
_, err = GetNamespaceByID(s, n.ID) _, err = GetNamespaceByID(s, n.ID)
if err != nil { if err != nil {
@ -681,23 +681,23 @@ func deleteNamespace(s *xorm.Session, n *Namespace, a web.Auth, withLists bool)
Doer: a, Doer: a,
} }
if !withLists { if !withProjects {
return events.Dispatch(namespaceDeleted) return events.Dispatch(namespaceDeleted)
} }
// Delete all lists with their tasks // Delete all projects with their tasks
lists, err := GetListsByNamespaceID(s, n.ID, &user.User{}) projects, err := GetProjectsByNamespaceID(s, n.ID, &user.User{})
if err != nil { if err != nil {
return return
} }
if len(lists) == 0 { if len(projects) == 0 {
return events.Dispatch(namespaceDeleted) return events.Dispatch(namespaceDeleted)
} }
// Looping over all lists to let the list handle properly cleaning up the tasks and everything else associated with it. // Looping over all projects to let the project handle properly cleaning up the tasks and everything else associated with it.
for _, list := range lists { for _, project := range projects {
err = list.Delete(s, a) err = project.Delete(s, a)
if err != nil { if err != nil {
return err return err
} }
@ -748,7 +748,7 @@ func (n *Namespace) Update(s *xorm.Session, a web.Auth) (err error) {
} }
} }
// We need to specify the cols we want to update here to be able to un-archive lists // We need to specify the cols we want to update here to be able to un-archive projects
colsToUpdate := []string{ colsToUpdate := []string{
"title", "title",
"is_archived", "is_archived",

View File

@ -73,7 +73,7 @@ func (n *Namespace) checkRight(s *xorm.Session, a web.Auth, rights ...Right) (bo
} }
if a.GetID() == nn.OwnerID || if a.GetID() == nn.OwnerID ||
nn.ID == SharedListsPseudoNamespace.ID || nn.ID == SharedProjectsPseudoNamespace.ID ||
nn.ID == FavoritesPseudoNamespace.ID || nn.ID == FavoritesPseudoNamespace.ID ||
nn.ID == SavedFiltersPseudoNamespace.ID { nn.ID == SavedFiltersPseudoNamespace.ID {
return true, int(RightAdmin), nil return true, int(RightAdmin), nil
@ -88,7 +88,7 @@ func (n *Namespace) checkRight(s *xorm.Session, a web.Auth, rights ...Right) (bo
for each passed right. That way, we can check with a single sql query (instead if 8) for each passed right. That way, we can check with a single sql query (instead if 8)
if the user has the right to see the list or not. if the user has the right to see the project or not.
*/ */
var conds []builder.Cond var conds []builder.Cond

View File

@ -72,7 +72,7 @@ func TestTeamNamespace_ReadAll(t *testing.T) {
} }
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
s := db.NewSession() s := db.NewSession()
teams, _, _, err := tn.ReadAll(s, u, "READ_only_on_list6", 1, 50) teams, _, _, err := tn.ReadAll(s, u, "READ_only_on_project6", 1, 50)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, reflect.TypeOf(teams).Kind(), reflect.Slice) assert.Equal(t, reflect.TypeOf(teams).Kind(), reflect.Slice)
ts := teams.([]*TeamWithRight) ts := teams.([]*TeamWithRight)

View File

@ -207,21 +207,21 @@ func TestNamespace_ReadAll(t *testing.T) {
n := &Namespace{} n := &Namespace{}
nn, _, _, err := n.ReadAll(s, user1, "", 1, -1) nn, _, _, err := n.ReadAll(s, user1, "", 1, -1)
assert.NoError(t, err) assert.NoError(t, err)
namespaces := nn.([]*NamespaceWithLists) namespaces := nn.([]*NamespaceWithProjects)
assert.NotNil(t, namespaces) assert.NotNil(t, namespaces)
assert.Len(t, namespaces, 11) // Total of 11 including shared, favorites and saved filters assert.Len(t, namespaces, 11) // Total of 11 including shared, favorites and saved filters
assert.Equal(t, int64(-3), namespaces[0].ID) // The first one should be the one with saved filters assert.Equal(t, int64(-3), namespaces[0].ID) // The first one should be the one with saved filters
assert.Equal(t, int64(-2), namespaces[1].ID) // The second one should be the one with favorites assert.Equal(t, int64(-2), namespaces[1].ID) // The second one should be the one with favorites
assert.Equal(t, int64(-1), namespaces[2].ID) // The third one should be the one with the shared namespaces assert.Equal(t, int64(-1), namespaces[2].ID) // The third one should be the one with the shared namespaces
// Ensure every list and namespace are not archived // Ensure every project and namespace are not archived
for _, namespace := range namespaces { for _, namespace := range namespaces {
assert.False(t, namespace.IsArchived) assert.False(t, namespace.IsArchived)
for _, list := range namespace.Lists { for _, project := range namespace.Projects {
assert.False(t, list.IsArchived) assert.False(t, project.IsArchived)
} }
} }
}) })
t.Run("no own shared lists", func(t *testing.T) { t.Run("no own shared projects", func(t *testing.T) {
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
s := db.NewSession() s := db.NewSession()
defer s.Close() defer s.Close()
@ -229,18 +229,18 @@ func TestNamespace_ReadAll(t *testing.T) {
n := &Namespace{} n := &Namespace{}
nn, _, _, err := n.ReadAll(s, user6, "", 1, -1) nn, _, _, err := n.ReadAll(s, user6, "", 1, -1)
assert.NoError(t, err) assert.NoError(t, err)
namespaces := nn.([]*NamespaceWithLists) namespaces := nn.([]*NamespaceWithProjects)
assert.NotNil(t, namespaces) assert.NotNil(t, namespaces)
assert.Equal(t, int64(-1), namespaces[1].ID) // The third one should be the one with the shared namespaces assert.Equal(t, int64(-1), namespaces[1].ID) // The third one should be the one with the shared namespaces
sharedListOccurences := make(map[int64]int64) sharedProjectOccurences := make(map[int64]int64)
for _, list := range namespaces[1].Lists { for _, project := range namespaces[1].Projects {
assert.NotEqual(t, user1.ID, list.OwnerID) assert.NotEqual(t, user1.ID, project.OwnerID)
sharedListOccurences[list.ID]++ sharedProjectOccurences[project.ID]++
} }
for listID, occ := range sharedListOccurences { for projectID, occ := range sharedProjectOccurences {
assert.Equal(t, int64(1), occ, "shared list %d is present %d times, should be 1", listID, occ) assert.Equal(t, int64(1), occ, "shared project %d is present %d times, should be 1", projectID, occ)
} }
}) })
t.Run("namespaces only", func(t *testing.T) { t.Run("namespaces only", func(t *testing.T) {
@ -253,12 +253,12 @@ func TestNamespace_ReadAll(t *testing.T) {
} }
nn, _, _, err := n.ReadAll(s, user1, "", 1, -1) nn, _, _, err := n.ReadAll(s, user1, "", 1, -1)
assert.NoError(t, err) assert.NoError(t, err)
namespaces := nn.([]*NamespaceWithLists) namespaces := nn.([]*NamespaceWithProjects)
assert.NotNil(t, namespaces) assert.NotNil(t, namespaces)
assert.Len(t, namespaces, 8) // Total of 8 - excluding shared, favorites and saved filters (normally 11) assert.Len(t, namespaces, 8) // Total of 8 - excluding shared, favorites and saved filters (normally 11)
// Ensure every namespace does not contain lists // Ensure every namespace does not contain projects
for _, namespace := range namespaces { for _, namespace := range namespaces {
assert.Nil(t, namespace.Lists) assert.Nil(t, namespace.Projects)
} }
}) })
t.Run("ids only", func(t *testing.T) { t.Run("ids only", func(t *testing.T) {
@ -271,7 +271,7 @@ func TestNamespace_ReadAll(t *testing.T) {
} }
nn, _, _, err := n.ReadAll(s, user7, "13,14", 1, -1) nn, _, _, err := n.ReadAll(s, user7, "13,14", 1, -1)
assert.NoError(t, err) assert.NoError(t, err)
namespaces := nn.([]*NamespaceWithLists) namespaces := nn.([]*NamespaceWithProjects)
assert.NotNil(t, namespaces) assert.NotNil(t, namespaces)
assert.Len(t, namespaces, 2) assert.Len(t, namespaces, 2)
assert.Equal(t, int64(13), namespaces[0].ID) assert.Equal(t, int64(13), namespaces[0].ID)
@ -287,7 +287,7 @@ func TestNamespace_ReadAll(t *testing.T) {
} }
nn, _, _, err := n.ReadAll(s, user1, "1,w", 1, -1) nn, _, _, err := n.ReadAll(s, user1, "1,w", 1, -1)
assert.NoError(t, err) assert.NoError(t, err)
namespaces := nn.([]*NamespaceWithLists) namespaces := nn.([]*NamespaceWithProjects)
assert.NotNil(t, namespaces) assert.NotNil(t, namespaces)
assert.Len(t, namespaces, 1) assert.Len(t, namespaces, 1)
assert.Equal(t, int64(1), namespaces[0].ID) assert.Equal(t, int64(1), namespaces[0].ID)
@ -301,7 +301,7 @@ func TestNamespace_ReadAll(t *testing.T) {
IsArchived: true, IsArchived: true,
} }
nn, _, _, err := n.ReadAll(s, user1, "", 1, -1) nn, _, _, err := n.ReadAll(s, user1, "", 1, -1)
namespaces := nn.([]*NamespaceWithLists) namespaces := nn.([]*NamespaceWithProjects)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotNil(t, namespaces) assert.NotNil(t, namespaces)
assert.Len(t, namespaces, 12) // Total of 12 including shared & favorites, one is archived assert.Len(t, namespaces, 12) // Total of 12 including shared & favorites, one is archived
@ -316,7 +316,7 @@ func TestNamespace_ReadAll(t *testing.T) {
n := &Namespace{} n := &Namespace{}
nn, _, _, err := n.ReadAll(s, user11, "", 1, -1) nn, _, _, err := n.ReadAll(s, user11, "", 1, -1)
namespaces := nn.([]*NamespaceWithLists) namespaces := nn.([]*NamespaceWithProjects)
assert.NoError(t, err) assert.NoError(t, err)
// Assert the first namespace is not the favorites namespace // Assert the first namespace is not the favorites namespace
assert.NotEqual(t, FavoritesPseudoNamespace.ID, namespaces[0].ID) assert.NotEqual(t, FavoritesPseudoNamespace.ID, namespaces[0].ID)
@ -328,11 +328,11 @@ func TestNamespace_ReadAll(t *testing.T) {
n := &Namespace{} n := &Namespace{}
nn, _, _, err := n.ReadAll(s, user12, "", 1, -1) nn, _, _, err := n.ReadAll(s, user12, "", 1, -1)
namespaces := nn.([]*NamespaceWithLists) namespaces := nn.([]*NamespaceWithProjects)
assert.NoError(t, err) assert.NoError(t, err)
// Assert the first namespace is the favorites namespace and contains lists // Assert the first namespace is the favorites namespace and contains projects
assert.Equal(t, FavoritesPseudoNamespace.ID, namespaces[0].ID) assert.Equal(t, FavoritesPseudoNamespace.ID, namespaces[0].ID)
assert.NotEqual(t, 0, namespaces[0].Lists) assert.NotEqual(t, 0, namespaces[0].Projects)
}) })
t.Run("no saved filters", func(t *testing.T) { t.Run("no saved filters", func(t *testing.T) {
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
@ -341,7 +341,7 @@ func TestNamespace_ReadAll(t *testing.T) {
n := &Namespace{} n := &Namespace{}
nn, _, _, err := n.ReadAll(s, user11, "", 1, -1) nn, _, _, err := n.ReadAll(s, user11, "", 1, -1)
namespaces := nn.([]*NamespaceWithLists) namespaces := nn.([]*NamespaceWithProjects)
assert.NoError(t, err) assert.NoError(t, err)
// Assert the first namespace is not the favorites namespace // Assert the first namespace is not the favorites namespace
assert.NotEqual(t, SavedFiltersPseudoNamespace.ID, namespaces[0].ID) assert.NotEqual(t, SavedFiltersPseudoNamespace.ID, namespaces[0].ID)
@ -364,7 +364,7 @@ func TestNamespace_ReadAll(t *testing.T) {
n := &Namespace{} n := &Namespace{}
nn, _, _, err := n.ReadAll(s, user6, "NamespACE7", 1, -1) nn, _, _, err := n.ReadAll(s, user6, "NamespACE7", 1, -1)
assert.NoError(t, err) assert.NoError(t, err)
namespaces := nn.([]*NamespaceWithLists) namespaces := nn.([]*NamespaceWithProjects)
assert.NotNil(t, namespaces) assert.NotNil(t, namespaces)
assert.Len(t, namespaces, 2) assert.Len(t, namespaces, 2)
assert.Equal(t, int64(7), namespaces[1].ID) assert.Equal(t, int64(7), namespaces[1].ID)

View File

@ -79,7 +79,7 @@ func TestNamespaceUser_Create(t *testing.T) {
errType: IsErrInvalidRight, errType: IsErrInvalidRight,
}, },
{ {
name: "NamespaceUsers Create with inexisting list", name: "NamespaceUsers Create with inexisting project",
fields: fields{ fields: fields{
Username: "user1", Username: "user1",
NamespaceID: 2000, NamespaceID: 2000,
@ -208,7 +208,7 @@ func TestNamespaceUser_ReadAll(t *testing.T) {
}, },
}, },
{ {
name: "Test ReadAll by a user who does not have access to the list", name: "Test ReadAll by a user who does not have access to the project",
fields: fields{ fields: fields{
NamespaceID: 3, NamespaceID: 3,
}, },

View File

@ -150,28 +150,28 @@ func (n *TaskDeletedNotification) Name() string {
return "task.deleted" return "task.deleted"
} }
// ListCreatedNotification represents a ListCreatedNotification notification // ProjectCreatedNotification represents a ProjectCreatedNotification notification
type ListCreatedNotification struct { type ProjectCreatedNotification struct {
Doer *user.User `json:"doer"` Doer *user.User `json:"doer"`
List *List `json:"list"` Project *Project `json:"project"`
} }
// ToMail returns the mail notification for ListCreatedNotification // ToMail returns the mail notification for ProjectCreatedNotification
func (n *ListCreatedNotification) ToMail() *notifications.Mail { func (n *ProjectCreatedNotification) ToMail() *notifications.Mail {
return notifications.NewMail(). return notifications.NewMail().
Subject(n.Doer.GetName()+` created the list "`+n.List.Title+`"`). Subject(n.Doer.GetName()+` created the project "`+n.Project.Title+`"`).
Line(n.Doer.GetName()+` created the list "`+n.List.Title+`"`). Line(n.Doer.GetName()+` created the project "`+n.Project.Title+`"`).
Action("View List", config.ServiceFrontendurl.GetString()+"lists/") Action("View Project", config.ServiceFrontendurl.GetString()+"projects/")
} }
// ToDB returns the ListCreatedNotification notification in a format which can be saved in the db // ToDB returns the ProjectCreatedNotification notification in a format which can be saved in the db
func (n *ListCreatedNotification) ToDB() interface{} { func (n *ProjectCreatedNotification) ToDB() interface{} {
return n return n
} }
// Name returns the name of the notification // Name returns the name of the notification
func (n *ListCreatedNotification) Name() string { func (n *ProjectCreatedNotification) Name() string {
return "list.created" return "project.created"
} }
// TeamMemberAddedNotification represents a TeamMemberAddedNotification notification // TeamMemberAddedNotification represents a TeamMemberAddedNotification notification

View File

@ -16,7 +16,7 @@
package models package models
// Right defines the rights users/teams can have for lists/namespaces // Right defines the rights users/teams can have for projects/namespaces
type Right int type Right int
// define unknown right // define unknown right
@ -26,11 +26,11 @@ const (
// Enumerate all the team rights // Enumerate all the team rights
const ( const (
// Can read lists in a // Can read projects in a
RightRead Right = iota RightRead Right = iota
// Can write in a like lists and tasks. Cannot create new lists. // Can write in a like projects and tasks. Cannot create new projects.
RightWrite RightWrite
// Can manage a list/namespace, can do everything // Can manage a project/namespace, can do everything
RightAdmin RightAdmin
) )

View File

@ -39,7 +39,7 @@ type SavedFilter struct {
// The user who owns this filter // The user who owns this filter
Owner *user.User `xorm:"-" json:"owner" valid:"-"` Owner *user.User `xorm:"-" json:"owner" valid:"-"`
// True if the filter is a favorite. Favorite filters show up in a separate namespace together with favorite lists. // True if the filter is a favorite. Favorite filters show up in a separate namespace together with favorite projects.
IsFavorite bool `xorm:"default false" json:"is_favorite"` IsFavorite bool `xorm:"default false" json:"is_favorite"`
// A timestamp when this filter was created. You cannot change this value. // A timestamp when this filter was created. You cannot change this value.
@ -57,28 +57,28 @@ func (sf *SavedFilter) TableName() string {
} }
func (sf *SavedFilter) getTaskCollection() *TaskCollection { func (sf *SavedFilter) getTaskCollection() *TaskCollection {
// We're resetting the listID to return tasks from all lists // We're resetting the projectID to return tasks from all projects
sf.Filters.ListID = 0 sf.Filters.ProjectID = 0
return sf.Filters return sf.Filters
} }
// Returns the saved filter ID from a list ID. Will not check if the filter actually exists. // Returns the saved filter ID from a project ID. Will not check if the filter actually exists.
// If the returned ID is zero, means that it is probably invalid. // If the returned ID is zero, means that it is probably invalid.
func getSavedFilterIDFromListID(listID int64) (filterID int64) { func getSavedFilterIDFromProjectID(projectID int64) (filterID int64) {
// We get the id of the saved filter by multiplying the ListID with -1 and subtracting one // We get the id of the saved filter by multiplying the ProjectID with -1 and subtracting one
filterID = listID*-1 - 1 filterID = projectID*-1 - 1
// FilterIDs from listIDs are always positive // FilterIDs from projectIDs are always positive
if filterID < 0 { if filterID < 0 {
filterID = 0 filterID = 0
} }
return return
} }
func getListIDFromSavedFilterID(filterID int64) (listID int64) { func getProjectIDFromSavedFilterID(filterID int64) (projectID int64) {
listID = filterID*-1 - 1 projectID = filterID*-1 - 1
// ListIDs from saved filters are always negative // ProjectIDs from saved filters are always negative
if listID > 0 { if projectID > 0 {
listID = 0 projectID = 0
} }
return return
} }
@ -93,9 +93,9 @@ func getSavedFiltersForUser(s *xorm.Session, auth web.Auth) (filters []*SavedFil
return return
} }
func (sf *SavedFilter) toList() *List { func (sf *SavedFilter) toProject() *Project {
return &List{ return &Project{
ID: getListIDFromSavedFilterID(sf.ID), ID: getProjectIDFromSavedFilterID(sf.ID),
Title: sf.Title, Title: sf.Title,
Description: sf.Description, Description: sf.Description,
IsFavorite: sf.IsFavorite, IsFavorite: sf.IsFavorite,

View File

@ -25,21 +25,21 @@ import (
"xorm.io/xorm/schemas" "xorm.io/xorm/schemas"
) )
func TestSavedFilter_getListIDFromFilter(t *testing.T) { func TestSavedFilter_getProjectIDFromFilter(t *testing.T) {
t.Run("normal", func(t *testing.T) { t.Run("normal", func(t *testing.T) {
assert.Equal(t, int64(-2), getListIDFromSavedFilterID(1)) assert.Equal(t, int64(-2), getProjectIDFromSavedFilterID(1))
}) })
t.Run("invalid", func(t *testing.T) { t.Run("invalid", func(t *testing.T) {
assert.Equal(t, int64(0), getListIDFromSavedFilterID(-1)) assert.Equal(t, int64(0), getProjectIDFromSavedFilterID(-1))
}) })
} }
func TestSavedFilter_getFilterIDFromListID(t *testing.T) { func TestSavedFilter_getFilterIDFromProjectID(t *testing.T) {
t.Run("normal", func(t *testing.T) { t.Run("normal", func(t *testing.T) {
assert.Equal(t, int64(1), getSavedFilterIDFromListID(-2)) assert.Equal(t, int64(1), getSavedFilterIDFromProjectID(-2))
}) })
t.Run("invalid", func(t *testing.T) { t.Run("invalid", func(t *testing.T) {
assert.Equal(t, int64(0), getSavedFilterIDFromListID(2)) assert.Equal(t, int64(0), getSavedFilterIDFromProjectID(2))
}) })
} }

View File

@ -32,13 +32,13 @@ type SubscriptionEntityType int
const ( const (
SubscriptionEntityUnknown = iota SubscriptionEntityUnknown = iota
SubscriptionEntityNamespace SubscriptionEntityNamespace
SubscriptionEntityList SubscriptionEntityProject
SubscriptionEntityTask SubscriptionEntityTask
) )
const ( const (
entityNamespace = `namespace` entityNamespace = `namespace`
entityList = `list` entityProject = `project`
entityTask = `task` entityTask = `task`
) )
@ -72,8 +72,8 @@ func getEntityTypeFromString(entityType string) SubscriptionEntityType {
switch entityType { switch entityType {
case entityNamespace: case entityNamespace:
return SubscriptionEntityNamespace return SubscriptionEntityNamespace
case entityList: case entityProject:
return SubscriptionEntityList return SubscriptionEntityProject
case entityTask: case entityTask:
return SubscriptionEntityTask return SubscriptionEntityTask
} }
@ -86,8 +86,8 @@ func (et SubscriptionEntityType) String() string {
switch et { switch et {
case SubscriptionEntityNamespace: case SubscriptionEntityNamespace:
return entityNamespace return entityNamespace
case SubscriptionEntityList: case SubscriptionEntityProject:
return entityList return entityProject
case SubscriptionEntityTask: case SubscriptionEntityTask:
return entityTask return entityTask
} }
@ -97,7 +97,7 @@ func (et SubscriptionEntityType) String() string {
func (et SubscriptionEntityType) validate() error { func (et SubscriptionEntityType) validate() error {
if et == SubscriptionEntityNamespace || if et == SubscriptionEntityNamespace ||
et == SubscriptionEntityList || et == SubscriptionEntityProject ||
et == SubscriptionEntityTask { et == SubscriptionEntityTask {
return nil return nil
} }
@ -112,7 +112,7 @@ func (et SubscriptionEntityType) validate() error {
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Security JWTKeyAuth // @Security JWTKeyAuth
// @Param entity path string true "The entity the user subscribes to. Can be either `namespace`, `list` or `task`." // @Param entity path string true "The entity the user subscribes to. Can be either `namespace`, `project` or `task`."
// @Param entityID path string true "The numeric id of the entity to subscribe to." // @Param entityID path string true "The numeric id of the entity to subscribe to."
// @Success 201 {object} models.Subscription "The subscription" // @Success 201 {object} models.Subscription "The subscription"
// @Failure 403 {object} web.HTTPError "The user does not have access to subscribe to this entity." // @Failure 403 {object} web.HTTPError "The user does not have access to subscribe to this entity."
@ -153,7 +153,7 @@ func (sb *Subscription) Create(s *xorm.Session, auth web.Auth) (err error) {
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Security JWTKeyAuth // @Security JWTKeyAuth
// @Param entity path string true "The entity the user subscribed to. Can be either `namespace`, `list` or `task`." // @Param entity path string true "The entity the user subscribed to. Can be either `namespace`, `project` or `task`."
// @Param entityID path string true "The numeric id of the subscribed entity to." // @Param entityID path string true "The numeric id of the subscribed entity to."
// @Success 200 {object} models.Subscription "The subscription" // @Success 200 {object} models.Subscription "The subscription"
// @Failure 403 {object} web.HTTPError "The user does not have access to subscribe to this entity." // @Failure 403 {object} web.HTTPError "The user does not have access to subscribe to this entity."
@ -177,16 +177,16 @@ func getSubscriberCondForEntity(entityType SubscriptionEntityType, entityID int6
) )
} }
if entityType == SubscriptionEntityList { if entityType == SubscriptionEntityProject {
cond = builder.Or( cond = builder.Or(
builder.And( builder.And(
builder.Eq{"entity_id": entityID}, builder.Eq{"entity_id": entityID},
builder.Eq{"entity_type": SubscriptionEntityList}, builder.Eq{"entity_type": SubscriptionEntityProject},
), ),
builder.And( builder.And(
builder.Eq{"entity_id": builder. builder.Eq{"entity_id": builder.
Select("namespace_id"). Select("namespace_id").
From("lists"). From("projects").
Where(builder.Eq{"id": entityID}), Where(builder.Eq{"id": entityID}),
}, },
builder.Eq{"entity_type": SubscriptionEntityNamespace}, builder.Eq{"entity_type": SubscriptionEntityNamespace},
@ -203,19 +203,19 @@ func getSubscriberCondForEntity(entityType SubscriptionEntityType, entityID int6
builder.And( builder.And(
builder.Eq{"entity_id": builder. builder.Eq{"entity_id": builder.
Select("namespace_id"). Select("namespace_id").
From("lists"). From("projects").
Join("INNER", "tasks", "lists.id = tasks.list_id"). Join("INNER", "tasks", "projects.id = tasks.project_id").
Where(builder.Eq{"tasks.id": entityID}), Where(builder.Eq{"tasks.id": entityID}),
}, },
builder.Eq{"entity_type": SubscriptionEntityNamespace}, builder.Eq{"entity_type": SubscriptionEntityNamespace},
), ),
builder.And( builder.And(
builder.Eq{"entity_id": builder. builder.Eq{"entity_id": builder.
Select("list_id"). Select("project_id").
From("tasks"). From("tasks").
Where(builder.Eq{"id": entityID}), Where(builder.Eq{"id": entityID}),
}, },
builder.Eq{"entity_type": SubscriptionEntityList}, builder.Eq{"entity_type": SubscriptionEntityProject},
), ),
) )
} }
@ -225,8 +225,8 @@ func getSubscriberCondForEntity(entityType SubscriptionEntityType, entityID int6
// GetSubscription returns a matching subscription for an entity and user. // GetSubscription returns a matching subscription for an entity and user.
// It will return the next parent of a subscription. That means for tasks, it will first look for a subscription for // It will return the next parent of a subscription. That means for tasks, it will first look for a subscription for
// that task, if there is none it will look for a subscription on the list the task belongs to and if that also // that task, if there is none it will look for a subscription on the project the task belongs to and if that also
// doesn't exist it will check for a subscription for the namespace the list is belonging to. // doesn't exist it will check for a subscription for the namespace the project is belonging to.
func GetSubscription(s *xorm.Session, entityType SubscriptionEntityType, entityID int64, a web.Auth) (subscription *Subscription, err error) { func GetSubscription(s *xorm.Session, entityType SubscriptionEntityType, entityID int64, a web.Auth) (subscription *Subscription, err error) {
subs, err := GetSubscriptions(s, entityType, []int64{entityID}, a) subs, err := GetSubscriptions(s, entityType, []int64{entityID}, a)
if err != nil || len(subs) == 0 { if err != nil || len(subs) == 0 {
@ -242,7 +242,7 @@ func GetSubscription(s *xorm.Session, entityType SubscriptionEntityType, entityI
} }
// GetSubscriptions returns a map of subscriptions to a set of given entity IDs // GetSubscriptions returns a map of subscriptions to a set of given entity IDs
func GetSubscriptions(s *xorm.Session, entityType SubscriptionEntityType, entityIDs []int64, a web.Auth) (listsToSubscriptions map[int64]*Subscription, err error) { func GetSubscriptions(s *xorm.Session, entityType SubscriptionEntityType, entityIDs []int64, a web.Auth) (projectsToSubscriptions map[int64]*Subscription, err error) {
u, is := a.(*user.User) u, is := a.(*user.User)
if !is { if !is {
return return
@ -269,12 +269,12 @@ func GetSubscriptions(s *xorm.Session, entityType SubscriptionEntityType, entity
return nil, err return nil, err
} }
listsToSubscriptions = make(map[int64]*Subscription) projectsToSubscriptions = make(map[int64]*Subscription)
for _, sub := range subscriptions { for _, sub := range subscriptions {
sub.Entity = sub.EntityType.String() sub.Entity = sub.EntityType.String()
listsToSubscriptions[sub.EntityID] = sub projectsToSubscriptions[sub.EntityID] = sub
} }
return listsToSubscriptions, nil return projectsToSubscriptions, nil
} }
func getSubscribersForEntity(s *xorm.Session, entityType SubscriptionEntityType, entityID int64) (subscriptions []*Subscription, err error) { func getSubscribersForEntity(s *xorm.Session, entityType SubscriptionEntityType, entityID int64) (subscriptions []*Subscription, err error) {

View File

@ -33,8 +33,8 @@ func (sb *Subscription) CanCreate(s *xorm.Session, a web.Auth) (can bool, err er
case SubscriptionEntityNamespace: case SubscriptionEntityNamespace:
n := &Namespace{ID: sb.EntityID} n := &Namespace{ID: sb.EntityID}
can, _, err = n.CanRead(s, a) can, _, err = n.CanRead(s, a)
case SubscriptionEntityList: case SubscriptionEntityProject:
l := &List{ID: sb.EntityID} l := &Project{ID: sb.EntityID}
can, _, err = l.CanRead(s, a) can, _, err = l.CanRead(s, a)
case SubscriptionEntityTask: case SubscriptionEntityTask:
t := &Task{ID: sb.EntityID} t := &Task{ID: sb.EntityID}

View File

@ -29,9 +29,9 @@ func TestSubscriptionGetTypeFromString(t *testing.T) {
entityType := getEntityTypeFromString("namespace") entityType := getEntityTypeFromString("namespace")
assert.Equal(t, SubscriptionEntityType(SubscriptionEntityNamespace), entityType) assert.Equal(t, SubscriptionEntityType(SubscriptionEntityNamespace), entityType)
}) })
t.Run("list", func(t *testing.T) { t.Run("project", func(t *testing.T) {
entityType := getEntityTypeFromString("list") entityType := getEntityTypeFromString("project")
assert.Equal(t, SubscriptionEntityType(SubscriptionEntityList), entityType) assert.Equal(t, SubscriptionEntityType(SubscriptionEntityProject), entityType)
}) })
t.Run("task", func(t *testing.T) { t.Run("task", func(t *testing.T) {
entityType := getEntityTypeFromString("task") entityType := getEntityTypeFromString("task")
@ -104,20 +104,20 @@ func TestSubscription_Create(t *testing.T) {
assert.True(t, IsErrNamespaceDoesNotExist(err)) assert.True(t, IsErrNamespaceDoesNotExist(err))
assert.False(t, can) assert.False(t, can)
}) })
t.Run("noneixsting list", func(t *testing.T) { t.Run("noneixsting project", func(t *testing.T) {
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
s := db.NewSession() s := db.NewSession()
defer s.Close() defer s.Close()
sb := &Subscription{ sb := &Subscription{
Entity: "list", Entity: "project",
EntityID: 99999999, EntityID: 99999999,
UserID: u.ID, UserID: u.ID,
} }
can, err := sb.CanCreate(s, u) can, err := sb.CanCreate(s, u)
assert.Error(t, err) assert.Error(t, err)
assert.True(t, IsErrListDoesNotExist(err)) assert.True(t, IsErrProjectDoesNotExist(err))
assert.False(t, can) assert.False(t, can)
}) })
t.Run("noneixsting task", func(t *testing.T) { t.Run("noneixsting task", func(t *testing.T) {
@ -151,13 +151,13 @@ func TestSubscription_Create(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.False(t, can) assert.False(t, can)
}) })
t.Run("no rights to see list", func(t *testing.T) { t.Run("no rights to see project", func(t *testing.T) {
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
s := db.NewSession() s := db.NewSession()
defer s.Close() defer s.Close()
sb := &Subscription{ sb := &Subscription{
Entity: "list", Entity: "project",
EntityID: 20, EntityID: 20,
UserID: u.ID, UserID: u.ID,
} }
@ -278,12 +278,12 @@ func TestSubscriptionGet(t *testing.T) {
assert.NotNil(t, sub) assert.NotNil(t, sub)
assert.Equal(t, int64(2), sub.ID) assert.Equal(t, int64(2), sub.ID)
}) })
t.Run("list", func(t *testing.T) { t.Run("project", func(t *testing.T) {
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
s := db.NewSession() s := db.NewSession()
defer s.Close() defer s.Close()
sub, err := GetSubscription(s, SubscriptionEntityList, 12, u) sub, err := GetSubscription(s, SubscriptionEntityProject, 12, u)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotNil(t, sub) assert.NotNil(t, sub)
assert.Equal(t, int64(3), sub.ID) assert.Equal(t, int64(3), sub.ID)
@ -300,13 +300,13 @@ func TestSubscriptionGet(t *testing.T) {
}) })
}) })
t.Run("inherited", func(t *testing.T) { t.Run("inherited", func(t *testing.T) {
t.Run("list from namespace", func(t *testing.T) { t.Run("project from namespace", func(t *testing.T) {
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
s := db.NewSession() s := db.NewSession()
defer s.Close() defer s.Close()
// List 6 belongs to namespace 6 where user 6 has subscribed to // Project 6 belongs to namespace 6 where user 6 has subscribed to
sub, err := GetSubscription(s, SubscriptionEntityList, 6, u) sub, err := GetSubscription(s, SubscriptionEntityProject, 6, u)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotNil(t, sub) assert.NotNil(t, sub)
assert.Equal(t, int64(2), sub.ID) assert.Equal(t, int64(2), sub.ID)
@ -316,18 +316,18 @@ func TestSubscriptionGet(t *testing.T) {
s := db.NewSession() s := db.NewSession()
defer s.Close() defer s.Close()
// Task 20 belongs to list 11 which belongs to namespace 6 where the user has subscribed // Task 20 belongs to project 11 which belongs to namespace 6 where the user has subscribed
sub, err := GetSubscription(s, SubscriptionEntityTask, 20, u) sub, err := GetSubscription(s, SubscriptionEntityTask, 20, u)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotNil(t, sub) assert.NotNil(t, sub)
assert.Equal(t, int64(2), sub.ID) assert.Equal(t, int64(2), sub.ID)
}) })
t.Run("task from list", func(t *testing.T) { t.Run("task from project", func(t *testing.T) {
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
s := db.NewSession() s := db.NewSession()
defer s.Close() defer s.Close()
// Task 21 belongs to list 12 which the user has subscribed to // Task 21 belongs to project 12 which the user has subscribed to
sub, err := GetSubscription(s, SubscriptionEntityTask, 21, u) sub, err := GetSubscription(s, SubscriptionEntityTask, 21, u)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotNil(t, sub) assert.NotNil(t, sub)

View File

@ -33,7 +33,7 @@ import (
// TaskAssginee represents an assignment of a user to a task // TaskAssginee represents an assignment of a user to a task
type TaskAssginee struct { type TaskAssginee struct {
ID int64 `xorm:"bigint autoincr not null unique pk" json:"-"` ID int64 `xorm:"bigint autoincr not null unique pk" json:"-"`
TaskID int64 `xorm:"bigint INDEX not null" json:"-" param:"listtask"` TaskID int64 `xorm:"bigint INDEX not null" json:"-" param:"projecttask"`
UserID int64 `xorm:"bigint INDEX not null" json:"user_id" param:"user"` UserID int64 `xorm:"bigint INDEX not null" json:"user_id" param:"user"`
Created time.Time `xorm:"created not null"` Created time.Time `xorm:"created not null"`
@ -102,10 +102,10 @@ func (t *Task) updateTaskAssignees(s *xorm.Session, assignees []*user.User, doer
for _, oldAssignee := range t.Assignees { for _, oldAssignee := range t.Assignees {
found = false found = false
if newAssignees[oldAssignee.ID] != nil { if newAssignees[oldAssignee.ID] != nil {
found = true // If a new assignee is already in the list with old assignees found = true // If a new assignee is already in the project with old assignees
} }
// Put all assignees which are only on the old list to the trash // Put all assignees which are only on the old project to the trash
if !found { if !found {
assigneesToDelete = append(assigneesToDelete, oldAssignee.ID) assigneesToDelete = append(assigneesToDelete, oldAssignee.ID)
} }
@ -123,8 +123,8 @@ func (t *Task) updateTaskAssignees(s *xorm.Session, assignees []*user.User, doer
} }
} }
// Get the list to perform later checks // Get the project to perform later checks
list, err := GetListSimpleByID(s, t.ListID) project, err := GetProjectSimpleByID(s, t.ProjectID)
if err != nil { if err != nil {
return return
} }
@ -138,7 +138,7 @@ func (t *Task) updateTaskAssignees(s *xorm.Session, assignees []*user.User, doer
} }
// Add the new assignee // Add the new assignee
err = t.addNewAssigneeByID(s, u.ID, list, doer) err = t.addNewAssigneeByID(s, u.ID, project, doer)
if err != nil { if err != nil {
return err return err
} }
@ -146,7 +146,7 @@ func (t *Task) updateTaskAssignees(s *xorm.Session, assignees []*user.User, doer
t.setTaskAssignees(assignees) t.setTaskAssignees(assignees)
err = updateListLastUpdated(s, &List{ID: t.ListID}) err = updateProjectLastUpdated(s, &Project{ID: t.ProjectID})
return return
} }
@ -178,7 +178,7 @@ func (la *TaskAssginee) Delete(s *xorm.Session, a web.Auth) (err error) {
return err return err
} }
err = updateListByTaskID(s, la.TaskID) err = updateProjectByTaskID(s, la.TaskID)
if err != nil { if err != nil {
return err return err
} }
@ -193,7 +193,7 @@ func (la *TaskAssginee) Delete(s *xorm.Session, a web.Auth) (err error) {
// Create adds a new assignee to a task // Create adds a new assignee to a task
// @Summary Add a new assignee to a task // @Summary Add a new assignee to a task
// @Description Adds a new assignee to a task. The assignee needs to have access to the list, the doer must be able to edit this task. // @Description Adds a new assignee to a task. The assignee needs to have access to the project, the doer must be able to edit this task.
// @tags assignees // @tags assignees
// @Accept json // @Accept json
// @Produce json // @Produce json
@ -206,28 +206,28 @@ func (la *TaskAssginee) Delete(s *xorm.Session, a web.Auth) (err error) {
// @Router /tasks/{taskID}/assignees [put] // @Router /tasks/{taskID}/assignees [put]
func (la *TaskAssginee) Create(s *xorm.Session, a web.Auth) (err error) { func (la *TaskAssginee) Create(s *xorm.Session, a web.Auth) (err error) {
// Get the list to perform later checks // Get the project to perform later checks
list, err := GetListSimplByTaskID(s, la.TaskID) project, err := GetProjectSimplByTaskID(s, la.TaskID)
if err != nil { if err != nil {
return return
} }
task := &Task{ID: la.TaskID} task := &Task{ID: la.TaskID}
return task.addNewAssigneeByID(s, la.UserID, list, a) return task.addNewAssigneeByID(s, la.UserID, project, a)
} }
func (t *Task) addNewAssigneeByID(s *xorm.Session, newAssigneeID int64, list *List, auth web.Auth) (err error) { func (t *Task) addNewAssigneeByID(s *xorm.Session, newAssigneeID int64, project *Project, auth web.Auth) (err error) {
// Check if the user exists and has access to the list // Check if the user exists and has access to the project
newAssignee, err := user.GetUserByID(s, newAssigneeID) newAssignee, err := user.GetUserByID(s, newAssigneeID)
if err != nil { if err != nil {
return err return err
} }
canRead, _, err := list.CanRead(s, newAssignee) canRead, _, err := project.CanRead(s, newAssignee)
if err != nil { if err != nil {
return err return err
} }
if !canRead { if !canRead {
return ErrUserDoesNotHaveAccessToList{list.ID, newAssigneeID} return ErrUserDoesNotHaveAccessToProject{project.ID, newAssigneeID}
} }
exist, err := s. exist, err := s.
@ -261,7 +261,7 @@ func (t *Task) addNewAssigneeByID(s *xorm.Session, newAssigneeID int64, list *Li
return err return err
} }
err = updateListLastUpdated(s, &List{ID: t.ListID}) err = updateProjectLastUpdated(s, &Project{ID: t.ProjectID})
return return
} }
@ -280,7 +280,7 @@ func (t *Task) addNewAssigneeByID(s *xorm.Session, newAssigneeID int64, list *Li
// @Failure 500 {object} models.Message "Internal error" // @Failure 500 {object} models.Message "Internal error"
// @Router /tasks/{taskID}/assignees [get] // @Router /tasks/{taskID}/assignees [get]
func (la *TaskAssginee) ReadAll(s *xorm.Session, a web.Auth, search string, page int, perPage int) (result interface{}, resultCount int, numberOfTotalItems int64, err error) { func (la *TaskAssginee) ReadAll(s *xorm.Session, a web.Auth, search string, page int, perPage int) (result interface{}, resultCount int, numberOfTotalItems int64, err error) {
task, err := GetListSimplByTaskID(s, la.TaskID) task, err := GetProjectSimplByTaskID(s, la.TaskID)
if err != nil { if err != nil {
return nil, 0, 0, err return nil, 0, 0, err
} }
@ -319,9 +319,9 @@ func (la *TaskAssginee) ReadAll(s *xorm.Session, a web.Auth, search string, page
// BulkAssignees is a helper struct used to update multiple assignees at once. // BulkAssignees is a helper struct used to update multiple assignees at once.
type BulkAssignees struct { type BulkAssignees struct {
// A list with all assignees // A project with all assignees
Assignees []*user.User `json:"assignees"` Assignees []*user.User `json:"assignees"`
TaskID int64 `json:"-" param:"listtask"` TaskID int64 `json:"-" param:"projecttask"`
web.CRUDable `json:"-"` web.CRUDable `json:"-"`
web.Rights `json:"-"` web.Rights `json:"-"`
@ -329,7 +329,7 @@ type BulkAssignees struct {
// Create adds new assignees to a task // Create adds new assignees to a task
// @Summary Add multiple new assignees to a task // @Summary Add multiple new assignees to a task
// @Description Adds multiple new assignees to a task. The assignee needs to have access to the list, the doer must be able to edit this task. Every user not in the list will be unassigned from the task, pass an empty array to unassign everyone. // @Description Adds multiple new assignees to a task. The assignee needs to have access to the project, the doer must be able to edit this task. Every user not in the project will be unassigned from the task, pass an empty array to unassign everyone.
// @tags assignees // @tags assignees
// @Accept json // @Accept json
// @Produce json // @Produce json

View File

@ -37,10 +37,10 @@ func (la *TaskAssginee) CanDelete(s *xorm.Session, a web.Auth) (bool, error) {
} }
func canDoTaskAssingee(s *xorm.Session, taskID int64, a web.Auth) (bool, error) { func canDoTaskAssingee(s *xorm.Session, taskID int64, a web.Auth) (bool, error) {
// Check if the current user can edit the list // Check if the current user can edit the project
list, err := GetListSimplByTaskID(s, taskID) project, err := GetProjectSimplByTaskID(s, taskID)
if err != nil { if err != nil {
return false, err return false, err
} }
return list.CanUpdate(s, a) return project.CanUpdate(s, a)
} }

View File

@ -112,7 +112,7 @@ func (ta *TaskAttachment) ReadOne(s *xorm.Session, a web.Auth) (err error) {
return return
} }
// ReadAll returns a list with all attachments // ReadAll returns a project with all attachments
// @Summary Get all attachments for one task. // @Summary Get all attachments for one task.
// @Description Get all task attachments for one task. // @Description Get all task attachments for one task.
// @tags task // @tags task

View File

@ -24,8 +24,8 @@ import (
// TaskCollection is a struct used to hold filter details and not clutter the Task struct with information not related to actual tasks. // TaskCollection is a struct used to hold filter details and not clutter the Task struct with information not related to actual tasks.
type TaskCollection struct { type TaskCollection struct {
ListID int64 `param:"list" json:"-"` ProjectID int64 `param:"project" json:"-"`
Lists []*List `json:"-"` Projects []*Project `json:"-"`
// The query parameter to sort by. This is for ex. done, priority, etc. // The query parameter to sort by. This is for ex. done, priority, etc.
SortBy []string `query:"sort_by" json:"sort_by"` SortBy []string `query:"sort_by" json:"sort_by"`
@ -62,7 +62,7 @@ func validateTaskField(fieldName string) error {
taskPropertyDoneAt, taskPropertyDoneAt,
taskPropertyDueDate, taskPropertyDueDate,
taskPropertyCreatedByID, taskPropertyCreatedByID,
taskPropertyListID, taskPropertyProjectID,
taskPropertyRepeatAfter, taskPropertyRepeatAfter,
taskPropertyPriority, taskPropertyPriority,
taskPropertyStartDate, taskPropertyStartDate,
@ -120,16 +120,16 @@ func getTaskFilterOptsFromCollection(tf *TaskCollection) (opts *taskOptions, err
} }
// ReadAll gets all tasks for a collection // ReadAll gets all tasks for a collection
// @Summary Get tasks in a list // @Summary Get tasks in a project
// @Description Returns all tasks for the current list. // @Description Returns all tasks for the current project.
// @tags task // @tags task
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param listID path int true "The list ID." // @Param projectID path int true "The project ID."
// @Param page query int false "The page number. Used for pagination. If not provided, the first page of results is returned." // @Param page query int false "The page number. Used for pagination. If not provided, the first page of results is returned."
// @Param per_page query int false "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page." // @Param per_page query int false "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page."
// @Param s query string false "Search tasks by task text." // @Param s query string false "Search tasks by task text."
// @Param sort_by query string false "The sorting parameter. You can pass this multiple times to get the tasks ordered by multiple different parametes, along with `order_by`. Possible values to sort by are `id`, `title`, `description`, `done`, `done_at`, `due_date`, `created_by_id`, `list_id`, `repeat_after`, `priority`, `start_date`, `end_date`, `hex_color`, `percent_done`, `uid`, `created`, `updated`. Default is `id`." // @Param sort_by query string false "The sorting parameter. You can pass this multiple times to get the tasks ordered by multiple different parametes, along with `order_by`. Possible values to sort by are `id`, `title`, `description`, `done`, `done_at`, `due_date`, `created_by_id`, `project_id`, `repeat_after`, `priority`, `start_date`, `end_date`, `hex_color`, `percent_done`, `uid`, `created`, `updated`. Default is `id`."
// @Param order_by query string false "The ordering parameter. Possible values to order by are `asc` or `desc`. Default is `asc`." // @Param order_by query string false "The ordering parameter. Possible values to order by are `asc` or `desc`. Default is `asc`."
// @Param filter_by query string false "The name of the field to filter by. Allowed values are all task properties. Task properties which are their own object require passing in the id of that entity. Accepts an array for multiple filters which will be chanied together, all supplied filter must match." // @Param filter_by query string false "The name of the field to filter by. Allowed values are all task properties. Task properties which are their own object require passing in the id of that entity. Accepts an array for multiple filters which will be chanied together, all supplied filter must match."
// @Param filter_value query string false "The value to filter for. You can use [grafana](https://grafana.com/docs/grafana/latest/dashboards/time-range-controls)- or [elasticsearch](https://www.elastic.co/guide/en/elasticsearch/reference/7.3/common-options.html#date-math)-style relative dates for all date fields like `due_date`, `start_date`, `end_date`, etc." // @Param filter_value query string false "The value to filter for. You can use [grafana](https://grafana.com/docs/grafana/latest/dashboards/time-range-controls)- or [elasticsearch](https://www.elastic.co/guide/en/elasticsearch/reference/7.3/common-options.html#date-math)-style relative dates for all date fields like `due_date`, `start_date`, `end_date`, etc."
@ -139,13 +139,13 @@ func getTaskFilterOptsFromCollection(tf *TaskCollection) (opts *taskOptions, err
// @Security JWTKeyAuth // @Security JWTKeyAuth
// @Success 200 {array} models.Task "The tasks" // @Success 200 {array} models.Task "The tasks"
// @Failure 500 {object} models.Message "Internal error" // @Failure 500 {object} models.Message "Internal error"
// @Router /lists/{listID}/tasks [get] // @Router /projects/{projectID}/tasks [get]
func (tf *TaskCollection) ReadAll(s *xorm.Session, a web.Auth, search string, page int, perPage int) (result interface{}, resultCount int, totalItems int64, err error) { func (tf *TaskCollection) ReadAll(s *xorm.Session, a web.Auth, search string, page int, perPage int) (result interface{}, resultCount int, totalItems int64, err error) {
// If the list id is < -1 this means we're dealing with a saved filter - in that case we get and populate the filter // If the project id is < -1 this means we're dealing with a saved filter - in that case we get and populate the filter
// -1 is the favorites list which works as intended // -1 is the favorites project which works as intended
if tf.ListID < -1 { if tf.ProjectID < -1 {
sf, err := getSavedFilterSimpleByID(s, getSavedFilterIDFromListID(tf.ListID)) sf, err := getSavedFilterSimpleByID(s, getSavedFilterIDFromProjectID(tf.ProjectID))
if err != nil { if err != nil {
return nil, 0, 0, err return nil, 0, 0, err
} }
@ -169,19 +169,19 @@ func (tf *TaskCollection) ReadAll(s *xorm.Session, a web.Auth, search string, pa
shareAuth, is := a.(*LinkSharing) shareAuth, is := a.(*LinkSharing)
if is { if is {
list, err := GetListSimpleByID(s, shareAuth.ListID) project, err := GetProjectSimpleByID(s, shareAuth.ProjectID)
if err != nil { if err != nil {
return nil, 0, 0, err return nil, 0, 0, err
} }
return getTasksForLists(s, []*List{list}, a, taskopts) return getTasksForProjects(s, []*Project{project}, a, taskopts)
} }
// If the list ID is not set, we get all tasks for the user. // If the project ID is not set, we get all tasks for the user.
// This allows to use this function in Task.ReadAll with a possibility to deprecate the latter at some point. // This allows to use this function in Task.ReadAll with a possibility to deprecate the latter at some point.
if tf.ListID == 0 { if tf.ProjectID == 0 {
tf.Lists, _, _, err = getRawListsForUser( tf.Projects, _, _, err = getRawProjectsForUser(
s, s,
&listOptions{ &projectOptions{
user: &user.User{ID: a.GetID()}, user: &user.User{ID: a.GetID()},
page: -1, page: -1,
}, },
@ -190,17 +190,17 @@ func (tf *TaskCollection) ReadAll(s *xorm.Session, a web.Auth, search string, pa
return nil, 0, 0, err return nil, 0, 0, err
} }
} else { } else {
// Check the list exists and the user has acess on it // Check the project exists and the user has acess on it
list := &List{ID: tf.ListID} project := &Project{ID: tf.ProjectID}
canRead, _, err := list.CanRead(s, a) canRead, _, err := project.CanRead(s, a)
if err != nil { if err != nil {
return nil, 0, 0, err return nil, 0, 0, err
} }
if !canRead { if !canRead {
return nil, 0, 0, ErrUserDoesNotHaveAccessToList{ListID: tf.ListID} return nil, 0, 0, ErrUserDoesNotHaveAccessToProject{ProjectID: tf.ProjectID}
} }
tf.Lists = []*List{{ID: tf.ListID}} tf.Projects = []*Project{{ID: tf.ProjectID}}
} }
return getTasksForLists(s, tf.Lists, a, taskopts) return getTasksForProjects(s, tf.Projects, a, taskopts)
} }

View File

@ -33,7 +33,7 @@ const (
taskPropertyDoneAt string = "done_at" taskPropertyDoneAt string = "done_at"
taskPropertyDueDate string = "due_date" taskPropertyDueDate string = "due_date"
taskPropertyCreatedByID string = "created_by_id" taskPropertyCreatedByID string = "created_by_id"
taskPropertyListID string = "list_id" taskPropertyProjectID string = "project_id"
taskPropertyRepeatAfter string = "repeat_after" taskPropertyRepeatAfter string = "repeat_after"
taskPropertyPriority string = "priority" taskPropertyPriority string = "priority"
taskPropertyStartDate string = "start_date" taskPropertyStartDate string = "start_date"

View File

@ -50,7 +50,7 @@ func TestSortParamValidation(t *testing.T) {
taskPropertyDoneAt, taskPropertyDoneAt,
taskPropertyDueDate, taskPropertyDueDate,
taskPropertyCreatedByID, taskPropertyCreatedByID,
taskPropertyListID, taskPropertyProjectID,
taskPropertyRepeatAfter, taskPropertyRepeatAfter,
taskPropertyPriority, taskPropertyPriority,
taskPropertyStartDate, taskPropertyStartDate,

View File

@ -90,7 +90,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
Index: 1, Index: 1,
CreatedByID: 1, CreatedByID: 1,
CreatedBy: user1, CreatedBy: user1,
ListID: 1, ProjectID: 1,
BucketID: 1, BucketID: 1,
IsFavorite: true, IsFavorite: true,
Position: 2, Position: 2,
@ -104,7 +104,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
Title: "task #29 with parent task (1)", Title: "task #29 with parent task (1)",
Index: 14, Index: 14,
CreatedByID: 1, CreatedByID: 1,
ListID: 1, ProjectID: 1,
BucketID: 1, BucketID: 1,
Created: time.Unix(1543626724, 0).In(loc), Created: time.Unix(1543626724, 0).In(loc),
Updated: time.Unix(1543626724, 0).In(loc), Updated: time.Unix(1543626724, 0).In(loc),
@ -162,7 +162,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
Done: true, Done: true,
CreatedByID: 1, CreatedByID: 1,
CreatedBy: user1, CreatedBy: user1,
ListID: 1, ProjectID: 1,
BucketID: 1, BucketID: 1,
Position: 4, Position: 4,
Labels: []*Label{ Labels: []*Label{
@ -182,7 +182,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
Index: 3, Index: 3,
CreatedByID: 1, CreatedByID: 1,
CreatedBy: user1, CreatedBy: user1,
ListID: 1, ProjectID: 1,
RelatedTasks: map[RelationKind][]*Task{}, RelatedTasks: map[RelationKind][]*Task{},
Created: time.Unix(1543626724, 0).In(loc), Created: time.Unix(1543626724, 0).In(loc),
Updated: time.Unix(1543626724, 0).In(loc), Updated: time.Unix(1543626724, 0).In(loc),
@ -196,7 +196,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
Index: 4, Index: 4,
CreatedByID: 1, CreatedByID: 1,
CreatedBy: user1, CreatedBy: user1,
ListID: 1, ProjectID: 1,
RelatedTasks: map[RelationKind][]*Task{}, RelatedTasks: map[RelationKind][]*Task{},
Created: time.Unix(1543626724, 0).In(loc), Created: time.Unix(1543626724, 0).In(loc),
Updated: time.Unix(1543626724, 0).In(loc), Updated: time.Unix(1543626724, 0).In(loc),
@ -210,7 +210,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
Index: 5, Index: 5,
CreatedByID: 1, CreatedByID: 1,
CreatedBy: user1, CreatedBy: user1,
ListID: 1, ProjectID: 1,
RelatedTasks: map[RelationKind][]*Task{}, RelatedTasks: map[RelationKind][]*Task{},
Created: time.Unix(1543626724, 0).In(loc), Created: time.Unix(1543626724, 0).In(loc),
Updated: time.Unix(1543626724, 0).In(loc), Updated: time.Unix(1543626724, 0).In(loc),
@ -224,7 +224,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
Index: 6, Index: 6,
CreatedByID: 1, CreatedByID: 1,
CreatedBy: user1, CreatedBy: user1,
ListID: 1, ProjectID: 1,
RelatedTasks: map[RelationKind][]*Task{}, RelatedTasks: map[RelationKind][]*Task{},
Created: time.Unix(1543626724, 0).In(loc), Created: time.Unix(1543626724, 0).In(loc),
Updated: time.Unix(1543626724, 0).In(loc), Updated: time.Unix(1543626724, 0).In(loc),
@ -238,7 +238,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
Index: 7, Index: 7,
CreatedByID: 1, CreatedByID: 1,
CreatedBy: user1, CreatedBy: user1,
ListID: 1, ProjectID: 1,
RelatedTasks: map[RelationKind][]*Task{}, RelatedTasks: map[RelationKind][]*Task{},
Created: time.Unix(1543626724, 0).In(loc), Created: time.Unix(1543626724, 0).In(loc),
Updated: time.Unix(1543626724, 0).In(loc), Updated: time.Unix(1543626724, 0).In(loc),
@ -252,7 +252,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
Index: 8, Index: 8,
CreatedByID: 1, CreatedByID: 1,
CreatedBy: user1, CreatedBy: user1,
ListID: 1, ProjectID: 1,
RelatedTasks: map[RelationKind][]*Task{}, RelatedTasks: map[RelationKind][]*Task{},
Created: time.Unix(1543626724, 0).In(loc), Created: time.Unix(1543626724, 0).In(loc),
Updated: time.Unix(1543626724, 0).In(loc), Updated: time.Unix(1543626724, 0).In(loc),
@ -266,7 +266,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
Index: 9, Index: 9,
CreatedByID: 1, CreatedByID: 1,
CreatedBy: user1, CreatedBy: user1,
ListID: 1, ProjectID: 1,
RelatedTasks: map[RelationKind][]*Task{}, RelatedTasks: map[RelationKind][]*Task{},
BucketID: 1, BucketID: 1,
Created: time.Unix(1543626724, 0).In(loc), Created: time.Unix(1543626724, 0).In(loc),
@ -281,7 +281,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
Index: 10, Index: 10,
CreatedByID: 1, CreatedByID: 1,
CreatedBy: user1, CreatedBy: user1,
ListID: 1, ProjectID: 1,
RelatedTasks: map[RelationKind][]*Task{}, RelatedTasks: map[RelationKind][]*Task{},
BucketID: 1, BucketID: 1,
Created: time.Unix(1543626724, 0).In(loc), Created: time.Unix(1543626724, 0).In(loc),
@ -294,7 +294,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
Index: 11, Index: 11,
CreatedByID: 1, CreatedByID: 1,
CreatedBy: user1, CreatedBy: user1,
ListID: 1, ProjectID: 1,
RelatedTasks: map[RelationKind][]*Task{}, RelatedTasks: map[RelationKind][]*Task{},
BucketID: 1, BucketID: 1,
Created: time.Unix(1543626724, 0).In(loc), Created: time.Unix(1543626724, 0).In(loc),
@ -307,7 +307,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
Index: 12, Index: 12,
CreatedByID: 1, CreatedByID: 1,
CreatedBy: user1, CreatedBy: user1,
ListID: 1, ProjectID: 1,
RelatedTasks: map[RelationKind][]*Task{}, RelatedTasks: map[RelationKind][]*Task{},
BucketID: 1, BucketID: 1,
Created: time.Unix(1543626724, 0).In(loc), Created: time.Unix(1543626724, 0).In(loc),
@ -320,7 +320,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
Index: 1, Index: 1,
CreatedByID: 6, CreatedByID: 6,
CreatedBy: user6, CreatedBy: user6,
ListID: 6, ProjectID: 6,
IsFavorite: true, IsFavorite: true,
RelatedTasks: map[RelationKind][]*Task{}, RelatedTasks: map[RelationKind][]*Task{},
BucketID: 6, BucketID: 6,
@ -334,7 +334,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
Index: 1, Index: 1,
CreatedByID: 6, CreatedByID: 6,
CreatedBy: user6, CreatedBy: user6,
ListID: 7, ProjectID: 7,
RelatedTasks: map[RelationKind][]*Task{}, RelatedTasks: map[RelationKind][]*Task{},
BucketID: 7, BucketID: 7,
Created: time.Unix(1543626724, 0).In(loc), Created: time.Unix(1543626724, 0).In(loc),
@ -347,7 +347,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
Index: 1, Index: 1,
CreatedByID: 6, CreatedByID: 6,
CreatedBy: user6, CreatedBy: user6,
ListID: 8, ProjectID: 8,
RelatedTasks: map[RelationKind][]*Task{}, RelatedTasks: map[RelationKind][]*Task{},
BucketID: 8, BucketID: 8,
Created: time.Unix(1543626724, 0).In(loc), Created: time.Unix(1543626724, 0).In(loc),
@ -360,7 +360,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
Index: 1, Index: 1,
CreatedByID: 6, CreatedByID: 6,
CreatedBy: user6, CreatedBy: user6,
ListID: 9, ProjectID: 9,
RelatedTasks: map[RelationKind][]*Task{}, RelatedTasks: map[RelationKind][]*Task{},
BucketID: 9, BucketID: 9,
Created: time.Unix(1543626724, 0).In(loc), Created: time.Unix(1543626724, 0).In(loc),
@ -373,7 +373,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
Index: 1, Index: 1,
CreatedByID: 6, CreatedByID: 6,
CreatedBy: user6, CreatedBy: user6,
ListID: 10, ProjectID: 10,
RelatedTasks: map[RelationKind][]*Task{}, RelatedTasks: map[RelationKind][]*Task{},
BucketID: 10, BucketID: 10,
Created: time.Unix(1543626724, 0).In(loc), Created: time.Unix(1543626724, 0).In(loc),
@ -386,7 +386,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
Index: 1, Index: 1,
CreatedByID: 6, CreatedByID: 6,
CreatedBy: user6, CreatedBy: user6,
ListID: 11, ProjectID: 11,
RelatedTasks: map[RelationKind][]*Task{}, RelatedTasks: map[RelationKind][]*Task{},
BucketID: 11, BucketID: 11,
Created: time.Unix(1543626724, 0).In(loc), Created: time.Unix(1543626724, 0).In(loc),
@ -399,7 +399,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
Index: 1, Index: 1,
CreatedByID: 6, CreatedByID: 6,
CreatedBy: user6, CreatedBy: user6,
ListID: 12, ProjectID: 12,
RelatedTasks: map[RelationKind][]*Task{}, RelatedTasks: map[RelationKind][]*Task{},
BucketID: 12, BucketID: 12,
Created: time.Unix(1543626724, 0).In(loc), Created: time.Unix(1543626724, 0).In(loc),
@ -412,7 +412,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
Index: 1, Index: 1,
CreatedByID: 6, CreatedByID: 6,
CreatedBy: user6, CreatedBy: user6,
ListID: 13, ProjectID: 13,
RelatedTasks: map[RelationKind][]*Task{}, RelatedTasks: map[RelationKind][]*Task{},
BucketID: 13, BucketID: 13,
Created: time.Unix(1543626724, 0).In(loc), Created: time.Unix(1543626724, 0).In(loc),
@ -425,7 +425,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
Index: 1, Index: 1,
CreatedByID: 6, CreatedByID: 6,
CreatedBy: user6, CreatedBy: user6,
ListID: 14, ProjectID: 14,
RelatedTasks: map[RelationKind][]*Task{}, RelatedTasks: map[RelationKind][]*Task{},
BucketID: 14, BucketID: 14,
Created: time.Unix(1543626724, 0).In(loc), Created: time.Unix(1543626724, 0).In(loc),
@ -438,7 +438,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
Index: 1, Index: 1,
CreatedByID: 6, CreatedByID: 6,
CreatedBy: user6, CreatedBy: user6,
ListID: 15, ProjectID: 15,
RelatedTasks: map[RelationKind][]*Task{}, RelatedTasks: map[RelationKind][]*Task{},
BucketID: 15, BucketID: 15,
Created: time.Unix(1543626724, 0).In(loc), Created: time.Unix(1543626724, 0).In(loc),
@ -451,7 +451,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
Index: 1, Index: 1,
CreatedByID: 6, CreatedByID: 6,
CreatedBy: user6, CreatedBy: user6,
ListID: 16, ProjectID: 16,
RelatedTasks: map[RelationKind][]*Task{}, RelatedTasks: map[RelationKind][]*Task{},
BucketID: 16, BucketID: 16,
Created: time.Unix(1543626724, 0).In(loc), Created: time.Unix(1543626724, 0).In(loc),
@ -464,7 +464,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
Index: 1, Index: 1,
CreatedByID: 6, CreatedByID: 6,
CreatedBy: user6, CreatedBy: user6,
ListID: 17, ProjectID: 17,
RelatedTasks: map[RelationKind][]*Task{}, RelatedTasks: map[RelationKind][]*Task{},
BucketID: 17, BucketID: 17,
Created: time.Unix(1543626724, 0).In(loc), Created: time.Unix(1543626724, 0).In(loc),
@ -481,7 +481,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
time.Unix(1543626724, 0).In(loc), time.Unix(1543626724, 0).In(loc),
time.Unix(1543626824, 0).In(loc), time.Unix(1543626824, 0).In(loc),
}, },
ListID: 1, ProjectID: 1,
BucketID: 1, BucketID: 1,
RelatedTasks: map[RelationKind][]*Task{}, RelatedTasks: map[RelationKind][]*Task{},
Created: time.Unix(1543626724, 0).In(loc), Created: time.Unix(1543626724, 0).In(loc),
@ -494,7 +494,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
Index: 13, Index: 13,
CreatedByID: 1, CreatedByID: 1,
CreatedBy: user1, CreatedBy: user1,
ListID: 1, ProjectID: 1,
RelatedTasks: map[RelationKind][]*Task{}, RelatedTasks: map[RelationKind][]*Task{},
RepeatAfter: 3600, RepeatAfter: 3600,
BucketID: 1, BucketID: 1,
@ -508,7 +508,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
Index: 14, Index: 14,
CreatedByID: 1, CreatedByID: 1,
CreatedBy: user1, CreatedBy: user1,
ListID: 1, ProjectID: 1,
RelatedTasks: map[RelationKind][]*Task{ RelatedTasks: map[RelationKind][]*Task{
RelationKindParenttask: { RelationKindParenttask: {
{ {
@ -517,7 +517,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
Description: "Lorem Ipsum", Description: "Lorem Ipsum",
Index: 1, Index: 1,
CreatedByID: 1, CreatedByID: 1,
ListID: 1, ProjectID: 1,
IsFavorite: true, IsFavorite: true,
Created: time.Unix(1543626724, 0).In(loc), Created: time.Unix(1543626724, 0).In(loc),
Updated: time.Unix(1543626724, 0).In(loc), Updated: time.Unix(1543626724, 0).In(loc),
@ -537,7 +537,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
Index: 15, Index: 15,
CreatedByID: 1, CreatedByID: 1,
CreatedBy: user1, CreatedBy: user1,
ListID: 1, ProjectID: 1,
Assignees: []*user.User{ Assignees: []*user.User{
user1, user1,
user2, user2,
@ -555,7 +555,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
HexColor: "f0f0f0", HexColor: "f0f0f0",
CreatedByID: 1, CreatedByID: 1,
CreatedBy: user1, CreatedBy: user1,
ListID: 1, ProjectID: 1,
RelatedTasks: map[RelationKind][]*Task{}, RelatedTasks: map[RelationKind][]*Task{},
BucketID: 1, BucketID: 1,
Created: time.Unix(1543626724, 0).In(loc), Created: time.Unix(1543626724, 0).In(loc),
@ -568,7 +568,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
Index: 1, Index: 1,
CreatedByID: 1, CreatedByID: 1,
CreatedBy: user1, CreatedBy: user1,
ListID: 3, ProjectID: 3,
RelatedTasks: map[RelationKind][]*Task{}, RelatedTasks: map[RelationKind][]*Task{},
BucketID: 21, BucketID: 21,
Created: time.Unix(1543626724, 0).In(loc), Created: time.Unix(1543626724, 0).In(loc),
@ -581,7 +581,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
Index: 17, Index: 17,
CreatedByID: 1, CreatedByID: 1,
CreatedBy: user1, CreatedBy: user1,
ListID: 1, ProjectID: 1,
PercentDone: 0.5, PercentDone: 0.5,
RelatedTasks: map[RelationKind][]*Task{}, RelatedTasks: map[RelationKind][]*Task{},
BucketID: 1, BucketID: 1,
@ -590,10 +590,10 @@ func TestTaskCollection_ReadAll(t *testing.T) {
} }
type fields struct { type fields struct {
ListID int64 ProjectID int64
Lists []*List Projects []*Project
SortBy []string // Is a string, since this is the place where a query string comes from the user SortBy []string // Is a string, since this is the place where a query string comes from the user
OrderBy []string OrderBy []string
FilterBy []string FilterBy []string
FilterValue []string FilterValue []string
@ -876,12 +876,12 @@ func TestTaskCollection_ReadAll(t *testing.T) {
name: "favorited tasks", name: "favorited tasks",
args: defaultArgs, args: defaultArgs,
fields: fields{ fields: fields{
ListID: FavoritesPseudoList.ID, ProjectID: FavoritesPseudoProject.ID,
}, },
want: []*Task{ want: []*Task{
task1, task1,
task15, task15,
// Task 34 is also a favorite, but on a list user 1 has no access to. // Task 34 is also a favorite, but on a project user 1 has no access to.
}, },
}, },
{ {
@ -1042,9 +1042,9 @@ func TestTaskCollection_ReadAll(t *testing.T) {
wantErr: false, wantErr: false,
}, },
{ {
name: "filter list", name: "filter project",
fields: fields{ fields: fields{
FilterBy: []string{"list_id"}, FilterBy: []string{"project_id"},
FilterValue: []string{"6"}, FilterValue: []string{"6"},
FilterComparator: []string{"equals"}, FilterComparator: []string{"equals"},
}, },
@ -1200,9 +1200,9 @@ func TestTaskCollection_ReadAll(t *testing.T) {
{ {
name: "saved filter with sort order", name: "saved filter with sort order",
fields: fields{ fields: fields{
ListID: -2, ProjectID: -2,
SortBy: []string{"title", "id"}, SortBy: []string{"title", "id"},
OrderBy: []string{"desc", "asc"}, OrderBy: []string{"desc", "asc"},
}, },
args: args{ args: args{
a: &user.User{ID: 1}, a: &user.User{ID: 1},
@ -1224,9 +1224,9 @@ func TestTaskCollection_ReadAll(t *testing.T) {
defer s.Close() defer s.Close()
lt := &TaskCollection{ lt := &TaskCollection{
ListID: tt.fields.ListID, ProjectID: tt.fields.ProjectID,
SortBy: tt.fields.SortBy, SortBy: tt.fields.SortBy,
OrderBy: tt.fields.OrderBy, OrderBy: tt.fields.OrderBy,
FilterBy: tt.fields.FilterBy, FilterBy: tt.fields.FilterBy,
FilterValue: tt.fields.FilterValue, FilterValue: tt.fields.FilterValue,

View File

@ -74,7 +74,7 @@ func TestTaskComment_Create(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
tc := &TaskComment{ tc := &TaskComment{
Comment: "Lorem Ipsum @user2", Comment: "Lorem Ipsum @user2",
TaskID: 32, // user2 has access to the list that task belongs to TaskID: 32, // user2 has access to the project that task belongs to
} }
err = tc.Create(s, u) err = tc.Create(s, u)
assert.NoError(t, err) assert.NoError(t, err)

View File

@ -80,7 +80,7 @@ func getUndoneOverdueTasks(s *xorm.Session, now time.Time) (usersWithTasks map[i
tzs[t.User.Timezone] = tz tzs[t.User.Timezone] = tz
} }
// If it is time for that current user, add the task to their list of overdue tasks // If it is time for that current user, add the task to their project of overdue tasks
tm, err := time.Parse("15:04", t.User.OverdueTasksRemindersTime) tm, err := time.Parse("15:04", t.User.OverdueTasksRemindersTime)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -110,7 +110,7 @@ type RelatedTaskMap map[RelationKind][]*Task
// Create creates a new task relation // Create creates a new task relation
// @Summary Create a new relation between two tasks // @Summary Create a new relation between two tasks
// @Description Creates a new relation between two tasks. The user needs to have update rights on the base task and at least read rights on the other task. Both tasks do not need to be on the same list. Take a look at the docs for available task relation kinds. // @Description Creates a new relation between two tasks. The user needs to have update rights on the base task and at least read rights on the other task. Both tasks do not need to be on the same project. Take a look at the docs for available task relation kinds.
// @tags task // @tags task
// @Accept json // @Accept json
// @Produce json // @Produce json

View File

@ -42,7 +42,7 @@ func (rel *TaskRelation) CanCreate(s *xorm.Session, a web.Auth) (bool, error) {
return false, err return false, err
} }
// We explicitly don't check if the two tasks are on the same list. // We explicitly don't check if the two tasks are on the same project.
otherTask := &Task{ID: rel.OtherTaskID} otherTask := &Task{ID: rel.OtherTaskID}
has, _, err = otherTask.CanRead(s, a) has, _, err = otherTask.CanRead(s, a)
if err != nil { if err != nil {

View File

@ -46,7 +46,7 @@ func TestTaskRelation_Create(t *testing.T) {
"created_by_id": 1, "created_by_id": 1,
}, false) }, false)
}) })
t.Run("Two Tasks In Different Lists", func(t *testing.T) { t.Run("Two Tasks In Different Projects", func(t *testing.T) {
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
s := db.NewSession() s := db.NewSession()
defer s.Close() defer s.Close()
@ -150,7 +150,7 @@ func TestTaskRelation_CanCreate(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.True(t, can) assert.True(t, can)
}) })
t.Run("Two tasks on different lists", func(t *testing.T) { t.Run("Two tasks on different projects", func(t *testing.T) {
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
s := db.NewSession() s := db.NewSession()
defer s.Close() defer s.Close()

View File

@ -47,11 +47,11 @@ const (
TaskRepeatModeFromCurrentDate TaskRepeatModeFromCurrentDate
) )
// Task represents an task in a todolist // Task represents an task in a project
type Task struct { type Task struct {
// The unique, numeric id of this task. // The unique, numeric id of this task.
ID int64 `xorm:"bigint autoincr not null unique pk" json:"id" param:"listtask"` ID int64 `xorm:"bigint autoincr not null unique pk" json:"id" param:"projecttask"`
// The task text. This is what you'll see in the list. // The task text. This is what you'll see in the project.
Title string `xorm:"TEXT not null" json:"title" valid:"minstringlength(1)" minLength:"1"` Title string `xorm:"TEXT not null" json:"title" valid:"minstringlength(1)" minLength:"1"`
// The task description. // The task description.
Description string `xorm:"longtext null" json:"description"` Description string `xorm:"longtext null" json:"description"`
@ -63,8 +63,8 @@ type Task struct {
DueDate time.Time `xorm:"DATETIME INDEX null 'due_date'" json:"due_date"` DueDate time.Time `xorm:"DATETIME INDEX null 'due_date'" json:"due_date"`
// An array of datetimes when the user wants to be reminded of the task. // An array of datetimes when the user wants to be reminded of the task.
Reminders []time.Time `xorm:"-" json:"reminder_dates"` Reminders []time.Time `xorm:"-" json:"reminder_dates"`
// The list this task belongs to. // The project this task belongs to.
ListID int64 `xorm:"bigint INDEX not null" json:"list_id" param:"list"` ProjectID int64 `xorm:"bigint INDEX not null" json:"project_id" param:"project"`
// An amount in seconds this task repeats itself. If this is set, when marking the task as done, it will mark itself as "undone" and then increase all remindes and the due date by its amount. // An amount in seconds this task repeats itself. If this is set, when marking the task as done, it will mark itself as "undone" and then increase all remindes and the due date by its amount.
RepeatAfter int64 `xorm:"bigint INDEX null" json:"repeat_after" valid:"range(0|9223372036854775807)"` RepeatAfter int64 `xorm:"bigint INDEX null" json:"repeat_after" valid:"range(0|9223372036854775807)"`
// Can have three possible values which will trigger when the task is marked as done: 0 = repeats after the amount specified in repeat_after, 1 = repeats all dates each months (ignoring repeat_after), 3 = repeats from the current date rather than the last set date. // Can have three possible values which will trigger when the task is marked as done: 0 = repeats after the amount specified in repeat_after, 1 = repeats all dates each months (ignoring repeat_after), 3 = repeats from the current date rather than the last set date.
@ -84,9 +84,9 @@ type Task struct {
// Determines how far a task is left from being done // Determines how far a task is left from being done
PercentDone float64 `xorm:"DOUBLE null" json:"percent_done"` PercentDone float64 `xorm:"DOUBLE null" json:"percent_done"`
// The task identifier, based on the list identifier and the task's index // The task identifier, based on the project identifier and the task's index
Identifier string `xorm:"-" json:"identifier"` Identifier string `xorm:"-" json:"identifier"`
// The task index, calculated per list // The task index, calculated per project
Index int64 `xorm:"bigint not null default 0" json:"index"` Index int64 `xorm:"bigint not null default 0" json:"index"`
// The UID is currently not used for anything other than caldav, which is why we don't expose it over json // The UID is currently not used for anything other than caldav, which is why we don't expose it over json
@ -101,7 +101,7 @@ type Task struct {
// If this task has a cover image, the field will return the id of the attachment that is the cover image. // If this task has a cover image, the field will return the id of the attachment that is the cover image.
CoverImageAttachmentID int64 `xorm:"bigint default 0" json:"cover_image_attachment_id"` CoverImageAttachmentID int64 `xorm:"bigint default 0" json:"cover_image_attachment_id"`
// True if a task is a favorite task. Favorite tasks show up in a separate "Important" list. This value depends on the user making the call to the api. // True if a task is a favorite task. Favorite tasks show up in a separate "Important" project. This value depends on the user making the call to the api.
IsFavorite bool `xorm:"-" json:"is_favorite"` IsFavorite bool `xorm:"-" json:"is_favorite"`
// The subscription status for the user reading this task. You can only read this property, use the subscription endpoints to modify it. // The subscription status for the user reading this task. You can only read this property, use the subscription endpoints to modify it.
@ -116,7 +116,7 @@ type Task struct {
// BucketID is the ID of the kanban bucket this task belongs to. // BucketID is the ID of the kanban bucket this task belongs to.
BucketID int64 `xorm:"bigint null" json:"bucket_id"` BucketID int64 `xorm:"bigint null" json:"bucket_id"`
// The position of the task - any task list can be sorted as usual by this parameter. // The position of the task - any task project can be sorted as usual by this parameter.
// When accessing tasks via kanban buckets, this is primarily used to sort them based on a range // When accessing tasks via kanban buckets, this is primarily used to sort them based on a range
// We're using a float64 here to make it possible to put any task within any two other tasks (by changing the number). // We're using a float64 here to make it possible to put any task within any two other tasks (by changing the number).
// You would calculate the new position between two tasks with something like task3.position = (task2.position - task1.position) / 2. // You would calculate the new position between two tasks with something like task3.position = (task2.position - task1.position) / 2.
@ -128,7 +128,7 @@ type Task struct {
// The user who initially created the task. // The user who initially created the task.
CreatedBy *user.User `xorm:"-" json:"created_by" valid:"-"` CreatedBy *user.User `xorm:"-" json:"created_by" valid:"-"`
CreatedByID int64 `xorm:"bigint not null" json:"-"` // ID of the user who put that task on the list CreatedByID int64 `xorm:"bigint not null" json:"-"` // ID of the user who put that task on the project
web.CRUDable `xorm:"-" json:"-"` web.CRUDable `xorm:"-" json:"-"`
web.Rights `xorm:"-" json:"-"` web.Rights `xorm:"-" json:"-"`
@ -139,7 +139,7 @@ type TaskWithComments struct {
Comments []*TaskComment `xorm:"-" json:"comments"` Comments []*TaskComment `xorm:"-" json:"comments"`
} }
// TableName returns the table name for listtasks // TableName returns the table name for tasks
func (*Task) TableName() string { func (*Task) TableName() string {
return "tasks" return "tasks"
} }
@ -176,14 +176,14 @@ type taskOptions struct {
// ReadAll is a dummy function to still have that endpoint documented // ReadAll is a dummy function to still have that endpoint documented
// @Summary Get tasks // @Summary Get tasks
// @Description Returns all tasks on any list the user has access to. // @Description Returns all tasks on any project the user has access to.
// @tags task // @tags task
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param page query int false "The page number. Used for pagination. If not provided, the first page of results is returned." // @Param page query int false "The page number. Used for pagination. If not provided, the first page of results is returned."
// @Param per_page query int false "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page." // @Param per_page query int false "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page."
// @Param s query string false "Search tasks by task text." // @Param s query string false "Search tasks by task text."
// @Param sort_by query string false "The sorting parameter. You can pass this multiple times to get the tasks ordered by multiple different parametes, along with `order_by`. Possible values to sort by are `id`, `title`, `description`, `done`, `done_at`, `due_date`, `created_by_id`, `list_id`, `repeat_after`, `priority`, `start_date`, `end_date`, `hex_color`, `percent_done`, `uid`, `created`, `updated`. Default is `id`." // @Param sort_by query string false "The sorting parameter. You can pass this multiple times to get the tasks ordered by multiple different parametes, along with `order_by`. Possible values to sort by are `id`, `title`, `description`, `done`, `done_at`, `due_date`, `created_by_id`, `project_id`, `repeat_after`, `priority`, `start_date`, `end_date`, `hex_color`, `percent_done`, `uid`, `created`, `updated`. Default is `id`."
// @Param order_by query string false "The ordering parameter. Possible values to order by are `asc` or `desc`. Default is `asc`." // @Param order_by query string false "The ordering parameter. Possible values to order by are `asc` or `desc`. Default is `asc`."
// @Param filter_by query string false "The name of the field to filter by. Allowed values are all task properties. Task properties which are their own object require passing in the id of that entity. Accepts an array for multiple filters which will be chanied together, all supplied filter must match." // @Param filter_by query string false "The name of the field to filter by. Allowed values are all task properties. Task properties which are their own object require passing in the id of that entity. Accepts an array for multiple filters which will be chanied together, all supplied filter must match."
// @Param filter_value query string false "The value to filter for." // @Param filter_value query string false "The value to filter for."
@ -263,10 +263,10 @@ func getTaskIndexFromSearchString(s string) (index int64) {
} }
//nolint:gocyclo //nolint:gocyclo
func getRawTasksForLists(s *xorm.Session, lists []*List, a web.Auth, opts *taskOptions) (tasks []*Task, resultCount int, totalItems int64, err error) { func getRawTasksForProjects(s *xorm.Session, projects []*Project, a web.Auth, opts *taskOptions) (tasks []*Task, resultCount int, totalItems int64, err error) {
// If the user does not have any lists, don't try to get any tasks // If the user does not have any projects, don't try to get any tasks
if len(lists) == 0 { if len(projects) == 0 {
return nil, 0, 0, nil return nil, 0, 0, nil
} }
@ -275,15 +275,15 @@ func getRawTasksForLists(s *xorm.Session, lists []*List, a web.Auth, opts *taskO
opts.filterConcat = filterConcatOr opts.filterConcat = filterConcatOr
} }
// Get all list IDs and get the tasks // Get all project IDs and get the tasks
var listIDs []int64 var projectIDs []int64
var hasFavoritesList bool var hasFavoritesProject bool
for _, l := range lists { for _, l := range projects {
if l.ID == FavoritesPseudoList.ID { if l.ID == FavoritesPseudoProject.ID {
hasFavoritesList = true hasFavoritesProject = true
continue continue
} }
listIDs = append(listIDs, l.ID) projectIDs = append(projectIDs, l.ID)
} }
// Add the id parameter as the last parameter to sorty by default, but only if it is not already passed as the last parameter. // Add the id parameter as the last parameter to sorty by default, but only if it is not already passed as the last parameter.
@ -384,7 +384,7 @@ func getRawTasksForLists(s *xorm.Session, lists []*List, a web.Auth, opts *taskO
filters = append(filters, filter) filters = append(filters, filter)
} }
// Then return all tasks for that lists // Then return all tasks for that projects
var where builder.Cond var where builder.Cond
if opts.search != "" { if opts.search != "" {
@ -396,18 +396,18 @@ func getRawTasksForLists(s *xorm.Session, lists []*List, a web.Auth, opts *taskO
} }
} }
var listIDCond builder.Cond var projectIDCond builder.Cond
var listCond builder.Cond var projectCond builder.Cond
if len(listIDs) > 0 { if len(projectIDs) > 0 {
listIDCond = builder.In("list_id", listIDs) projectIDCond = builder.In("project_id", projectIDs)
listCond = listIDCond projectCond = projectIDCond
} }
if hasFavoritesList { if hasFavoritesProject {
// Make sure users can only see their favorites // Make sure users can only see their favorites
userLists, _, _, err := getRawListsForUser( userProjects, _, _, err := getRawProjectsForUser(
s, s,
&listOptions{ &projectOptions{
user: &user.User{ID: a.GetID()}, user: &user.User{ID: a.GetID()},
page: -1, page: -1,
}, },
@ -416,9 +416,9 @@ func getRawTasksForLists(s *xorm.Session, lists []*List, a web.Auth, opts *taskO
return nil, 0, 0, err return nil, 0, 0, err
} }
userListIDs := make([]int64, 0, len(userLists)) userProjectIDs := make([]int64, 0, len(userProjects))
for _, l := range userLists { for _, l := range userProjects {
userListIDs = append(userListIDs, l.ID) userProjectIDs = append(userProjectIDs, l.ID)
} }
// All favorite tasks for that user // All favorite tasks for that user
@ -431,7 +431,7 @@ func getRawTasksForLists(s *xorm.Session, lists []*List, a web.Auth, opts *taskO
builder.Eq{"kind": FavoriteKindTask}, builder.Eq{"kind": FavoriteKindTask},
)) ))
listCond = builder.And(listCond, builder.And(builder.In("id", favCond), builder.In("list_id", userListIDs))) projectCond = builder.And(projectCond, builder.And(builder.In("id", favCond), builder.In("project_id", userProjectIDs)))
} }
if len(reminderFilters) > 0 { if len(reminderFilters) > 0 {
@ -462,10 +462,10 @@ func getRawTasksForLists(s *xorm.Session, lists []*List, a web.Auth, opts *taskO
} }
cond := builder.In( cond := builder.In(
"list_id", "project_id",
builder. builder.
Select("id"). Select("id").
From("lists"). From("projects").
Where(filtercond), Where(filtercond),
) )
filters = append(filters, cond) filters = append(filters, cond)
@ -482,7 +482,7 @@ func getRawTasksForLists(s *xorm.Session, lists []*List, a web.Auth, opts *taskO
} }
limit, start := getLimitFromPageIndex(opts.page, opts.perPage) limit, start := getLimitFromPageIndex(opts.page, opts.perPage)
cond := builder.And(listCond, where, filterCond) cond := builder.And(projectCond, where, filterCond)
query := s.Where(cond) query := s.Where(cond)
if limit > 0 { if limit > 0 {
@ -505,9 +505,9 @@ func getRawTasksForLists(s *xorm.Session, lists []*List, a web.Auth, opts *taskO
return tasks, len(tasks), totalItems, nil return tasks, len(tasks), totalItems, nil
} }
func getTasksForLists(s *xorm.Session, lists []*List, a web.Auth, opts *taskOptions) (tasks []*Task, resultCount int, totalItems int64, err error) { func getTasksForProjects(s *xorm.Session, projects []*Project, a web.Auth, opts *taskOptions) (tasks []*Task, resultCount int, totalItems int64, err error) {
tasks, resultCount, totalItems, err = getRawTasksForLists(s, lists, a, opts) tasks, resultCount, totalItems, err = getRawTasksForProjects(s, projects, a, opts)
if err != nil { if err != nil {
return nil, 0, 0, err return nil, 0, 0, err
} }
@ -548,7 +548,7 @@ func GetTaskSimple(s *xorm.Session, t *Task) (task Task, err error) {
return return
} }
// GetTasksByIDs returns all tasks for a list of ids // GetTasksByIDs returns all tasks for a project of ids
func (bt *BulkTask) GetTasksByIDs(s *xorm.Session) (err error) { func (bt *BulkTask) GetTasksByIDs(s *xorm.Session) (err error) {
for _, id := range bt.IDs { for _, id := range bt.IDs {
if id < 1 { if id < 1 {
@ -589,8 +589,8 @@ func getRemindersForTasks(s *xorm.Session, taskIDs []int64) (reminders []*TaskRe
return return
} }
func (t *Task) setIdentifier(list *List) { func (t *Task) setIdentifier(project *Project) {
t.Identifier = list.Identifier + "-" + strconv.FormatInt(t.Index, 10) t.Identifier = project.Identifier + "-" + strconv.FormatInt(t.Index, 10)
} }
// Get all assignees // Get all assignees
@ -715,7 +715,7 @@ func addRelatedTasksToTasks(s *xorm.Session, taskIDs []int64, taskMap map[int64]
// It adds more stuff like assignees/labels/etc to a bunch of tasks // It adds more stuff like assignees/labels/etc to a bunch of tasks
func addMoreInfoToTasks(s *xorm.Session, taskMap map[int64]*Task, a web.Auth) (err error) { func addMoreInfoToTasks(s *xorm.Session, taskMap map[int64]*Task, a web.Auth) (err error) {
// No need to iterate over users and stuff if the list doesn't have tasks // No need to iterate over users and stuff if the project doesn't have tasks
if len(taskMap) == 0 { if len(taskMap) == 0 {
return return
} }
@ -723,11 +723,11 @@ func addMoreInfoToTasks(s *xorm.Session, taskMap map[int64]*Task, a web.Auth) (e
// Get all users & task ids and put them into the array // Get all users & task ids and put them into the array
var userIDs []int64 var userIDs []int64
var taskIDs []int64 var taskIDs []int64
var listIDs []int64 var projectIDs []int64
for _, i := range taskMap { for _, i := range taskMap {
taskIDs = append(taskIDs, i.ID) taskIDs = append(taskIDs, i.ID)
userIDs = append(userIDs, i.CreatedByID) userIDs = append(userIDs, i.CreatedByID)
listIDs = append(listIDs, i.ListID) projectIDs = append(projectIDs, i.ProjectID)
} }
err = addAssigneesToTasks(s, taskIDs, taskMap) err = addAssigneesToTasks(s, taskIDs, taskMap)
@ -761,7 +761,7 @@ func addMoreInfoToTasks(s *xorm.Session, taskMap map[int64]*Task, a web.Auth) (e
} }
// Get all identifiers // Get all identifiers
lists, err := GetListsByIDs(s, listIDs) projects, err := GetProjectsByIDs(s, projectIDs)
if err != nil { if err != nil {
return err return err
} }
@ -778,8 +778,8 @@ func addMoreInfoToTasks(s *xorm.Session, taskMap map[int64]*Task, a web.Auth) (e
// Prepare the subtasks // Prepare the subtasks
task.RelatedTasks = make(RelatedTaskMap) task.RelatedTasks = make(RelatedTaskMap)
// Build the task identifier from the list identifier and task index // Build the task identifier from the project identifier and task index
task.setIdentifier(lists[task.ListID]) task.setIdentifier(projects[task.ProjectID])
task.IsFavorite = taskFavorites[task.ID] task.IsFavorite = taskFavorites[task.ID]
} }
@ -789,11 +789,11 @@ func addMoreInfoToTasks(s *xorm.Session, taskMap map[int64]*Task, a web.Auth) (e
return return
} }
func checkBucketAndTaskBelongToSameList(fullTask *Task, bucket *Bucket) (err error) { func checkBucketAndTaskBelongToSameProject(fullTask *Task, bucket *Bucket) (err error) {
if fullTask.ListID != bucket.ListID { if fullTask.ProjectID != bucket.ProjectID {
return ErrBucketDoesNotBelongToList{ return ErrBucketDoesNotBelongToProject{
ListID: fullTask.ListID, ProjectID: fullTask.ProjectID,
BucketID: fullTask.BucketID, BucketID: fullTask.BucketID,
} }
} }
@ -821,7 +821,7 @@ func setTaskBucket(s *xorm.Session, task *Task, originalTask *Task, doCheckBucke
// Make sure we have a bucket // Make sure we have a bucket
var bucket *Bucket var bucket *Bucket
if task.Done && originalTask != nil && !originalTask.Done { if task.Done && originalTask != nil && !originalTask.Done {
bucket, err := getDoneBucketForList(s, task.ListID) bucket, err := getDoneBucketForProject(s, task.ProjectID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -834,9 +834,9 @@ func setTaskBucket(s *xorm.Session, task *Task, originalTask *Task, doCheckBucke
task.BucketID = originalTask.BucketID task.BucketID = originalTask.BucketID
} }
// Either no bucket was provided or the task was moved between lists // Either no bucket was provided or the task was moved between projects
if task.BucketID == 0 || (originalTask != nil && task.ListID != 0 && originalTask.ListID != task.ListID) { if task.BucketID == 0 || (originalTask != nil && task.ProjectID != 0 && originalTask.ProjectID != task.ProjectID) {
bucket, err = getDefaultBucket(s, task.ListID) bucket, err = getDefaultBucket(s, task.ProjectID)
if err != nil { if err != nil {
return return
} }
@ -850,8 +850,8 @@ func setTaskBucket(s *xorm.Session, task *Task, originalTask *Task, doCheckBucke
} }
} }
// If there is a bucket set, make sure they belong to the same list as the task // If there is a bucket set, make sure they belong to the same project as the task
err = checkBucketAndTaskBelongToSameList(task, bucket) err = checkBucketAndTaskBelongToSameProject(task, bucket)
if err != nil { if err != nil {
return return
} }
@ -879,10 +879,10 @@ func calculateDefaultPosition(entityID int64, position float64) float64 {
return position return position
} }
func getNextTaskIndex(s *xorm.Session, listID int64) (nextIndex int64, err error) { func getNextTaskIndex(s *xorm.Session, projectID int64) (nextIndex int64, err error) {
latestTask := &Task{} latestTask := &Task{}
_, err = s. _, err = s.
Where("list_id = ?", listID). Where("project_id = ?", projectID).
OrderBy("`index` desc"). OrderBy("`index` desc").
Get(latestTask) Get(latestTask)
if err != nil { if err != nil {
@ -892,20 +892,20 @@ func getNextTaskIndex(s *xorm.Session, listID int64) (nextIndex int64, err error
return latestTask.Index + 1, nil return latestTask.Index + 1, nil
} }
// Create is the implementation to create a list task // Create is the implementation to create a project task
// @Summary Create a task // @Summary Create a task
// @Description Inserts a task into a list. // @Description Inserts a task into a project.
// @tags task // @tags task
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Security JWTKeyAuth // @Security JWTKeyAuth
// @Param id path int true "List ID" // @Param id path int true "Project ID"
// @Param task body models.Task true "The task object" // @Param task body models.Task true "The task object"
// @Success 201 {object} models.Task "The created task object." // @Success 201 {object} models.Task "The created task object."
// @Failure 400 {object} web.HTTPError "Invalid task object provided." // @Failure 400 {object} web.HTTPError "Invalid task object provided."
// @Failure 403 {object} web.HTTPError "The user does not have access to the list" // @Failure 403 {object} web.HTTPError "The user does not have access to the project"
// @Failure 500 {object} models.Message "Internal error" // @Failure 500 {object} models.Message "Internal error"
// @Router /lists/{id} [put] // @Router /projects/{id} [put]
func (t *Task) Create(s *xorm.Session, a web.Auth) (err error) { func (t *Task) Create(s *xorm.Session, a web.Auth) (err error) {
return createTask(s, t, a, true) return createTask(s, t, a, true)
} }
@ -919,8 +919,8 @@ func createTask(s *xorm.Session, t *Task, a web.Auth, updateAssignees bool) (err
return ErrTaskCannotBeEmpty{} return ErrTaskCannotBeEmpty{}
} }
// Check if the list exists // Check if the project exists
l, err := GetListSimpleByID(s, t.ListID) l, err := GetProjectSimpleByID(s, t.ProjectID)
if err != nil { if err != nil {
return err return err
} }
@ -943,7 +943,7 @@ func createTask(s *xorm.Session, t *Task, a web.Auth, updateAssignees bool) (err
} }
// Get the index for this task // Get the index for this task
t.Index, err = getNextTaskIndex(s, t.ListID) t.Index, err = getNextTaskIndex(s, t.ProjectID)
if err != nil { if err != nil {
return err return err
} }
@ -985,11 +985,11 @@ func createTask(s *xorm.Session, t *Task, a web.Auth, updateAssignees bool) (err
return err return err
} }
err = updateListLastUpdated(s, &List{ID: t.ListID}) err = updateProjectLastUpdated(s, &Project{ID: t.ProjectID})
return return
} }
// Update updates a list task // Update updates a project task
// @Summary Update a task // @Summary Update a task
// @Description Updates a task. This includes marking it as done. Assignees you pass will be updated, see their individual endpoints for more details on how this is done. To update labels, see the description of the endpoint. // @Description Updates a task. This includes marking it as done. Assignees you pass will be updated, see their individual endpoints for more details on how this is done. To update labels, see the description of the endpoint.
// @tags task // @tags task
@ -1000,7 +1000,7 @@ func createTask(s *xorm.Session, t *Task, a web.Auth, updateAssignees bool) (err
// @Param task body models.Task true "The task object" // @Param task body models.Task true "The task object"
// @Success 200 {object} models.Task "The updated task object." // @Success 200 {object} models.Task "The updated task object."
// @Failure 400 {object} web.HTTPError "Invalid task object provided." // @Failure 400 {object} web.HTTPError "Invalid task object provided."
// @Failure 403 {object} web.HTTPError "The user does not have access to the task (aka its list)" // @Failure 403 {object} web.HTTPError "The user does not have access to the task (aka its project)"
// @Failure 500 {object} models.Message "Internal error" // @Failure 500 {object} models.Message "Internal error"
// @Router /tasks/{id} [post] // @Router /tasks/{id} [post]
// //
@ -1013,8 +1013,8 @@ func (t *Task) Update(s *xorm.Session, a web.Auth) (err error) {
return return
} }
if t.ListID == 0 { if t.ProjectID == 0 {
t.ListID = ot.ListID t.ProjectID = ot.ProjectID
} }
// Get the reminders // Get the reminders
@ -1066,7 +1066,7 @@ func (t *Task) Update(s *xorm.Session, a web.Auth) (err error) {
"hex_color", "hex_color",
"done_at", "done_at",
"percent_done", "percent_done",
"list_id", "project_id",
"bucket_id", "bucket_id",
"position", "position",
"repeat_mode", "repeat_mode",
@ -1074,9 +1074,9 @@ func (t *Task) Update(s *xorm.Session, a web.Auth) (err error) {
"cover_image_attachment_id", "cover_image_attachment_id",
} }
// If the task is being moved between lists, make sure to move the bucket + index as well // If the task is being moved between projects, make sure to move the bucket + index as well
if t.ListID != 0 && ot.ListID != t.ListID { if t.ProjectID != 0 && ot.ProjectID != t.ProjectID {
t.Index, err = getNextTaskIndex(s, t.ListID) t.Index, err = getNextTaskIndex(s, t.ProjectID)
if err != nil { if err != nil {
return err return err
} }
@ -1242,7 +1242,7 @@ func (t *Task) Update(s *xorm.Session, a web.Auth) (err error) {
return err return err
} }
return updateListLastUpdated(s, &List{ID: t.ListID}) return updateProjectLastUpdated(s, &Project{ID: t.ProjectID})
} }
func recalculateTaskKanbanPositions(s *xorm.Session, bucketID int64) (err error) { func recalculateTaskKanbanPositions(s *xorm.Session, bucketID int64) (err error) {
@ -1510,7 +1510,7 @@ func (t *Task) updateReminders(s *xorm.Session, reminders []time.Time) (err erro
t.Reminders = nil t.Reminders = nil
} }
err = updateListLastUpdated(s, &List{ID: t.ListID}) err = updateProjectLastUpdated(s, &Project{ID: t.ProjectID})
return return
} }
@ -1519,16 +1519,16 @@ func updateTaskLastUpdated(s *xorm.Session, task *Task) error {
return err return err
} }
// Delete implements the delete method for listTask // Delete implements the delete method for a task
// @Summary Delete a task // @Summary Delete a task
// @Description Deletes a task from a list. This does not mean "mark it done". // @Description Deletes a task from a project. This does not mean "mark it done".
// @tags task // @tags task
// @Produce json // @Produce json
// @Security JWTKeyAuth // @Security JWTKeyAuth
// @Param id path int true "Task ID" // @Param id path int true "Task ID"
// @Success 200 {object} models.Message "The created task object." // @Success 200 {object} models.Message "The created task object."
// @Failure 400 {object} web.HTTPError "Invalid task ID provided." // @Failure 400 {object} web.HTTPError "Invalid task ID provided."
// @Failure 403 {object} web.HTTPError "The user does not have access to the list" // @Failure 403 {object} web.HTTPError "The user does not have access to the project"
// @Failure 500 {object} models.Message "Internal error" // @Failure 500 {object} models.Message "Internal error"
// @Router /tasks/{id} [delete] // @Router /tasks/{id} [delete]
func (t *Task) Delete(s *xorm.Session, a web.Auth) (err error) { func (t *Task) Delete(s *xorm.Session, a web.Auth) (err error) {
@ -1594,7 +1594,7 @@ func (t *Task) Delete(s *xorm.Session, a web.Auth) (err error) {
return return
} }
err = updateListLastUpdated(s, &List{ID: t.ListID}) err = updateProjectLastUpdated(s, &Project{ID: t.ProjectID})
return return
} }

View File

@ -26,15 +26,15 @@ func (t *Task) CanDelete(s *xorm.Session, a web.Auth) (bool, error) {
return t.canDoTask(s, a) return t.canDoTask(s, a)
} }
// CanUpdate determines if a user has the right to update a list task // CanUpdate determines if a user has the right to update a project task
func (t *Task) CanUpdate(s *xorm.Session, a web.Auth) (bool, error) { func (t *Task) CanUpdate(s *xorm.Session, a web.Auth) (bool, error) {
return t.canDoTask(s, a) return t.canDoTask(s, a)
} }
// CanCreate determines if a user has the right to create a list task // CanCreate determines if a user has the right to create a project task
func (t *Task) CanCreate(s *xorm.Session, a web.Auth) (bool, error) { func (t *Task) CanCreate(s *xorm.Session, a web.Auth) (bool, error) {
// A user can do a task if he has write acces to its list // A user can do a task if he has write acces to its project
l := &List{ID: t.ListID} l := &Project{ID: t.ProjectID}
return l.CanWrite(s, a) return l.CanWrite(s, a)
} }
@ -46,8 +46,8 @@ func (t *Task) CanRead(s *xorm.Session, a web.Auth) (canRead bool, maxRight int,
return return
} }
// A user can read a task if it has access to the list // A user can read a task if it has access to the project
l := &List{ID: t.ListID} l := &Project{ID: t.ProjectID}
return l.CanRead(s, a) return l.CanRead(s, a)
} }
@ -56,7 +56,7 @@ func (t *Task) CanWrite(s *xorm.Session, a web.Auth) (canWrite bool, err error)
return t.canDoTask(s, a) return t.canDoTask(s, a)
} }
// Helper function to check if a user can do stuff on a list task // Helper function to check if a user can do stuff on a project task
func (t *Task) canDoTask(s *xorm.Session, a web.Auth) (bool, error) { func (t *Task) canDoTask(s *xorm.Session, a web.Auth) (bool, error) {
// Get the task // Get the task
ot, err := GetTaskByIDSimple(s, t.ID) ot, err := GetTaskByIDSimple(s, t.ID)
@ -64,10 +64,10 @@ func (t *Task) canDoTask(s *xorm.Session, a web.Auth) (bool, error) {
return false, err return false, err
} }
// Check if we're moving the task into a different list to check if the user has sufficient rights for that on the new list // Check if we're moving the task into a different project to check if the user has sufficient rights for that on the new project
if t.ListID != 0 && t.ListID != ot.ListID { if t.ProjectID != 0 && t.ProjectID != ot.ProjectID {
newList := &List{ID: t.ListID} newProject := &Project{ID: t.ProjectID}
can, err := newList.CanWrite(s, a) can, err := newProject.CanWrite(s, a)
if err != nil { if err != nil {
return false, err return false, err
} }
@ -76,7 +76,7 @@ func (t *Task) canDoTask(s *xorm.Session, a web.Auth) (bool, error) {
} }
} }
// A user can do a task if it has write acces to its list // A user can do a task if it has write acces to its project
l := &List{ID: ot.ListID} l := &Project{ID: ot.ProjectID}
return l.CanWrite(s, a) return l.CanWrite(s, a)
} }

View File

@ -45,7 +45,7 @@ func TestTask_Create(t *testing.T) {
task := &Task{ task := &Task{
Title: "Lorem", Title: "Lorem",
Description: "Lorem Ipsum Dolor", Description: "Lorem Ipsum Dolor",
ListID: 1, ProjectID: 1,
} }
err := task.Create(s, usr) err := task.Create(s, usr)
assert.NoError(t, err) assert.NoError(t, err)
@ -63,7 +63,7 @@ func TestTask_Create(t *testing.T) {
"id": task.ID, "id": task.ID,
"title": "Lorem", "title": "Lorem",
"description": "Lorem Ipsum Dolor", "description": "Lorem Ipsum Dolor",
"list_id": 1, "project_id": 1,
"created_by_id": 1, "created_by_id": 1,
"bucket_id": 1, "bucket_id": 1,
}, false) }, false)
@ -78,13 +78,13 @@ func TestTask_Create(t *testing.T) {
task := &Task{ task := &Task{
Title: "", Title: "",
Description: "Lorem Ipsum Dolor", Description: "Lorem Ipsum Dolor",
ListID: 1, ProjectID: 1,
} }
err := task.Create(s, usr) err := task.Create(s, usr)
assert.Error(t, err) assert.Error(t, err)
assert.True(t, IsErrTaskCannotBeEmpty(err)) assert.True(t, IsErrTaskCannotBeEmpty(err))
}) })
t.Run("nonexistant list", func(t *testing.T) { t.Run("nonexistant project", func(t *testing.T) {
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
s := db.NewSession() s := db.NewSession()
defer s.Close() defer s.Close()
@ -92,11 +92,11 @@ func TestTask_Create(t *testing.T) {
task := &Task{ task := &Task{
Title: "Test", Title: "Test",
Description: "Lorem Ipsum Dolor", Description: "Lorem Ipsum Dolor",
ListID: 9999999, ProjectID: 9999999,
} }
err := task.Create(s, usr) err := task.Create(s, usr)
assert.Error(t, err) assert.Error(t, err)
assert.True(t, IsErrListDoesNotExist(err)) assert.True(t, IsErrProjectDoesNotExist(err))
}) })
t.Run("noneixtant user", func(t *testing.T) { t.Run("noneixtant user", func(t *testing.T) {
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
@ -107,7 +107,7 @@ func TestTask_Create(t *testing.T) {
task := &Task{ task := &Task{
Title: "Test", Title: "Test",
Description: "Lorem Ipsum Dolor", Description: "Lorem Ipsum Dolor",
ListID: 1, ProjectID: 1,
} }
err := task.Create(s, nUser) err := task.Create(s, nUser)
assert.Error(t, err) assert.Error(t, err)
@ -121,7 +121,7 @@ func TestTask_Create(t *testing.T) {
task := &Task{ task := &Task{
Title: "Lorem", Title: "Lorem",
Description: "Lorem Ipsum Dolor", Description: "Lorem Ipsum Dolor",
ListID: 1, ProjectID: 1,
BucketID: 2, // Bucket 2 already has 3 tasks and a limit of 3 BucketID: 2, // Bucket 2 already has 3 tasks and a limit of 3
} }
err := task.Create(s, usr) err := task.Create(s, usr)
@ -142,7 +142,7 @@ func TestTask_Update(t *testing.T) {
ID: 1, ID: 1,
Title: "test10000", Title: "test10000",
Description: "Lorem Ipsum Dolor", Description: "Lorem Ipsum Dolor",
ListID: 1, ProjectID: 1,
} }
err := task.Update(s, u) err := task.Update(s, u)
assert.NoError(t, err) assert.NoError(t, err)
@ -153,7 +153,7 @@ func TestTask_Update(t *testing.T) {
"id": 1, "id": 1,
"title": "test10000", "title": "test10000",
"description": "Lorem Ipsum Dolor", "description": "Lorem Ipsum Dolor",
"list_id": 1, "project_id": 1,
}, false) }, false)
}) })
t.Run("nonexistant task", func(t *testing.T) { t.Run("nonexistant task", func(t *testing.T) {
@ -165,7 +165,7 @@ func TestTask_Update(t *testing.T) {
ID: 9999999, ID: 9999999,
Title: "test10000", Title: "test10000",
Description: "Lorem Ipsum Dolor", Description: "Lorem Ipsum Dolor",
ListID: 1, ProjectID: 1,
} }
err := task.Update(s, u) err := task.Update(s, u)
assert.Error(t, err) assert.Error(t, err)
@ -180,7 +180,7 @@ func TestTask_Update(t *testing.T) {
ID: 1, ID: 1,
Title: "test10000", Title: "test10000",
Description: "Lorem Ipsum Dolor", Description: "Lorem Ipsum Dolor",
ListID: 1, ProjectID: 1,
BucketID: 2, // Bucket 2 already has 3 tasks and a limit of 3 BucketID: 2, // Bucket 2 already has 3 tasks and a limit of 3
} }
err := task.Update(s, u) err := task.Update(s, u)
@ -197,13 +197,13 @@ func TestTask_Update(t *testing.T) {
Title: "test10000", Title: "test10000",
Description: "Lorem Ipsum Dolor", Description: "Lorem Ipsum Dolor",
KanbanPosition: 10, KanbanPosition: 10,
ListID: 1, ProjectID: 1,
BucketID: 2, // Bucket 2 already has 3 tasks and a limit of 3 BucketID: 2, // Bucket 2 already has 3 tasks and a limit of 3
} }
err := task.Update(s, u) err := task.Update(s, u)
assert.NoError(t, err) assert.NoError(t, err)
}) })
t.Run("bucket on other list", func(t *testing.T) { t.Run("bucket on other project", func(t *testing.T) {
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
s := db.NewSession() s := db.NewSession()
defer s.Close() defer s.Close()
@ -212,12 +212,12 @@ func TestTask_Update(t *testing.T) {
ID: 1, ID: 1,
Title: "test10000", Title: "test10000",
Description: "Lorem Ipsum Dolor", Description: "Lorem Ipsum Dolor",
ListID: 1, ProjectID: 1,
BucketID: 4, // Bucket 4 belongs to list 2 BucketID: 4, // Bucket 4 belongs to project 2
} }
err := task.Update(s, u) err := task.Update(s, u)
assert.Error(t, err) assert.Error(t, err)
assert.True(t, IsErrBucketDoesNotBelongToList(err)) assert.True(t, IsErrBucketDoesNotBelongToProject(err))
}) })
t.Run("moving a task to the done bucket", func(t *testing.T) { t.Run("moving a task to the done bucket", func(t *testing.T) {
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
@ -225,10 +225,10 @@ func TestTask_Update(t *testing.T) {
defer s.Close() defer s.Close()
task := &Task{ task := &Task{
ID: 1, ID: 1,
Title: "test", Title: "test",
ListID: 1, ProjectID: 1,
BucketID: 3, // Bucket 3 is the done bucket BucketID: 3, // Bucket 3 is the done bucket
} }
err := task.Update(s, u) err := task.Update(s, u)
assert.NoError(t, err) assert.NoError(t, err)
@ -237,11 +237,11 @@ func TestTask_Update(t *testing.T) {
assert.True(t, task.Done) assert.True(t, task.Done)
db.AssertExists(t, "tasks", map[string]interface{}{ db.AssertExists(t, "tasks", map[string]interface{}{
"id": 1, "id": 1,
"done": true, "done": true,
"title": "test", "title": "test",
"list_id": 1, "project_id": 1,
"bucket_id": 3, "bucket_id": 3,
}, false) }, false)
}) })
t.Run("moving a repeating task to the done bucket", func(t *testing.T) { t.Run("moving a repeating task to the done bucket", func(t *testing.T) {
@ -252,7 +252,7 @@ func TestTask_Update(t *testing.T) {
task := &Task{ task := &Task{
ID: 28, ID: 28,
Title: "test updated", Title: "test updated",
ListID: 1, ProjectID: 1,
BucketID: 3, // Bucket 3 is the done bucket BucketID: 3, // Bucket 3 is the done bucket
RepeatAfter: 3600, RepeatAfter: 3600,
} }
@ -271,21 +271,21 @@ func TestTask_Update(t *testing.T) {
"bucket_id": 1, "bucket_id": 1,
}, false) }, false)
}) })
t.Run("default bucket when moving a task between lists", func(t *testing.T) { t.Run("default bucket when moving a task between projects", func(t *testing.T) {
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
s := db.NewSession() s := db.NewSession()
defer s.Close() defer s.Close()
task := &Task{ task := &Task{
ID: 1, ID: 1,
ListID: 2, ProjectID: 2,
} }
err := task.Update(s, u) err := task.Update(s, u)
assert.NoError(t, err) assert.NoError(t, err)
err = s.Commit() err = s.Commit()
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, int64(4), task.BucketID) // bucket 4 is the default bucket on list 2 assert.Equal(t, int64(4), task.BucketID) // bucket 4 is the default bucket on project 2
assert.True(t, task.Done) // bucket 4 is the done bucket, so the task should be marked as done as well assert.True(t, task.Done) // bucket 4 is the done bucket, so the task should be marked as done as well
}) })
t.Run("marking a task as done should move it to the done bucket", func(t *testing.T) { t.Run("marking a task as done should move it to the done bucket", func(t *testing.T) {
@ -310,14 +310,14 @@ func TestTask_Update(t *testing.T) {
"bucket_id": 3, "bucket_id": 3,
}, false) }, false)
}) })
t.Run("move task to another list", func(t *testing.T) { t.Run("move task to another project", func(t *testing.T) {
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
s := db.NewSession() s := db.NewSession()
defer s.Close() defer s.Close()
task := &Task{ task := &Task{
ID: 1, ID: 1,
ListID: 2, ProjectID: 2,
} }
err := task.Update(s, u) err := task.Update(s, u)
assert.NoError(t, err) assert.NoError(t, err)
@ -325,9 +325,9 @@ func TestTask_Update(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
db.AssertExists(t, "tasks", map[string]interface{}{ db.AssertExists(t, "tasks", map[string]interface{}{
"id": 1, "id": 1,
"list_id": 2, "project_id": 2,
"bucket_id": 4, "bucket_id": 4,
}, false) }, false)
}) })
t.Run("repeating tasks should not be moved to the done bucket", func(t *testing.T) { t.Run("repeating tasks should not be moved to the done bucket", func(t *testing.T) {
@ -353,14 +353,14 @@ func TestTask_Update(t *testing.T) {
"bucket_id": 1, "bucket_id": 1,
}, false) }, false)
}) })
t.Run("moving a task between lists should give it a correct index", func(t *testing.T) { t.Run("moving a task between projects should give it a correct index", func(t *testing.T) {
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
s := db.NewSession() s := db.NewSession()
defer s.Close() defer s.Close()
task := &Task{ task := &Task{
ID: 12, ID: 12,
ListID: 2, // From list 1 ProjectID: 2, // From project 1
} }
err := task.Update(s, u) err := task.Update(s, u)
assert.NoError(t, err) assert.NoError(t, err)

View File

@ -46,7 +46,7 @@ func (tm *TeamMember) CanUpdate(s *xorm.Session, a web.Auth) (bool, error) {
// IsAdmin checks if the user is team admin // IsAdmin checks if the user is team admin
func (tm *TeamMember) IsAdmin(s *xorm.Session, a web.Auth) (bool, error) { func (tm *TeamMember) IsAdmin(s *xorm.Session, a web.Auth) (bool, error) {
// Don't allow anything if we're dealing with a list share here // Don't allow anything if we're dealing with a project share here
if _, is := a.(*LinkSharing); is { if _, is := a.(*LinkSharing); is {
return false, nil return false, nil
} }

View File

@ -163,7 +163,7 @@ func addMoreInfoToTeams(s *xorm.Session, teams []*Team) (err error) {
teamMap[u.TeamID].Members = append(teamMap[u.TeamID].Members, u) teamMap[u.TeamID].Members = append(teamMap[u.TeamID].Members, u)
} }
// We need to do this in a second loop as owners might not be the last ones in the list // We need to do this in a second loop as owners might not be the last ones in the project
for _, team := range teamMap { for _, team := range teamMap {
if teamUser, has := users[team.CreatedByID]; has { if teamUser, has := users[team.CreatedByID]; has {
team.CreatedBy = &teamUser.User team.CreatedBy = &teamUser.User
@ -313,8 +313,8 @@ func (t *Team) Delete(s *xorm.Session, a web.Auth) (err error) {
return return
} }
// Delete team <-> lists relations // Delete team <-> projects relations
_, err = s.Where("team_id = ?", t.ID).Delete(&TeamList{}) _, err = s.Where("team_id = ?", t.ID).Delete(&TeamProject{})
if err != nil { if err != nil {
return return
} }

View File

@ -117,7 +117,7 @@ func TestTeam_ReadAll(t *testing.T) {
defer s.Close() defer s.Close()
team := &Team{} team := &Team{}
teams, _, _, err := team.ReadAll(s, doer, "READ_only_on_list6", 1, 50) teams, _, _, err := team.ReadAll(s, doer, "READ_only_on_project6", 1, 50)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, reflect.TypeOf(teams).Kind(), reflect.Slice) assert.Equal(t, reflect.TypeOf(teams).Kind(), reflect.Slice)
ts := teams.([]*Team) ts := teams.([]*Team)

View File

@ -47,7 +47,7 @@ func SetupTests() {
"label_tasks", "label_tasks",
"labels", "labels",
"link_shares", "link_shares",
"lists", "projects",
"namespaces", "namespaces",
"task_assignees", "task_assignees",
"task_attachments", "task_attachments",
@ -55,13 +55,13 @@ func SetupTests() {
"task_relations", "task_relations",
"task_reminders", "task_reminders",
"tasks", "tasks",
"team_lists", "team_projects",
"team_members", "team_members",
"team_namespaces", "team_namespaces",
"teams", "teams",
"users", "users",
"user_tokens", "user_tokens",
"users_lists", "users_projects",
"users_namespaces", "users_namespaces",
"buckets", "buckets",
"saved_filters", "saved_filters",

View File

@ -99,7 +99,7 @@ func getNamespacesToDelete(s *xorm.Session, u *user.User) (namespacesToDelete []
return nil, nil return nil, nil
} }
namespaces := res.([]*NamespaceWithLists) namespaces := res.([]*NamespaceWithProjects)
for _, n := range namespaces { for _, n := range namespaces {
if n.ID < 0 { if n.ID < 0 {
continue continue
@ -126,9 +126,9 @@ func getNamespacesToDelete(s *xorm.Session, u *user.User) (namespacesToDelete []
return return
} }
func getListsToDelete(s *xorm.Session, u *user.User) (listsToDelete []*List, err error) { func getProjectsToDelete(s *xorm.Session, u *user.User) (projectsToDelete []*Project, err error) {
listsToDelete = []*List{} projectsToDelete = []*Project{}
lm := &List{IsArchived: true} lm := &Project{IsArchived: true}
res, _, _, err := lm.ReadAll(s, u, "", 0, -1) res, _, _, err := lm.ReadAll(s, u, "", 0, -1)
if err != nil { if err != nil {
return nil, err return nil, err
@ -138,20 +138,20 @@ func getListsToDelete(s *xorm.Session, u *user.User) (listsToDelete []*List, err
return nil, nil return nil, nil
} }
lists := res.([]*List) projects := res.([]*Project)
for _, l := range lists { for _, l := range projects {
if l.ID < 0 { if l.ID < 0 {
continue continue
} }
hadUsers, err := ensureListAdminUser(s, l) hadUsers, err := ensureProjectAdminUser(s, l)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if hadUsers { if hadUsers {
continue continue
} }
hadTeams, err := ensureListAdminTeam(s, l) hadTeams, err := ensureProjectAdminTeam(s, l)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -160,13 +160,13 @@ func getListsToDelete(s *xorm.Session, u *user.User) (listsToDelete []*List, err
continue continue
} }
listsToDelete = append(listsToDelete, l) projectsToDelete = append(projectsToDelete, l)
} }
return return
} }
// DeleteUser completely removes a user and all their associated lists, namespaces and tasks. // DeleteUser completely removes a user and all their associated projects, namespaces and tasks.
// This action is irrevocable. // This action is irrevocable.
// Public to allow deletion from the CLI. // Public to allow deletion from the CLI.
func DeleteUser(s *xorm.Session, u *user.User) (err error) { func DeleteUser(s *xorm.Session, u *user.User) (err error) {
@ -175,7 +175,7 @@ func DeleteUser(s *xorm.Session, u *user.User) (err error) {
return err return err
} }
listsToDelete, err := getListsToDelete(s, u) projectsToDelete, err := getProjectsToDelete(s, u)
if err != nil { if err != nil {
return err return err
} }
@ -188,7 +188,7 @@ func DeleteUser(s *xorm.Session, u *user.User) (err error) {
} }
} }
for _, l := range listsToDelete { for _, l := range projectsToDelete {
err = l.Delete(s, u) err = l.Delete(s, u)
if err != nil { if err != nil {
return err return err
@ -218,7 +218,7 @@ func ensureNamespaceAdminUser(s *xorm.Session, n *Namespace) (hadUsers bool, err
for _, lu := range namespaceUsers { for _, lu := range namespaceUsers {
if lu.Right == RightAdmin { if lu.Right == RightAdmin {
// List already has more than one admin, no need to do anything // Project already has more than one admin, no need to do anything
return true, nil return true, nil
} }
} }
@ -244,7 +244,7 @@ func ensureNamespaceAdminTeam(s *xorm.Session, n *Namespace) (hadTeams bool, err
for _, lu := range namespaceTeams { for _, lu := range namespaceTeams {
if lu.Right == RightAdmin { if lu.Right == RightAdmin {
// List already has more than one admin, no need to do anything // Project already has more than one admin, no need to do anything
return true, nil return true, nil
} }
} }
@ -257,25 +257,25 @@ func ensureNamespaceAdminTeam(s *xorm.Session, n *Namespace) (hadTeams bool, err
return true, err return true, err
} }
func ensureListAdminUser(s *xorm.Session, l *List) (hadUsers bool, err error) { func ensureProjectAdminUser(s *xorm.Session, l *Project) (hadUsers bool, err error) {
listUsers := []*ListUser{} projectUsers := []*ProjectUser{}
err = s.Where("list_id = ?", l.ID).Find(&listUsers) err = s.Where("project_id = ?", l.ID).Find(&projectUsers)
if err != nil { if err != nil {
return return
} }
if len(listUsers) == 0 { if len(projectUsers) == 0 {
return false, nil return false, nil
} }
for _, lu := range listUsers { for _, lu := range projectUsers {
if lu.Right == RightAdmin { if lu.Right == RightAdmin {
// List already has more than one admin, no need to do anything // Project already has more than one admin, no need to do anything
return true, nil return true, nil
} }
} }
firstUser := listUsers[0] firstUser := projectUsers[0]
firstUser.Right = RightAdmin firstUser.Right = RightAdmin
_, err = s.Where("id = ?", firstUser.ID). _, err = s.Where("id = ?", firstUser.ID).
Cols("right"). Cols("right").
@ -283,25 +283,25 @@ func ensureListAdminUser(s *xorm.Session, l *List) (hadUsers bool, err error) {
return true, err return true, err
} }
func ensureListAdminTeam(s *xorm.Session, l *List) (hadTeams bool, err error) { func ensureProjectAdminTeam(s *xorm.Session, l *Project) (hadTeams bool, err error) {
listTeams := []*TeamList{} projectTeams := []*TeamProject{}
err = s.Where("list_id = ?", l.ID).Find(&listTeams) err = s.Where("project_id = ?", l.ID).Find(&projectTeams)
if err != nil { if err != nil {
return return
} }
if len(listTeams) == 0 { if len(projectTeams) == 0 {
return false, nil return false, nil
} }
for _, lu := range listTeams { for _, lu := range projectTeams {
if lu.Right == RightAdmin { if lu.Right == RightAdmin {
// List already has more than one admin, no need to do anything // Project already has more than one admin, no need to do anything
return true, nil return true, nil
} }
} }
firstTeam := listTeams[0] firstTeam := projectTeams[0]
firstTeam.Right = RightAdmin firstTeam.Right = RightAdmin
_, err = s.Where("id = ?", firstTeam.ID). _, err = s.Where("id = ?", firstTeam.ID).
Cols("right"). Cols("right").

View File

@ -38,13 +38,13 @@ func TestDeleteUser(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
db.AssertMissing(t, "users", map[string]interface{}{"id": u.ID}) db.AssertMissing(t, "users", map[string]interface{}{"id": u.ID})
db.AssertMissing(t, "lists", map[string]interface{}{"id": 24}) // only user6 had access to this list db.AssertMissing(t, "projects", map[string]interface{}{"id": 24}) // only user6 had access to this project
db.AssertExists(t, "lists", map[string]interface{}{"id": 6}, false) db.AssertExists(t, "projects", map[string]interface{}{"id": 6}, false)
db.AssertExists(t, "lists", map[string]interface{}{"id": 7}, false) db.AssertExists(t, "projects", map[string]interface{}{"id": 7}, false)
db.AssertExists(t, "lists", map[string]interface{}{"id": 8}, false) db.AssertExists(t, "projects", map[string]interface{}{"id": 8}, false)
db.AssertExists(t, "lists", map[string]interface{}{"id": 9}, false) db.AssertExists(t, "projects", map[string]interface{}{"id": 9}, false)
db.AssertExists(t, "lists", map[string]interface{}{"id": 10}, false) db.AssertExists(t, "projects", map[string]interface{}{"id": 10}, false)
db.AssertExists(t, "lists", map[string]interface{}{"id": 11}, false) db.AssertExists(t, "projects", map[string]interface{}{"id": 11}, false)
}) })
t.Run("user with no namespaces", func(t *testing.T) { t.Run("user with no namespaces", func(t *testing.T) {
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
@ -56,6 +56,6 @@ func TestDeleteUser(t *testing.T) {
err := DeleteUser(s, u) err := DeleteUser(s, u)
assert.NoError(t, err) assert.NoError(t, err)
// No assertions for deleted lists and namespaces since that user doesn't have any // No assertions for deleted projects and namespaces since that user doesn't have any
}) })
} }

View File

@ -22,38 +22,38 @@ import (
"xorm.io/xorm" "xorm.io/xorm"
) )
// ListUIDs hold all kinds of user IDs from accounts who have somehow access to a list // ProjectUIDs hold all kinds of user IDs from accounts who have somehow access to a project
type ListUIDs struct { type ProjectUIDs struct {
ListOwnerID int64 `xorm:"listOwner"` ProjectOwnerID int64 `xorm:"projectOwner"`
NamespaceUserID int64 `xorm:"unID"` NamespaceUserID int64 `xorm:"unID"`
ListUserID int64 `xorm:"ulID"` ProjectUserID int64 `xorm:"ulID"`
NamespaceOwnerUserID int64 `xorm:"nOwner"` NamespaceOwnerUserID int64 `xorm:"nOwner"`
TeamNamespaceUserID int64 `xorm:"tnUID"` TeamNamespaceUserID int64 `xorm:"tnUID"`
TeamListUserID int64 `xorm:"tlUID"` TeamProjectUserID int64 `xorm:"tlUID"`
} }
// ListUsersFromList returns a list with all users who have access to a list, regardless of the method which gave them access // ProjectUsersFromProject returns a project with all users who have access to a project, regardless of the method which gave them access
func ListUsersFromList(s *xorm.Session, l *List, search string) (users []*user.User, err error) { func ProjectUsersFromProject(s *xorm.Session, l *Project, search string) (users []*user.User, err error) {
userids := []*ListUIDs{} userids := []*ProjectUIDs{}
err = s. err = s.
Select(`l.owner_id as listOwner, Select(`l.owner_id as projectOwner,
un.user_id as unID, un.user_id as unID,
ul.user_id as ulID, ul.user_id as ulID,
n.owner_id as nOwner, n.owner_id as nOwner,
tm.user_id as tnUID, tm.user_id as tnUID,
tm2.user_id as tlUID`). tm2.user_id as tlUID`).
Table("lists"). Table("projects").
Alias("l"). Alias("l").
// User stuff // User stuff
Join("LEFT", []string{"users_namespaces", "un"}, "un.namespace_id = l.namespace_id"). Join("LEFT", []string{"users_namespaces", "un"}, "un.namespace_id = l.namespace_id").
Join("LEFT", []string{"users_lists", "ul"}, "ul.list_id = l.id"). Join("LEFT", []string{"users_projects", "ul"}, "ul.project_id = l.id").
Join("LEFT", []string{"namespaces", "n"}, "n.id = l.namespace_id"). Join("LEFT", []string{"namespaces", "n"}, "n.id = l.namespace_id").
// Team stuff // Team stuff
Join("LEFT", []string{"team_namespaces", "tn"}, " l.namespace_id = tn.namespace_id"). Join("LEFT", []string{"team_namespaces", "tn"}, " l.namespace_id = tn.namespace_id").
Join("LEFT", []string{"team_members", "tm"}, "tm.team_id = tn.team_id"). Join("LEFT", []string{"team_members", "tm"}, "tm.team_id = tn.team_id").
Join("LEFT", []string{"team_lists", "tl"}, "l.id = tl.list_id"). Join("LEFT", []string{"team_projects", "tl"}, "l.id = tl.project_id").
Join("LEFT", []string{"team_members", "tm2"}, "tm2.team_id = tl.team_id"). Join("LEFT", []string{"team_members", "tm2"}, "tm2.team_id = tl.team_id").
// The actual condition // The actual condition
Where( Where(
@ -80,14 +80,14 @@ func ListUsersFromList(s *xorm.Session, l *List, search string) (users []*user.U
return return
} }
// Remove duplicates from the list of ids and make it a slice // Remove duplicates from the project of ids and make it a slice
uidmap := make(map[int64]bool) uidmap := make(map[int64]bool)
uidmap[l.OwnerID] = true uidmap[l.OwnerID] = true
for _, u := range userids { for _, u := range userids {
uidmap[u.ListUserID] = true uidmap[u.ProjectUserID] = true
uidmap[u.NamespaceOwnerUserID] = true uidmap[u.NamespaceOwnerUserID] = true
uidmap[u.NamespaceUserID] = true uidmap[u.NamespaceUserID] = true
uidmap[u.TeamListUserID] = true uidmap[u.TeamProjectUserID] = true
uidmap[u.TeamNamespaceUserID] = true uidmap[u.TeamNamespaceUserID] = true
} }
@ -102,7 +102,7 @@ func ListUsersFromList(s *xorm.Session, l *List, search string) (users []*user.U
cond = builder.In("id", uids) cond = builder.In("id", uids)
} }
users, err = user.ListUsers(s, search, &user.ListUserOpts{ users, err = user.ProjectUsers(s, search, &user.ProjectUserOpts{
AdditionalCond: cond, AdditionalCond: cond,
ReturnAllIfNoSearchProvided: true, ReturnAllIfNoSearchProvided: true,
}) })

View File

@ -24,7 +24,7 @@ import (
"gopkg.in/d4l3k/messagediff.v1" "gopkg.in/d4l3k/messagediff.v1"
) )
func TestListUsersFromList(t *testing.T) { func TestProjectUsersFromProject(t *testing.T) {
testuser1 := &user.User{ testuser1 := &user.User{
ID: 1, ID: 1,
Username: "user1", Username: "user1",
@ -176,7 +176,7 @@ func TestListUsersFromList(t *testing.T) {
} }
type args struct { type args struct {
l *List l *Project
search string search string
} }
tests := []struct { tests := []struct {
@ -187,13 +187,13 @@ func TestListUsersFromList(t *testing.T) {
}{ }{
{ {
name: "Check owner only", name: "Check owner only",
args: args{l: &List{ID: 18, OwnerID: 7}}, args: args{l: &Project{ID: 18, OwnerID: 7}},
wantUsers: []*user.User{testuser7}, wantUsers: []*user.User{testuser7},
}, },
{ {
// This list has another different user shared for each possible method // This project has another different user shared for each possible method
name: "Check with owner and other users", name: "Check with owner and other users",
args: args{l: &List{ID: 19, OwnerID: 7}}, args: args{l: &Project{ID: 19, OwnerID: 7}},
wantUsers: []*user.User{ wantUsers: []*user.User{
testuser1, // Shared Via Team readonly testuser1, // Shared Via Team readonly
testuser2, // Shared Via Team write testuser2, // Shared Via Team write
@ -216,7 +216,7 @@ func TestListUsersFromList(t *testing.T) {
}, },
{ {
name: "search for user1", name: "search for user1",
args: args{l: &List{ID: 19, OwnerID: 7}, search: "user1"}, args: args{l: &Project{ID: 19, OwnerID: 7}, search: "user1"},
wantUsers: []*user.User{ wantUsers: []*user.User{
testuser1, // Shared Via Team readonly testuser1, // Shared Via Team readonly
}, },
@ -228,9 +228,9 @@ func TestListUsersFromList(t *testing.T) {
s := db.NewSession() s := db.NewSession()
defer s.Close() defer s.Close()
gotUsers, err := ListUsersFromList(s, tt.args.l, tt.args.search) gotUsers, err := ProjectUsersFromProject(s, tt.args.l, tt.args.search)
if (err != nil) != tt.wantErr { if (err != nil) != tt.wantErr {
t.Errorf("ListUsersFromList() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("ProjectUsersFromProject() error = %v, wantErr %v", err, tt.wantErr)
return return
} }
if diff, equal := messagediff.PrettyDiff(tt.wantUsers, gotUsers); !equal { if diff, equal := messagediff.PrettyDiff(tt.wantUsers, gotUsers); !equal {

View File

@ -89,7 +89,7 @@ func NewLinkShareJWTAuthtoken(share *models.LinkSharing) (token string, err erro
claims["type"] = AuthTypeLinkShare claims["type"] = AuthTypeLinkShare
claims["id"] = share.ID claims["id"] = share.ID
claims["hash"] = share.Hash claims["hash"] = share.Hash
claims["list_id"] = share.ListID claims["project_id"] = share.ProjectID
claims["right"] = share.Right claims["right"] = share.Right
claims["sharedByID"] = share.SharedByID claims["sharedByID"] = share.SharedByID
claims["exp"] = exp claims["exp"] = exp

View File

@ -22,7 +22,7 @@ import (
"xorm.io/xorm" "xorm.io/xorm"
) )
// Image represents an image which can be used as a list background // Image represents an image which can be used as a project background
type Image struct { type Image struct {
ID string `json:"id"` ID string `json:"id"`
URL string `json:"url"` URL string `json:"url"`
@ -32,10 +32,10 @@ type Image struct {
Info interface{} `json:"info,omitempty"` Info interface{} `json:"info,omitempty"`
} }
// Provider represents something that is able to get a list of images and set one of them as background // Provider represents something that is able to get a project of images and set one of them as background
type Provider interface { type Provider interface {
// Search is used to either return a pre-defined list of Image or let the user search for an image // Search is used to either return a pre-defined project of Image or let the user search for an image
Search(s *xorm.Session, search string, page int64) (result []*Image, err error) Search(s *xorm.Session, search string, page int64) (result []*Image, err error)
// Set sets an image which was most likely previously obtained by Search as list background // Set sets an image which was most likely previously obtained by Search as project background
Set(s *xorm.Session, image *Image, list *models.List, auth web.Auth) (err error) Set(s *xorm.Session, image *Image, project *models.Project, auth web.Auth) (err error)
} }

View File

@ -92,38 +92,38 @@ func (bp *BackgroundProvider) SearchBackgrounds(c echo.Context) error {
} }
// This function does all kinds of preparations for setting and uploading a background // This function does all kinds of preparations for setting and uploading a background
func (bp *BackgroundProvider) setBackgroundPreparations(s *xorm.Session, c echo.Context) (list *models.List, auth web.Auth, err error) { func (bp *BackgroundProvider) setBackgroundPreparations(s *xorm.Session, c echo.Context) (project *models.Project, auth web.Auth, err error) {
auth, err = auth2.GetAuthFromClaims(c) auth, err = auth2.GetAuthFromClaims(c)
if err != nil { if err != nil {
return nil, nil, echo.NewHTTPError(http.StatusBadRequest, "Invalid auth token: "+err.Error()) return nil, nil, echo.NewHTTPError(http.StatusBadRequest, "Invalid auth token: "+err.Error())
} }
listID, err := strconv.ParseInt(c.Param("list"), 10, 64) projectID, err := strconv.ParseInt(c.Param("project"), 10, 64)
if err != nil { if err != nil {
return nil, nil, echo.NewHTTPError(http.StatusBadRequest, "Invalid list ID: "+err.Error()) return nil, nil, echo.NewHTTPError(http.StatusBadRequest, "Invalid project ID: "+err.Error())
} }
// Check if the user has the right to change the list background // Check if the user has the right to change the project background
list = &models.List{ID: listID} project = &models.Project{ID: projectID}
can, err := list.CanUpdate(s, auth) can, err := project.CanUpdate(s, auth)
if err != nil { if err != nil {
return return
} }
if !can { if !can {
log.Infof("Tried to update list background of list %d while not having the rights for it (User: %v)", listID, auth) log.Infof("Tried to update project background of project %d while not having the rights for it (User: %v)", projectID, auth)
return list, auth, models.ErrGenericForbidden{} return project, auth, models.ErrGenericForbidden{}
} }
// Load the list // Load the project
list, err = models.GetListSimpleByID(s, list.ID) project, err = models.GetProjectSimpleByID(s, project.ID)
return return
} }
// SetBackground sets an Image as list background // SetBackground sets an Image as project background
func (bp *BackgroundProvider) SetBackground(c echo.Context) error { func (bp *BackgroundProvider) SetBackground(c echo.Context) error {
s := db.NewSession() s := db.NewSession()
defer s.Close() defer s.Close()
list, auth, err := bp.setBackgroundPreparations(s, c) project, auth, err := bp.setBackgroundPreparations(s, c)
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err, c) return handler.HandleHTTPError(err, c)
@ -138,12 +138,12 @@ func (bp *BackgroundProvider) SetBackground(c echo.Context) error {
return echo.NewHTTPError(http.StatusBadRequest, "No or invalid model provided: "+err.Error()) return echo.NewHTTPError(http.StatusBadRequest, "No or invalid model provided: "+err.Error())
} }
err = p.Set(s, image, list, auth) err = p.Set(s, image, project, auth)
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err, c) return handler.HandleHTTPError(err, c)
} }
return c.JSON(http.StatusOK, list) return c.JSON(http.StatusOK, project)
} }
func CreateBlurHash(srcf io.Reader) (hash string, err error) { func CreateBlurHash(srcf io.Reader) (hash string, err error) {
@ -163,7 +163,7 @@ func (bp *BackgroundProvider) UploadBackground(c echo.Context) error {
s := db.NewSession() s := db.NewSession()
defer s.Close() defer s.Close()
list, auth, err := bp.setBackgroundPreparations(s, c) project, auth, err := bp.setBackgroundPreparations(s, c)
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err, c) return handler.HandleHTTPError(err, c)
@ -193,7 +193,7 @@ func (bp *BackgroundProvider) UploadBackground(c echo.Context) error {
return c.JSON(http.StatusBadRequest, models.Message{Message: "Uploaded file is no image."}) return c.JSON(http.StatusBadRequest, models.Message{Message: "Uploaded file is no image."})
} }
err = SaveBackgroundFile(s, auth, list, srcf, file.Filename, uint64(file.Size)) err = SaveBackgroundFile(s, auth, project, srcf, file.Filename, uint64(file.Size))
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
if files.IsErrFileIsTooLarge(err) { if files.IsErrFileIsTooLarge(err) {
@ -208,10 +208,10 @@ func (bp *BackgroundProvider) UploadBackground(c echo.Context) error {
return handler.HandleHTTPError(err, c) return handler.HandleHTTPError(err, c)
} }
return c.JSON(http.StatusOK, list) return c.JSON(http.StatusOK, project)
} }
func SaveBackgroundFile(s *xorm.Session, auth web.Auth, list *models.List, srcf io.ReadSeeker, filename string, filesize uint64) (err error) { func SaveBackgroundFile(s *xorm.Session, auth web.Auth, project *models.Project, srcf io.ReadSeeker, filename string, filesize uint64) (err error) {
_, _ = srcf.Seek(0, io.SeekStart) _, _ = srcf.Seek(0, io.SeekStart)
f, err := files.Create(srcf, filename, filesize, auth) f, err := files.Create(srcf, filename, filesize, auth)
if err != nil { if err != nil {
@ -220,7 +220,7 @@ func SaveBackgroundFile(s *xorm.Session, auth web.Auth, list *models.List, srcf
// Generate a blurHash // Generate a blurHash
_, _ = srcf.Seek(0, io.SeekStart) _, _ = srcf.Seek(0, io.SeekStart)
list.BackgroundBlurHash, err = CreateBlurHash(srcf) project.BackgroundBlurHash, err = CreateBlurHash(srcf)
if err != nil { if err != nil {
return err return err
} }
@ -228,68 +228,68 @@ func SaveBackgroundFile(s *xorm.Session, auth web.Auth, list *models.List, srcf
// Save it // Save it
p := upload.Provider{} p := upload.Provider{}
img := &background.Image{ID: strconv.FormatInt(f.ID, 10)} img := &background.Image{ID: strconv.FormatInt(f.ID, 10)}
err = p.Set(s, img, list, auth) err = p.Set(s, img, project, auth)
return err return err
} }
func checkListBackgroundRights(s *xorm.Session, c echo.Context) (list *models.List, auth web.Auth, err error) { func checkProjectBackgroundRights(s *xorm.Session, c echo.Context) (project *models.Project, auth web.Auth, err error) {
auth, err = auth2.GetAuthFromClaims(c) auth, err = auth2.GetAuthFromClaims(c)
if err != nil { if err != nil {
return nil, auth, echo.NewHTTPError(http.StatusBadRequest, "Invalid auth token: "+err.Error()) return nil, auth, echo.NewHTTPError(http.StatusBadRequest, "Invalid auth token: "+err.Error())
} }
listID, err := strconv.ParseInt(c.Param("list"), 10, 64) projectID, err := strconv.ParseInt(c.Param("project"), 10, 64)
if err != nil { if err != nil {
return nil, auth, echo.NewHTTPError(http.StatusBadRequest, "Invalid list ID: "+err.Error()) return nil, auth, echo.NewHTTPError(http.StatusBadRequest, "Invalid project ID: "+err.Error())
} }
// Check if a background for this list exists + Rights // Check if a background for this project exists + Rights
list = &models.List{ID: listID} project = &models.Project{ID: projectID}
can, _, err := list.CanRead(s, auth) can, _, err := project.CanRead(s, auth)
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return nil, auth, handler.HandleHTTPError(err, c) return nil, auth, handler.HandleHTTPError(err, c)
} }
if !can { if !can {
_ = s.Rollback() _ = s.Rollback()
log.Infof("Tried to get list background of list %d while not having the rights for it (User: %v)", listID, auth) log.Infof("Tried to get project background of project %d while not having the rights for it (User: %v)", projectID, auth)
return nil, auth, echo.NewHTTPError(http.StatusForbidden) return nil, auth, echo.NewHTTPError(http.StatusForbidden)
} }
return return
} }
// GetListBackground serves a previously set background from a list // GetProjectBackground serves a previously set background from a project
// It has no knowledge of the provider that was responsible for setting the background. // It has no knowledge of the provider that was responsible for setting the background.
// @Summary Get the list background // @Summary Get the project background
// @Description Get the list background of a specific list. **Returns json on error.** // @Description Get the project background of a specific project. **Returns json on error.**
// @tags list // @tags project
// @Produce octet-stream // @Produce octet-stream
// @Param id path int true "List ID" // @Param id path int true "Project ID"
// @Security JWTKeyAuth // @Security JWTKeyAuth
// @Success 200 {} string "The list background file." // @Success 200 {} string "The project background file."
// @Failure 403 {object} models.Message "No access to this list." // @Failure 403 {object} models.Message "No access to this project."
// @Failure 404 {object} models.Message "The list does not exist." // @Failure 404 {object} models.Message "The project does not exist."
// @Failure 500 {object} models.Message "Internal error" // @Failure 500 {object} models.Message "Internal error"
// @Router /lists/{id}/background [get] // @Router /projects/{id}/background [get]
func GetListBackground(c echo.Context) error { func GetProjectBackground(c echo.Context) error {
s := db.NewSession() s := db.NewSession()
defer s.Close() defer s.Close()
list, _, err := checkListBackgroundRights(s, c) project, _, err := checkProjectBackgroundRights(s, c)
if err != nil { if err != nil {
return err return err
} }
if list.BackgroundFileID == 0 { if project.BackgroundFileID == 0 {
_ = s.Rollback() _ = s.Rollback()
return echo.NotFoundHandler(c) return echo.NotFoundHandler(c)
} }
// Get the file // Get the file
bgFile := &files.File{ bgFile := &files.File{
ID: list.BackgroundFileID, ID: project.BackgroundFileID,
} }
if err := bgFile.LoadFileByID(); err != nil { if err := bgFile.LoadFileByID(); err != nil {
_ = s.Rollback() _ = s.Rollback()
@ -320,23 +320,23 @@ func GetListBackground(c echo.Context) error {
return c.Stream(http.StatusOK, "image/jpg", bgFile.File) return c.Stream(http.StatusOK, "image/jpg", bgFile.File)
} }
// RemoveListBackground removes a list background, no matter the background provider // RemoveProjectBackground removes a project background, no matter the background provider
// @Summary Remove a list background // @Summary Remove a project background
// @Description Removes a previously set list background, regardless of the list provider used to set the background. It does not throw an error if the list does not have a background. // @Description Removes a previously set project background, regardless of the project provider used to set the background. It does not throw an error if the project does not have a background.
// @tags list // @tags project
// @Produce json // @Produce json
// @Param id path int true "List ID" // @Param id path int true "Project ID"
// @Security JWTKeyAuth // @Security JWTKeyAuth
// @Success 200 {object} models.List "The list" // @Success 200 {object} models.Project "The project"
// @Failure 403 {object} models.Message "No access to this list." // @Failure 403 {object} models.Message "No access to this project."
// @Failure 404 {object} models.Message "The list does not exist." // @Failure 404 {object} models.Message "The project does not exist."
// @Failure 500 {object} models.Message "Internal error" // @Failure 500 {object} models.Message "Internal error"
// @Router /lists/{id}/background [delete] // @Router /projects/{id}/background [delete]
func RemoveListBackground(c echo.Context) error { func RemoveProjectBackground(c echo.Context) error {
s := db.NewSession() s := db.NewSession()
defer s.Close() defer s.Close()
list, auth, err := checkListBackgroundRights(s, c) project, auth, err := checkProjectBackgroundRights(s, c)
if err != nil { if err != nil {
return err return err
} }
@ -346,13 +346,13 @@ func RemoveListBackground(c echo.Context) error {
return err return err
} }
list.BackgroundFileID = 0 project.BackgroundFileID = 0
list.BackgroundInformation = nil project.BackgroundInformation = nil
list.BackgroundBlurHash = "" project.BackgroundBlurHash = ""
err = models.UpdateList(s, list, auth, true) err = models.UpdateProject(s, project, auth, true)
if err != nil { if err != nil {
return err return err
} }
return c.JSON(http.StatusOK, list) return c.JSON(http.StatusOK, project)
} }

View File

@ -45,7 +45,7 @@ func unsplashImage(url string, c echo.Context) error {
// ProxyUnsplashImage proxies a thumbnail from unsplash for privacy reasons. // ProxyUnsplashImage proxies a thumbnail from unsplash for privacy reasons.
// @Summary Get an unsplash image // @Summary Get an unsplash image
// @Description Get an unsplash image. **Returns json on error.** // @Description Get an unsplash image. **Returns json on error.**
// @tags list // @tags project
// @Produce octet-stream // @Produce octet-stream
// @Param image path int true "Unsplash Image ID" // @Param image path int true "Unsplash Image ID"
// @Security JWTKeyAuth // @Security JWTKeyAuth
@ -65,7 +65,7 @@ func ProxyUnsplashImage(c echo.Context) error {
// ProxyUnsplashThumb proxies a thumbnail from unsplash for privacy reasons. // ProxyUnsplashThumb proxies a thumbnail from unsplash for privacy reasons.
// @Summary Get an unsplash thumbnail image // @Summary Get an unsplash thumbnail image
// @Description Get an unsplash thumbnail image. The thumbnail is cropped to a max width of 200px. **Returns json on error.** // @Description Get an unsplash thumbnail image. The thumbnail is cropped to a max width of 200px. **Returns json on error.**
// @tags list // @tags project
// @Produce octet-stream // @Produce octet-stream
// @Param image path int true "Unsplash Image ID" // @Param image path int true "Unsplash Image ID"
// @Security JWTKeyAuth // @Security JWTKeyAuth

View File

@ -142,8 +142,8 @@ func getUnsplashPhotoInfoByID(photoID string) (photo *Photo, err error) {
// Search is the implementation to search on unsplash // Search is the implementation to search on unsplash
// @Summary Search for a background from unsplash // @Summary Search for a background from unsplash
// @Description Search for a list background from unsplash // @Description Search for a project background from unsplash
// @tags list // @tags project
// @Produce json // @Produce json
// @Security JWTKeyAuth // @Security JWTKeyAuth
// @Param s query string false "Search backgrounds from unsplash with this search term." // @Param s query string false "Search backgrounds from unsplash with this search term."
@ -232,21 +232,21 @@ func (p *Provider) Search(s *xorm.Session, search string, page int64) (result []
return return
} }
// Set sets an unsplash photo as list background // Set sets an unsplash photo as project background
// @Summary Set an unsplash photo as list background // @Summary Set an unsplash photo as project background
// @Description Sets a photo from unsplash as list background. // @Description Sets a photo from unsplash as project background.
// @tags list // @tags project
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Security JWTKeyAuth // @Security JWTKeyAuth
// @Param id path int true "List ID" // @Param id path int true "Project ID"
// @Param list body background.Image true "The image you want to set as background" // @Param project body background.Image true "The image you want to set as background"
// @Success 200 {object} models.List "The background has been successfully set." // @Success 200 {object} models.Project "The background has been successfully set."
// @Failure 400 {object} web.HTTPError "Invalid image object provided." // @Failure 400 {object} web.HTTPError "Invalid image object provided."
// @Failure 403 {object} web.HTTPError "The user does not have access to the list" // @Failure 403 {object} web.HTTPError "The user does not have access to the project"
// @Failure 500 {object} models.Message "Internal error" // @Failure 500 {object} models.Message "Internal error"
// @Router /lists/{id}/backgrounds/unsplash [post] // @Router /projects/{id}/backgrounds/unsplash [post]
func (p *Provider) Set(s *xorm.Session, image *background.Image, list *models.List, auth web.Auth) (err error) { func (p *Provider) Set(s *xorm.Session, image *background.Image, project *models.Project, auth web.Auth) (err error) {
// Find the photo // Find the photo
photo, err := getUnsplashPhotoInfoByID(image.ID) photo, err := getUnsplashPhotoInfoByID(image.ID)
@ -289,13 +289,13 @@ func (p *Provider) Set(s *xorm.Session, image *background.Image, list *models.Li
} }
// Remove the old background if one exists // Remove the old background if one exists
if list.BackgroundFileID != 0 { if project.BackgroundFileID != 0 {
file := files.File{ID: list.BackgroundFileID} file := files.File{ID: project.BackgroundFileID}
if err := file.Delete(); err != nil { if err := file.Delete(); err != nil {
return err return err
} }
if err := models.RemoveUnsplashPhoto(s, list.BackgroundFileID); err != nil { if err := models.RemoveUnsplashPhoto(s, project.BackgroundFileID); err != nil {
return err return err
} }
} }
@ -313,12 +313,12 @@ func (p *Provider) Set(s *xorm.Session, image *background.Image, list *models.Li
} }
log.Debugf("Saved unsplash photo %s as file %d with new entry %d", image.ID, file.ID, unsplashPhoto.ID) log.Debugf("Saved unsplash photo %s as file %d with new entry %d", image.ID, file.ID, unsplashPhoto.ID)
// Set the file in the list // Set the file in the project
list.BackgroundFileID = file.ID project.BackgroundFileID = file.ID
list.BackgroundInformation = unsplashPhoto project.BackgroundInformation = unsplashPhoto
// Set it as the list background // Set it as the project background
return models.SetListBackground(s, list.ID, file, photo.BlurHash) return models.SetProjectBackground(s, project.ID, file, photo.BlurHash)
} }
// Pingback pings the unsplash api if an unsplash photo has been accessed. // Pingback pings the unsplash api if an unsplash photo has been accessed.

View File

@ -37,24 +37,24 @@ func (p *Provider) Search(s *xorm.Session, search string, page int64) (result []
} }
// Set handles setting a background through a file upload // Set handles setting a background through a file upload
// @Summary Upload a list background // @Summary Upload a project background
// @Description Upload a list background. // @Description Upload a project background.
// @tags list // @tags project
// @Accept mpfd // @Accept mpfd
// @Produce json // @Produce json
// @Param id path int true "List ID" // @Param id path int true "Project ID"
// @Param background formData string true "The file as single file." // @Param background formData string true "The file as single file."
// @Security JWTKeyAuth // @Security JWTKeyAuth
// @Success 200 {object} models.Message "The background was set successfully." // @Success 200 {object} models.Message "The background was set successfully."
// @Failure 400 {object} models.Message "File is no image." // @Failure 400 {object} models.Message "File is no image."
// @Failure 403 {object} models.Message "No access to the list." // @Failure 403 {object} models.Message "No access to the project."
// @Failure 403 {object} models.Message "File too large." // @Failure 403 {object} models.Message "File too large."
// @Failure 404 {object} models.Message "The list does not exist." // @Failure 404 {object} models.Message "The project does not exist."
// @Failure 500 {object} models.Message "Internal error" // @Failure 500 {object} models.Message "Internal error"
// @Router /lists/{id}/backgrounds/upload [put] // @Router /projects/{id}/backgrounds/upload [put]
func (p *Provider) Set(s *xorm.Session, img *background.Image, list *models.List, auth web.Auth) (err error) { func (p *Provider) Set(s *xorm.Session, img *background.Image, project *models.Project, auth web.Auth) (err error) {
// Remove the old background if one exists // Remove the old background if one exists
err = list.DeleteBackgroundFileIfExists() err = project.DeleteBackgroundFileIfExists()
if err != nil { if err != nil {
return err return err
} }
@ -65,7 +65,7 @@ func (p *Provider) Set(s *xorm.Session, img *background.Image, list *models.List
return return
} }
list.BackgroundInformation = &models.ListBackgroundType{Type: models.ListBackgroundUpload} project.BackgroundInformation = &models.ProjectBackgroundType{Type: models.ProjectBackgroundUpload}
return models.SetListBackground(s, list.ID, file, list.BackgroundBlurHash) return models.SetProjectBackground(s, project.ID, file, project.BackgroundBlurHash)
} }

View File

@ -31,7 +31,7 @@ import (
// InsertFromStructure takes a fully nested Vikunja data structure and a user and then creates everything for this user // InsertFromStructure takes a fully nested Vikunja data structure and a user and then creates everything for this user
// (Namespaces, tasks, etc. Even attachments and relations.) // (Namespaces, tasks, etc. Even attachments and relations.)
func InsertFromStructure(str []*models.NamespaceWithListsAndTasks, user *user.User) (err error) { func InsertFromStructure(str []*models.NamespaceWithProjectsAndTasks, user *user.User) (err error) {
s := db.NewSession() s := db.NewSession()
defer s.Close() defer s.Close()
@ -45,13 +45,13 @@ func InsertFromStructure(str []*models.NamespaceWithListsAndTasks, user *user.Us
return s.Commit() return s.Commit()
} }
func insertFromStructure(s *xorm.Session, str []*models.NamespaceWithListsAndTasks, user *user.User) (err error) { func insertFromStructure(s *xorm.Session, str []*models.NamespaceWithProjectsAndTasks, user *user.User) (err error) {
log.Debugf("[creating structure] Creating %d namespaces", len(str)) log.Debugf("[creating structure] Creating %d namespaces", len(str))
labels := make(map[string]*models.Label) labels := make(map[string]*models.Label)
archivedLists := []int64{} archivedProjects := []int64{}
archivedNamespaces := []int64{} archivedNamespaces := []int64{}
// Create all namespaces // Create all namespaces
@ -75,18 +75,18 @@ func insertFromStructure(s *xorm.Session, str []*models.NamespaceWithListsAndTas
} }
log.Debugf("[creating structure] Created namespace %d", n.ID) log.Debugf("[creating structure] Created namespace %d", n.ID)
log.Debugf("[creating structure] Creating %d lists", len(n.Lists)) log.Debugf("[creating structure] Creating %d projects", len(n.Projects))
// Create all lists // Create all projects
for _, l := range n.Lists { for _, l := range n.Projects {
// The tasks and bucket slices are going to be reset during the creation of the list so we rescue it here // The tasks and bucket slices are going to be reset during the creation of the project so we rescue it here
// to be able to still loop over them aftere the list was created. // to be able to still loop over them aftere the project was created.
tasks := l.Tasks tasks := l.Tasks
originalBuckets := l.Buckets originalBuckets := l.Buckets
originalBackgroundInformation := l.BackgroundInformation originalBackgroundInformation := l.BackgroundInformation
needsDefaultBucket := false needsDefaultBucket := false
// Saving the archived status to archive the list again after creating it // Saving the archived status to archive the project again after creating it
var wasArchived bool var wasArchived bool
if l.IsArchived { if l.IsArchived {
wasArchived = true wasArchived = true
@ -101,24 +101,24 @@ func insertFromStructure(s *xorm.Session, str []*models.NamespaceWithListsAndTas
} }
if wasArchived { if wasArchived {
archivedLists = append(archivedLists, l.ID) archivedProjects = append(archivedProjects, l.ID)
} }
log.Debugf("[creating structure] Created list %d", l.ID) log.Debugf("[creating structure] Created project %d", l.ID)
bf, is := originalBackgroundInformation.(*bytes.Buffer) bf, is := originalBackgroundInformation.(*bytes.Buffer)
if is { if is {
backgroundFile := bytes.NewReader(bf.Bytes()) backgroundFile := bytes.NewReader(bf.Bytes())
log.Debugf("[creating structure] Creating a background file for list %d", l.ID) log.Debugf("[creating structure] Creating a background file for project %d", l.ID)
err = handler.SaveBackgroundFile(s, user, &l.List, backgroundFile, "", uint64(backgroundFile.Len())) err = handler.SaveBackgroundFile(s, user, &l.Project, backgroundFile, "", uint64(backgroundFile.Len()))
if err != nil { if err != nil {
return err return err
} }
log.Debugf("[creating structure] Created a background file for list %d", l.ID) log.Debugf("[creating structure] Created a background file for project %d", l.ID)
} }
// Create all buckets // Create all buckets
@ -129,7 +129,7 @@ func insertFromStructure(s *xorm.Session, str []*models.NamespaceWithListsAndTas
for _, bucket := range originalBuckets { for _, bucket := range originalBuckets {
oldID := bucket.ID oldID := bucket.ID
bucket.ID = 0 // We want a new id bucket.ID = 0 // We want a new id
bucket.ListID = l.ID bucket.ProjectID = l.ID
err = bucket.Create(s, user) err = bucket.Create(s, user)
if err != nil { if err != nil {
return return
@ -157,7 +157,7 @@ func insertFromStructure(s *xorm.Session, str []*models.NamespaceWithListsAndTas
for _, t := range tasks { for _, t := range tasks {
setBucketOrDefault(&t.Task) setBucketOrDefault(&t.Task)
t.ListID = l.ID t.ProjectID = l.ID
err = t.Create(s, user) err = t.Create(s, user)
if err != nil { if err != nil {
return return
@ -179,7 +179,7 @@ func insertFromStructure(s *xorm.Session, str []*models.NamespaceWithListsAndTas
// First create the related tasks if they do not exist // First create the related tasks if they do not exist
if rt.ID == 0 { if rt.ID == 0 {
setBucketOrDefault(rt) setBucketOrDefault(rt)
rt.ListID = t.ListID rt.ProjectID = t.ProjectID
err = rt.Create(s, user) err = rt.Create(s, user)
if err != nil { if err != nil {
return return
@ -263,7 +263,7 @@ func insertFromStructure(s *xorm.Session, str []*models.NamespaceWithListsAndTas
// All tasks brought their own bucket with them, therefore the newly created default bucket is just extra space // All tasks brought their own bucket with them, therefore the newly created default bucket is just extra space
if !needsDefaultBucket { if !needsDefaultBucket {
b := &models.Bucket{ListID: l.ID} b := &models.Bucket{ProjectID: l.ID}
bucketsIn, _, _, err := b.ReadAll(s, user, "", 1, 1) bucketsIn, _, _, err := b.ReadAll(s, user, "", 1, 1)
if err != nil { if err != nil {
return err return err
@ -280,11 +280,11 @@ func insertFromStructure(s *xorm.Session, str []*models.NamespaceWithListsAndTas
} }
} }
if len(archivedLists) > 0 { if len(archivedProjects) > 0 {
_, err = s. _, err = s.
Cols("is_archived"). Cols("is_archived").
In("id", archivedLists). In("id", archivedProjects).
Update(&models.List{IsArchived: true}) Update(&models.Project{IsArchived: true})
if err != nil { if err != nil {
return err return err
} }

View File

@ -32,16 +32,16 @@ func TestInsertFromStructure(t *testing.T) {
} }
t.Run("normal", func(t *testing.T) { t.Run("normal", func(t *testing.T) {
db.LoadAndAssertFixtures(t) db.LoadAndAssertFixtures(t)
testStructure := []*models.NamespaceWithListsAndTasks{ testStructure := []*models.NamespaceWithProjectsAndTasks{
{ {
Namespace: models.Namespace{ Namespace: models.Namespace{
Title: "Test1", Title: "Test1",
Description: "Lorem Ipsum", Description: "Lorem Ipsum",
}, },
Lists: []*models.ListWithTasksAndBuckets{ Projects: []*models.ProjectWithTasksAndBuckets{
{ {
List: models.List{ Project: models.Project{
Title: "Testlist1", Title: "Testproject1",
Description: "Something", Description: "Something",
}, },
Buckets: []*models.Bucket{ Buckets: []*models.Bucket{
@ -133,19 +133,19 @@ func TestInsertFromStructure(t *testing.T) {
"title": testStructure[0].Namespace.Title, "title": testStructure[0].Namespace.Title,
"description": testStructure[0].Namespace.Description, "description": testStructure[0].Namespace.Description,
}, false) }, false)
db.AssertExists(t, "lists", map[string]interface{}{ db.AssertExists(t, "projects", map[string]interface{}{
"title": testStructure[0].Lists[0].Title, "title": testStructure[0].Projects[0].Title,
"description": testStructure[0].Lists[0].Description, "description": testStructure[0].Projects[0].Description,
}, false) }, false)
db.AssertExists(t, "tasks", map[string]interface{}{ db.AssertExists(t, "tasks", map[string]interface{}{
"title": testStructure[0].Lists[0].Tasks[5].Title, "title": testStructure[0].Projects[0].Tasks[5].Title,
"bucket_id": testStructure[0].Lists[0].Buckets[0].ID, "bucket_id": testStructure[0].Projects[0].Buckets[0].ID,
}, false) }, false)
db.AssertMissing(t, "tasks", map[string]interface{}{ db.AssertMissing(t, "tasks", map[string]interface{}{
"title": testStructure[0].Lists[0].Tasks[6].Title, "title": testStructure[0].Projects[0].Tasks[6].Title,
"bucket_id": 1111, // No task with that bucket should exist "bucket_id": 1111, // No task with that bucket should exist
}) })
assert.NotEqual(t, 0, testStructure[0].Lists[0].Tasks[0].BucketID) // Should get the default bucket assert.NotEqual(t, 0, testStructure[0].Projects[0].Tasks[0].BucketID) // Should get the default bucket
assert.NotEqual(t, 0, testStructure[0].Lists[0].Tasks[6].BucketID) // Should get the default bucket assert.NotEqual(t, 0, testStructure[0].Projects[0].Tasks[6].BucketID) // Should get the default bucket
}) })
} }

View File

@ -98,19 +98,19 @@ type tasksResponse struct {
Value []*task `json:"value"` Value []*task `json:"value"`
} }
type list struct { type project struct {
ID string `json:"id"` ID string `json:"id"`
OdataEtag string `json:"@odata.etag"` OdataEtag string `json:"@odata.etag"`
DisplayName string `json:"displayName"` DisplayName string `json:"displayName"`
IsOwner bool `json:"isOwner"` IsOwner bool `json:"isOwner"`
IsShared bool `json:"isShared"` IsShared bool `json:"isShared"`
WellknownListName string `json:"wellknownListName"` WellknownProjectName string `json:"wellknownProjectName"`
Tasks []*task `json:"-"` // This field does not exist in the api, we're just using it to return a structure with everything at once Tasks []*task `json:"-"` // This field does not exist in the api, we're just using it to return a structure with everything at once
} }
type listsResponse struct { type projectsResponse struct {
OdataContext string `json:"@odata.context"` OdataContext string `json:"@odata.context"`
Value []*list `json:"value"` Value []*project `json:"value"`
} }
func (dtt *dateTimeTimeZone) toTime() (t time.Time, err error) { func (dtt *dateTimeTimeZone) toTime() (t time.Time, err error) {
@ -213,22 +213,22 @@ func makeAuthenticatedGetRequest(token, urlPart string, v interface{}) error {
return json.Unmarshal(buf.Bytes(), v) return json.Unmarshal(buf.Bytes(), v)
} }
func getMicrosoftTodoData(token string) (microsoftTodoData []*list, err error) { func getMicrosoftTodoData(token string) (microsoftTodoData []*project, err error) {
microsoftTodoData = []*list{} microsoftTodoData = []*project{}
lists := &listsResponse{} projects := &projectsResponse{}
err = makeAuthenticatedGetRequest(token, "lists", lists) err = makeAuthenticatedGetRequest(token, "projects", projects)
if err != nil { if err != nil {
log.Errorf("[Microsoft Todo Migration] Could not get lists: %s", err) log.Errorf("[Microsoft Todo Migration] Could not get projects: %s", err)
return return
} }
log.Debugf("[Microsoft Todo Migration] Got %d lists", len(lists.Value)) log.Debugf("[Microsoft Todo Migration] Got %d projects", len(projects.Value))
for _, list := range lists.Value { for _, project := range projects.Value {
link := "lists/" + list.ID + "/tasks" link := "projects/" + project.ID + "/tasks"
list.Tasks = []*task{} project.Tasks = []*task{}
// Microsoft's Graph API has pagination, so we're going through all pages to get all tasks // Microsoft's Graph API has pagination, so we're going through all pages to get all tasks
for { for {
@ -236,13 +236,13 @@ func getMicrosoftTodoData(token string) (microsoftTodoData []*list, err error) {
err = makeAuthenticatedGetRequest(token, link, tr) err = makeAuthenticatedGetRequest(token, link, tr)
if err != nil { if err != nil {
log.Errorf("[Microsoft Todo Migration] Could not get tasks for list %s: %s", list.ID, err) log.Errorf("[Microsoft Todo Migration] Could not get tasks for project %s: %s", project.ID, err)
return return
} }
log.Debugf("[Microsoft Todo Migration] Got %d tasks for list %s", len(tr.Value), list.ID) log.Debugf("[Microsoft Todo Migration] Got %d tasks for project %s", len(tr.Value), project.ID)
list.Tasks = append(list.Tasks, tr.Value...) project.Tasks = append(project.Tasks, tr.Value...)
if tr.Nextlink == "" { if tr.Nextlink == "" {
break break
@ -251,35 +251,35 @@ func getMicrosoftTodoData(token string) (microsoftTodoData []*list, err error) {
link = strings.ReplaceAll(tr.Nextlink, apiPrefix, "") link = strings.ReplaceAll(tr.Nextlink, apiPrefix, "")
} }
microsoftTodoData = append(microsoftTodoData, list) microsoftTodoData = append(microsoftTodoData, project)
} }
log.Debugf("[Microsoft Todo Migration] Got all tasks for %d lists", len(lists.Value)) log.Debugf("[Microsoft Todo Migration] Got all tasks for %d projects", len(projects.Value))
return return
} }
func convertMicrosoftTodoData(todoData []*list) (vikunjsStructure []*models.NamespaceWithListsAndTasks, err error) { func convertMicrosoftTodoData(todoData []*project) (vikunjsStructure []*models.NamespaceWithProjectsAndTasks, err error) {
// One namespace with all lists // One namespace with all projects
vikunjsStructure = []*models.NamespaceWithListsAndTasks{ vikunjsStructure = []*models.NamespaceWithProjectsAndTasks{
{ {
Namespace: models.Namespace{ Namespace: models.Namespace{
Title: "Migrated from Microsoft Todo", Title: "Migrated from Microsoft Todo",
}, },
Lists: []*models.ListWithTasksAndBuckets{}, Projects: []*models.ProjectWithTasksAndBuckets{},
}, },
} }
log.Debugf("[Microsoft Todo Migration] Converting %d lists", len(todoData)) log.Debugf("[Microsoft Todo Migration] Converting %d projects", len(todoData))
for _, l := range todoData { for _, l := range todoData {
log.Debugf("[Microsoft Todo Migration] Converting list %s", l.ID) log.Debugf("[Microsoft Todo Migration] Converting project %s", l.ID)
// Lists only with title // Projects only with title
list := &models.ListWithTasksAndBuckets{ project := &models.ProjectWithTasksAndBuckets{
List: models.List{ Project: models.Project{
Title: l.DisplayName, Title: l.DisplayName,
}, },
} }
@ -358,19 +358,19 @@ func convertMicrosoftTodoData(todoData []*list) (vikunjsStructure []*models.Name
} }
} }
list.Tasks = append(list.Tasks, &models.TaskWithComments{Task: *task}) project.Tasks = append(project.Tasks, &models.TaskWithComments{Task: *task})
log.Debugf("[Microsoft Todo Migration] Done converted %d tasks", len(l.Tasks)) log.Debugf("[Microsoft Todo Migration] Done converted %d tasks", len(l.Tasks))
} }
vikunjsStructure[0].Lists = append(vikunjsStructure[0].Lists, list) vikunjsStructure[0].Projects = append(vikunjsStructure[0].Projects, project)
log.Debugf("[Microsoft Todo Migration] Done converting list %s", l.ID) log.Debugf("[Microsoft Todo Migration] Done converting project %s", l.ID)
} }
return return
} }
// Migrate gets all tasks from Microsoft Todo for a user and puts them into vikunja // Migrate gets all tasks from Microsoft Todo for a user and puts them into vikunja
// @Summary Migrate all lists, tasks etc. from Microsoft Todo // @Summary Migrate all projects, tasks etc. from Microsoft Todo
// @Description Migrates all tasklinsts, tasks, notes and reminders from Microsoft Todo to Vikunja. // @Description Migrates all tasklinsts, tasks, notes and reminders from Microsoft Todo to Vikunja.
// @tags migration // @tags migration
// @Accept json // @Accept json

View File

@ -35,9 +35,9 @@ func TestConverting(t *testing.T) {
testtimeTime, err := time.Parse(time.RFC3339Nano, "2020-12-18T03:00:00.4770000Z") testtimeTime, err := time.Parse(time.RFC3339Nano, "2020-12-18T03:00:00.4770000Z")
assert.NoError(t, err) assert.NoError(t, err)
microsoftTodoData := []*list{ microsoftTodoData := []*project{
{ {
DisplayName: "List 1", DisplayName: "Project 1",
Tasks: []*task{ Tasks: []*task{
{ {
Title: "Task 1", Title: "Task 1",
@ -88,7 +88,7 @@ func TestConverting(t *testing.T) {
}, },
}, },
{ {
DisplayName: "List 2", DisplayName: "Project 2",
Tasks: []*task{ Tasks: []*task{
{ {
Title: "Task 1", Title: "Task 1",
@ -102,15 +102,15 @@ func TestConverting(t *testing.T) {
}, },
} }
expectedHierachie := []*models.NamespaceWithListsAndTasks{ expectedHierachie := []*models.NamespaceWithProjectsAndTasks{
{ {
Namespace: models.Namespace{ Namespace: models.Namespace{
Title: "Migrated from Microsoft Todo", Title: "Migrated from Microsoft Todo",
}, },
Lists: []*models.ListWithTasksAndBuckets{ Projects: []*models.ProjectWithTasksAndBuckets{
{ {
List: models.List{ Project: models.Project{
Title: "List 1", Title: "Project 1",
}, },
Tasks: []*models.TaskWithComments{ Tasks: []*models.TaskWithComments{
{ {
@ -162,8 +162,8 @@ func TestConverting(t *testing.T) {
}, },
}, },
{ {
List: models.List{ Project: models.Project{
Title: "List 2", Title: "Project 2",
}, },
Tasks: []*models.TaskWithComments{ Tasks: []*models.TaskWithComments{
{ {

View File

@ -43,7 +43,7 @@ type Migrator interface {
// FileMigrator handles importing Vikunja data from a file. The implementation of it determines the format. // FileMigrator handles importing Vikunja data from a file. The implementation of it determines the format.
type FileMigrator interface { type FileMigrator interface {
MigratorName MigratorName
// Migrate is the interface used to migrate a user's tasks, list and other things from a file to vikunja. // Migrate is the interface used to migrate a user's tasks, project and other things from a file to vikunja.
// The user object is the user who's tasks will be migrated. // The user object is the user who's tasks will be migrated.
Migrate(user *user.User, file io.ReaderAt, size int64) error Migrate(user *user.User, file io.ReaderAt, size int64) error
} }

View File

@ -105,21 +105,21 @@ func parseDurationPart(value string, unit time.Duration) time.Duration {
return 0 return 0
} }
func convertTickTickToVikunja(tasks []*tickTickTask) (result []*models.NamespaceWithListsAndTasks) { func convertTickTickToVikunja(tasks []*tickTickTask) (result []*models.NamespaceWithProjectsAndTasks) {
namespace := &models.NamespaceWithListsAndTasks{ namespace := &models.NamespaceWithProjectsAndTasks{
Namespace: models.Namespace{ Namespace: models.Namespace{
Title: "Migrated from TickTick", Title: "Migrated from TickTick",
}, },
Lists: []*models.ListWithTasksAndBuckets{}, Projects: []*models.ProjectWithTasksAndBuckets{},
} }
lists := make(map[string]*models.ListWithTasksAndBuckets) projects := make(map[string]*models.ProjectWithTasksAndBuckets)
for _, t := range tasks { for _, t := range tasks {
_, has := lists[t.ListName] _, has := projects[t.ProjectName]
if !has { if !has {
lists[t.ListName] = &models.ListWithTasksAndBuckets{ projects[t.ProjectName] = &models.ProjectWithTasksAndBuckets{
List: models.List{ Project: models.Project{
Title: t.ListName, Title: t.ProjectName,
}, },
} }
} }
@ -158,18 +158,18 @@ func convertTickTickToVikunja(tasks []*tickTickTask) (result []*models.Namespace
} }
} }
lists[t.ListName].Tasks = append(lists[t.ListName].Tasks, task) projects[t.ProjectName].Tasks = append(projects[t.ProjectName].Tasks, task)
} }
for _, l := range lists { for _, l := range projects {
namespace.Lists = append(namespace.Lists, l) namespace.Projects = append(namespace.Projects, l)
} }
sort.Slice(namespace.Lists, func(i, j int) bool { sort.Slice(namespace.Projects, func(i, j int) bool {
return namespace.Lists[i].Title < namespace.Lists[j].Title return namespace.Projects[i].Title < namespace.Projects[j].Title
}) })
return []*models.NamespaceWithListsAndTasks{namespace} return []*models.NamespaceWithProjectsAndTasks{namespace}
} }
// Name is used to get the name of the ticktick migration - we're using the docs here to annotate the status route. // Name is used to get the name of the ticktick migration - we're using the docs here to annotate the status route.
@ -202,7 +202,7 @@ func newLineSkipDecoder(r io.Reader, linesToSkip int) gocsv.SimpleDecoder {
} }
// Migrate takes a ticktick export, parses it and imports everything in it into Vikunja. // Migrate takes a ticktick export, parses it and imports everything in it into Vikunja.
// @Summary Import all lists, tasks etc. from a TickTick backup export // @Summary Import all projects, tasks etc. from a TickTick backup export
// @Description Imports all projects, tasks, notes, reminders, subtasks and files from a TickTick backup export into Vikunja. // @Description Imports all projects, tasks, notes, reminders, subtasks and files from a TickTick backup export into Vikunja.
// @tags migration // @tags migration
// @Accept json // @Accept json

View File

@ -40,76 +40,76 @@ func TestConvertTicktickTasksToVikunja(t *testing.T) {
tickTickTasks := []*tickTickTask{ tickTickTasks := []*tickTickTask{
{ {
TaskID: 1, TaskID: 1,
ParentID: 0, ParentID: 0,
ListName: "List 1", ProjectName: "Project 1",
Title: "Test task 1", Title: "Test task 1",
Tags: []string{"label1", "label2"}, Tags: []string{"label1", "label2"},
Content: "Lorem Ipsum Dolor sit amet", Content: "Lorem Ipsum Dolor sit amet",
StartDate: time1, StartDate: time1,
DueDate: time2, DueDate: time2,
Reminder: duration, Reminder: duration,
Repeat: "FREQ=WEEKLY;INTERVAL=1;UNTIL=20190117T210000Z", Repeat: "FREQ=WEEKLY;INTERVAL=1;UNTIL=20190117T210000Z",
Status: "0", Status: "0",
Order: -1099511627776, Order: -1099511627776,
}, },
{ {
TaskID: 2, TaskID: 2,
ParentID: 1, ParentID: 1,
ListName: "List 1", ProjectName: "Project 1",
Title: "Test task 2", Title: "Test task 2",
Status: "1", Status: "1",
CompletedTime: time3, CompletedTime: time3,
Order: -1099511626, Order: -1099511626,
}, },
{ {
TaskID: 3, TaskID: 3,
ParentID: 0, ParentID: 0,
ListName: "List 1", ProjectName: "Project 1",
Title: "Test task 3", Title: "Test task 3",
Tags: []string{"label1", "label2", "other label"}, Tags: []string{"label1", "label2", "other label"},
StartDate: time1, StartDate: time1,
DueDate: time2, DueDate: time2,
Reminder: duration, Reminder: duration,
Status: "0", Status: "0",
Order: -109951627776, Order: -109951627776,
}, },
{ {
TaskID: 4, TaskID: 4,
ParentID: 0, ParentID: 0,
ListName: "List 2", ProjectName: "Project 2",
Title: "Test task 4", Title: "Test task 4",
Status: "0", Status: "0",
Order: -109951627777, Order: -109951627777,
}, },
} }
vikunjaTasks := convertTickTickToVikunja(tickTickTasks) vikunjaTasks := convertTickTickToVikunja(tickTickTasks)
assert.Len(t, vikunjaTasks, 1) assert.Len(t, vikunjaTasks, 1)
assert.Len(t, vikunjaTasks[0].Lists, 2) assert.Len(t, vikunjaTasks[0].Projects, 2)
assert.Len(t, vikunjaTasks[0].Lists[0].Tasks, 3) assert.Len(t, vikunjaTasks[0].Projects[0].Tasks, 3)
assert.Equal(t, vikunjaTasks[0].Lists[0].Title, tickTickTasks[0].ListName) assert.Equal(t, vikunjaTasks[0].Projects[0].Title, tickTickTasks[0].ProjectName)
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[0].Title, tickTickTasks[0].Title) assert.Equal(t, vikunjaTasks[0].Projects[0].Tasks[0].Title, tickTickTasks[0].Title)
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[0].Description, tickTickTasks[0].Content) assert.Equal(t, vikunjaTasks[0].Projects[0].Tasks[0].Description, tickTickTasks[0].Content)
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[0].StartDate, tickTickTasks[0].StartDate.Time) assert.Equal(t, vikunjaTasks[0].Projects[0].Tasks[0].StartDate, tickTickTasks[0].StartDate)
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[0].EndDate, tickTickTasks[0].DueDate.Time) assert.Equal(t, vikunjaTasks[0].Projects[0].Tasks[0].EndDate, tickTickTasks[0].DueDate)
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[0].DueDate, tickTickTasks[0].DueDate.Time) assert.Equal(t, vikunjaTasks[0].Projects[0].Tasks[0].DueDate, tickTickTasks[0].DueDate)
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[0].Labels, []*models.Label{ assert.Equal(t, vikunjaTasks[0].Projects[0].Tasks[0].Labels, []*models.Label{
{Title: "label1"}, {Title: "label1"},
{Title: "label2"}, {Title: "label2"},
}) })
//assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[0].Reminders, tickTickTasks[0].) // TODO //assert.Equal(t, vikunjaTasks[0].Projects[0].Tasks[0].Reminders, tickTickTasks[0].) // TODO
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[0].Position, tickTickTasks[0].Order) assert.Equal(t, vikunjaTasks[0].Projects[0].Tasks[0].Position, tickTickTasks[0].Order)
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[0].Done, false) assert.Equal(t, vikunjaTasks[0].Projects[0].Tasks[0].Done, false)
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[1].Title, tickTickTasks[1].Title) assert.Equal(t, vikunjaTasks[0].Projects[0].Tasks[1].Title, tickTickTasks[1].Title)
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[1].Position, tickTickTasks[1].Order) assert.Equal(t, vikunjaTasks[0].Projects[0].Tasks[1].Position, tickTickTasks[1].Order)
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[1].Done, true) assert.Equal(t, vikunjaTasks[0].Projects[0].Tasks[1].Done, true)
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[1].DoneAt, tickTickTasks[1].CompletedTime.Time) assert.Equal(t, vikunjaTasks[0].Projects[0].Tasks[1].DoneAt, tickTickTasks[1].CompletedTime.Time)
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[1].RelatedTasks, models.RelatedTaskMap{ assert.Equal(t, vikunjaTasks[0].Projects[0].Tasks[1].RelatedTasks, models.RelatedTaskMap{
models.RelationKindParenttask: []*models.Task{ models.RelationKindParenttask: []*models.Task{
{ {
ID: tickTickTasks[1].ParentID, ID: tickTickTasks[1].ParentID,
@ -117,23 +117,23 @@ func TestConvertTicktickTasksToVikunja(t *testing.T) {
}, },
}) })
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[2].Title, tickTickTasks[2].Title) assert.Equal(t, vikunjaTasks[0].Projects[0].Tasks[2].Title, tickTickTasks[2].Title)
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[2].Description, tickTickTasks[2].Content) assert.Equal(t, vikunjaTasks[0].Projects[0].Tasks[2].Description, tickTickTasks[2].Content)
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[2].StartDate, tickTickTasks[2].StartDate.Time) assert.Equal(t, vikunjaTasks[0].Projects[0].Tasks[2].StartDate, tickTickTasks[2].StartDate.Time)
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[2].EndDate, tickTickTasks[2].DueDate.Time) assert.Equal(t, vikunjaTasks[0].Projects[0].Tasks[2].EndDate, tickTickTasks[2].DueDate.Time)
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[2].DueDate, tickTickTasks[2].DueDate.Time) assert.Equal(t, vikunjaTasks[0].Projects[0].Tasks[2].DueDate, tickTickTasks[2].DueDate.Time)
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[2].Labels, []*models.Label{ assert.Equal(t, vikunjaTasks[0].Projects[0].Tasks[2].Labels, []*models.Label{
{Title: "label1"}, {Title: "label1"},
{Title: "label2"}, {Title: "label2"},
{Title: "other label"}, {Title: "other label"},
}) })
//assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[0].Reminders, tickTickTasks[0].) // TODO //assert.Equal(t, vikunjaTasks[0].Projects[0].Tasks[0].Reminders, tickTickTasks[0].) // TODO
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[2].Position, tickTickTasks[2].Order) assert.Equal(t, vikunjaTasks[0].Projects[0].Tasks[2].Position, tickTickTasks[2].Order)
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[2].Done, false) assert.Equal(t, vikunjaTasks[0].Projects[0].Tasks[2].Done, false)
assert.Len(t, vikunjaTasks[0].Lists[1].Tasks, 1) assert.Len(t, vikunjaTasks[0].Projects[1].Tasks, 1)
assert.Equal(t, vikunjaTasks[0].Lists[1].Title, tickTickTasks[3].ListName) assert.Equal(t, vikunjaTasks[0].Projects[1].Title, tickTickTasks[3].ProjectName)
assert.Equal(t, vikunjaTasks[0].Lists[1].Tasks[0].Title, tickTickTasks[3].Title) assert.Equal(t, vikunjaTasks[0].Projects[1].Tasks[0].Title, tickTickTasks[3].Title)
assert.Equal(t, vikunjaTasks[0].Lists[1].Tasks[0].Position, tickTickTasks[3].Order) assert.Equal(t, vikunjaTasks[0].Projects[1].Tasks[0].Position, tickTickTasks[3].Order)
} }

View File

@ -363,14 +363,14 @@ func TestConvertTodoistToVikunja(t *testing.T) {
}, },
} }
expectedHierachie := []*models.NamespaceWithListsAndTasks{ expectedHierachie := []*models.NamespaceWithProjectsAndTasks{
{ {
Namespace: models.Namespace{ Namespace: models.Namespace{
Title: "Migrated from todoist", Title: "Migrated from todoist",
}, },
Lists: []*models.ListWithTasksAndBuckets{ Projects: []*models.ProjectWithTasksAndBuckets{
{ {
List: models.List{ Project: models.Project{
Title: "Project1", Title: "Project1",
Description: "Lorem Ipsum dolor sit amet\nLorem Ipsum dolor sit amet 2\nLorem Ipsum dolor sit amet 3", Description: "Lorem Ipsum dolor sit amet\nLorem Ipsum dolor sit amet 2\nLorem Ipsum dolor sit amet 3",
HexColor: todoistColors["berry_red"], HexColor: todoistColors["berry_red"],
@ -504,7 +504,7 @@ func TestConvertTodoistToVikunja(t *testing.T) {
}, },
}, },
{ {
List: models.List{ Project: models.Project{
Title: "Project2", Title: "Project2",
Description: "Lorem Ipsum dolor sit amet 4\nLorem Ipsum dolor sit amet 5", Description: "Lorem Ipsum dolor sit amet 4\nLorem Ipsum dolor sit amet 5",
HexColor: todoistColors["mint_green"], HexColor: todoistColors["mint_green"],
@ -602,7 +602,7 @@ func TestConvertTodoistToVikunja(t *testing.T) {
}, },
}, },
{ {
List: models.List{ Project: models.Project{
Title: "Project3 - Archived", Title: "Project3 - Archived",
HexColor: todoistColors["mint_green"], HexColor: todoistColors["mint_green"],
IsArchived: true, IsArchived: true,

View File

@ -99,14 +99,14 @@ func getTrelloData(token string) (trelloData []*trello.Board, err error) {
log.Debugf("[Trello Migration] Got %d trello boards", len(trelloData)) log.Debugf("[Trello Migration] Got %d trello boards", len(trelloData))
for _, board := range trelloData { for _, board := range trelloData {
log.Debugf("[Trello Migration] Getting lists for board %s", board.ID) log.Debugf("[Trello Migration] Getting projects for board %s", board.ID)
board.Lists, err = board.GetLists(trello.Defaults()) board.Lists, err = board.GetLists(trello.Defaults())
if err != nil { if err != nil {
return return
} }
log.Debugf("[Trello Migration] Got %d lists for board %s", len(board.Lists), board.ID) log.Debugf("[Trello Migration] Got %d projects for board %s", len(board.Lists), board.ID)
listMap := make(map[string]*trello.List, len(board.Lists)) listMap := make(map[string]*trello.List, len(board.Lists))
for _, list := range board.Lists { for _, list := range board.Lists {
@ -161,27 +161,27 @@ func getTrelloData(token string) (trelloData []*trello.Board, err error) {
} }
// Converts all previously obtained data from trello into the vikunja format. // Converts all previously obtained data from trello into the vikunja format.
// `trelloData` should contain all boards with their lists and cards respectively. // `trelloData` should contain all boards with their projects and cards respectively.
func convertTrelloDataToVikunja(trelloData []*trello.Board, token string) (fullVikunjaHierachie []*models.NamespaceWithListsAndTasks, err error) { func convertTrelloDataToVikunja(trelloData []*trello.Board, token string) (fullVikunjaHierachie []*models.NamespaceWithProjectsAndTasks, err error) {
log.Debugf("[Trello Migration] ") log.Debugf("[Trello Migration] ")
fullVikunjaHierachie = []*models.NamespaceWithListsAndTasks{ fullVikunjaHierachie = []*models.NamespaceWithProjectsAndTasks{
{ {
Namespace: models.Namespace{ Namespace: models.Namespace{
Title: "Imported from Trello", Title: "Imported from Trello",
}, },
Lists: []*models.ListWithTasksAndBuckets{}, Projects: []*models.ProjectWithTasksAndBuckets{},
}, },
} }
var bucketID int64 = 1 var bucketID int64 = 1
log.Debugf("[Trello Migration] Converting %d boards to vikunja lists", len(trelloData)) log.Debugf("[Trello Migration] Converting %d boards to vikunja projects", len(trelloData))
for _, board := range trelloData { for _, board := range trelloData {
list := &models.ListWithTasksAndBuckets{ project := &models.ProjectWithTasksAndBuckets{
List: models.List{ Project: models.Project{
Title: board.Name, Title: board.Name,
Description: board.Desc, Description: board.Desc,
IsArchived: board.Closed, IsArchived: board.Closed,
@ -189,7 +189,7 @@ func convertTrelloDataToVikunja(trelloData []*trello.Board, token string) (fullV
} }
// Background // Background
// We're pretty much abusing the backgroundinformation field here - not sure if this is really better than adding a new property to the list // We're pretty much abusing the backgroundinformation field here - not sure if this is really better than adding a new property to the project
if board.Prefs.BackgroundImage != "" { if board.Prefs.BackgroundImage != "" {
log.Debugf("[Trello Migration] Downloading background %s for board %s", board.Prefs.BackgroundImage, board.ID) log.Debugf("[Trello Migration] Downloading background %s for board %s", board.Prefs.BackgroundImage, board.ID)
buf, err := migration.DownloadFile(board.Prefs.BackgroundImage) buf, err := migration.DownloadFile(board.Prefs.BackgroundImage)
@ -197,7 +197,7 @@ func convertTrelloDataToVikunja(trelloData []*trello.Board, token string) (fullV
return nil, err return nil, err
} }
log.Debugf("[Trello Migration] Downloaded background %s for board %s", board.Prefs.BackgroundImage, board.ID) log.Debugf("[Trello Migration] Downloaded background %s for board %s", board.Prefs.BackgroundImage, board.ID)
list.BackgroundInformation = buf project.BackgroundInformation = buf
} else { } else {
log.Debugf("[Trello Migration] Board %s does not have a background image, not copying...", board.ID) log.Debugf("[Trello Migration] Board %s does not have a background image, not copying...", board.ID)
} }
@ -291,23 +291,23 @@ func convertTrelloDataToVikunja(trelloData []*trello.Board, token string) (fullV
log.Debugf("[Trello Migration] Downloaded card attachment %s", attachment.ID) log.Debugf("[Trello Migration] Downloaded card attachment %s", attachment.ID)
} }
list.Tasks = append(list.Tasks, &models.TaskWithComments{Task: *task}) project.Tasks = append(project.Tasks, &models.TaskWithComments{Task: *task})
} }
list.Buckets = append(list.Buckets, bucket) project.Buckets = append(project.Buckets, bucket)
bucketID++ bucketID++
} }
log.Debugf("[Trello Migration] Converted all cards to tasks for board %s", board.ID) log.Debugf("[Trello Migration] Converted all cards to tasks for board %s", board.ID)
fullVikunjaHierachie[0].Lists = append(fullVikunjaHierachie[0].Lists, list) fullVikunjaHierachie[0].Projects = append(fullVikunjaHierachie[0].Projects, project)
} }
return return
} }
// Migrate gets all tasks from trello for a user and puts them into vikunja // Migrate gets all tasks from trello for a user and puts them into vikunja
// @Summary Migrate all lists, tasks etc. from trello // @Summary Migrate all projects, tasks etc. from trello
// @Description Migrates all projects, tasks, notes, reminders, subtasks and files from trello to vikunja. // @Description Migrates all projects, tasks, notes, reminders, subtasks and files from trello to vikunja.
// @tags migration // @tags migration
// @Accept json // @Accept json

View File

@ -44,9 +44,9 @@ func TestConvertTrelloToVikunja(t *testing.T) {
Name: "TestBoard", Name: "TestBoard",
Desc: "This is a description", Desc: "This is a description",
Closed: false, Closed: false,
Lists: []*trello.List{ Projects: []*trello.Project{
{ {
Name: "Test List 1", Name: "Test Project 1",
Cards: []*trello.Card{ Cards: []*trello.Card{
{ {
Name: "Test Card 1", Name: "Test Card 1",
@ -77,9 +77,9 @@ func TestConvertTrelloToVikunja(t *testing.T) {
{ {
Name: "Test Card 2", Name: "Test Card 2",
Pos: 124, Pos: 124,
Checklists: []*trello.Checklist{ Checkprojects: []*trello.Checkproject{
{ {
Name: "Checklist 1", Name: "Checkproject 1",
CheckItems: []trello.CheckItem{ CheckItems: []trello.CheckItem{
{ {
State: "pending", State: "pending",
@ -92,7 +92,7 @@ func TestConvertTrelloToVikunja(t *testing.T) {
}, },
}, },
{ {
Name: "Checklist 2", Name: "Checkproject 2",
CheckItems: []trello.CheckItem{ CheckItems: []trello.CheckItem{
{ {
State: "pending", State: "pending",
@ -124,7 +124,7 @@ func TestConvertTrelloToVikunja(t *testing.T) {
}, },
}, },
{ {
Name: "Test List 2", Name: "Test Project 2",
Cards: []*trello.Card{ Cards: []*trello.Card{
{ {
Name: "Test Card 5", Name: "Test Card 5",
@ -157,9 +157,9 @@ func TestConvertTrelloToVikunja(t *testing.T) {
{ {
Name: "TestBoard 2", Name: "TestBoard 2",
Closed: false, Closed: false,
Lists: []*trello.List{ Projects: []*trello.Project{
{ {
Name: "Test List 4", Name: "Test Project 4",
Cards: []*trello.Card{ Cards: []*trello.Card{
{ {
Name: "Test Card 634", Name: "Test Card 634",
@ -172,9 +172,9 @@ func TestConvertTrelloToVikunja(t *testing.T) {
{ {
Name: "TestBoard Archived", Name: "TestBoard Archived",
Closed: true, Closed: true,
Lists: []*trello.List{ Projects: []*trello.Project{
{ {
Name: "Test List 5", Name: "Test Project 5",
Cards: []*trello.Card{ Cards: []*trello.Card{
{ {
Name: "Test Card 63423", Name: "Test Card 63423",
@ -187,14 +187,14 @@ func TestConvertTrelloToVikunja(t *testing.T) {
} }
trelloData[0].Prefs.BackgroundImage = "https://vikunja.io/testimage.jpg" // Using an image which we are hosting, so it'll still be up trelloData[0].Prefs.BackgroundImage = "https://vikunja.io/testimage.jpg" // Using an image which we are hosting, so it'll still be up
expectedHierachie := []*models.NamespaceWithListsAndTasks{ expectedHierachie := []*models.NamespaceWithProjectsAndTasks{
{ {
Namespace: models.Namespace{ Namespace: models.Namespace{
Title: "Imported from Trello", Title: "Imported from Trello",
}, },
Lists: []*models.ListWithTasksAndBuckets{ Projects: []*models.ProjectWithTasksAndBuckets{
{ {
List: models.List{ Project: models.Project{
Title: "TestBoard", Title: "TestBoard",
Description: "This is a description", Description: "This is a description",
BackgroundInformation: bytes.NewBuffer(exampleFile), BackgroundInformation: bytes.NewBuffer(exampleFile),
@ -202,11 +202,11 @@ func TestConvertTrelloToVikunja(t *testing.T) {
Buckets: []*models.Bucket{ Buckets: []*models.Bucket{
{ {
ID: 1, ID: 1,
Title: "Test List 1", Title: "Test Project 1",
}, },
{ {
ID: 2, ID: 2,
Title: "Test List 2", Title: "Test Project 2",
}, },
}, },
Tasks: []*models.TaskWithComments{ Tasks: []*models.TaskWithComments{
@ -244,12 +244,12 @@ func TestConvertTrelloToVikunja(t *testing.T) {
Title: "Test Card 2", Title: "Test Card 2",
Description: ` Description: `
## Checklist 1 ## Checkproject 1
* [ ] Pending Task * [ ] Pending Task
* [x] Completed Task * [x] Completed Task
## Checklist 2 ## Checkproject 2
* [ ] Pending Task * [ ] Pending Task
* [ ] Another Pending Task`, * [ ] Another Pending Task`,
@ -315,13 +315,13 @@ func TestConvertTrelloToVikunja(t *testing.T) {
}, },
}, },
{ {
List: models.List{ Project: models.Project{
Title: "TestBoard 2", Title: "TestBoard 2",
}, },
Buckets: []*models.Bucket{ Buckets: []*models.Bucket{
{ {
ID: 3, ID: 3,
Title: "Test List 4", Title: "Test Project 4",
}, },
}, },
Tasks: []*models.TaskWithComments{ Tasks: []*models.TaskWithComments{
@ -335,14 +335,14 @@ func TestConvertTrelloToVikunja(t *testing.T) {
}, },
}, },
{ {
List: models.List{ Project: models.Project{
Title: "TestBoard Archived", Title: "TestBoard Archived",
IsArchived: true, IsArchived: true,
}, },
Buckets: []*models.Bucket{ Buckets: []*models.Bucket{
{ {
ID: 4, ID: 4,
Title: "Test List 5", Title: "Test Project 5",
}, },
}, },
Tasks: []*models.TaskWithComments{ Tasks: []*models.TaskWithComments{

View File

@ -51,7 +51,7 @@ func (v *FileMigrator) Name() string {
} }
// Migrate takes a vikunja file export, parses it and imports everything in it into Vikunja. // Migrate takes a vikunja file export, parses it and imports everything in it into Vikunja.
// @Summary Import all lists, tasks etc. from a Vikunja data export // @Summary Import all projects, tasks etc. from a Vikunja data export
// @Description Imports all projects, tasks, notes, reminders, subtasks and files from a Vikunjda data export into Vikunja. // @Description Imports all projects, tasks, notes, reminders, subtasks and files from a Vikunjda data export into Vikunja.
// @tags migration // @tags migration
// @Accept json // @Accept json
@ -113,21 +113,21 @@ func (v *FileMigrator) Migrate(user *user.User, file io.ReaderAt, size int64) er
return fmt.Errorf("could not read data file: %w", err) return fmt.Errorf("could not read data file: %w", err)
} }
namespaces := []*models.NamespaceWithListsAndTasks{} namespaces := []*models.NamespaceWithProjectsAndTasks{}
if err := json.Unmarshal(bufData.Bytes(), &namespaces); err != nil { if err := json.Unmarshal(bufData.Bytes(), &namespaces); err != nil {
return fmt.Errorf("could not read data: %w", err) return fmt.Errorf("could not read data: %w", err)
} }
for _, n := range namespaces { for _, n := range namespaces {
for _, l := range n.Lists { for _, l := range n.Projects {
if b, exists := storedFiles[l.BackgroundFileID]; exists { if b, exists := storedFiles[l.BackgroundFileID]; exists {
bf, err := b.Open() bf, err := b.Open()
if err != nil { if err != nil {
return fmt.Errorf("could not open list background file %d for reading: %w", l.BackgroundFileID, err) return fmt.Errorf("could not open project background file %d for reading: %w", l.BackgroundFileID, err)
} }
var buf bytes.Buffer var buf bytes.Buffer
if _, err := buf.ReadFrom(bf); err != nil { if _, err := buf.ReadFrom(bf); err != nil {
return fmt.Errorf("could not read list background file %d: %w", l.BackgroundFileID, err) return fmt.Errorf("could not read project background file %d: %w", l.BackgroundFileID, err)
} }
l.BackgroundInformation = &buf l.BackgroundInformation = &buf

View File

@ -48,12 +48,12 @@ func TestVikunjaFileMigrator_Migrate(t *testing.T) {
"title": "test", "title": "test",
"owner_id": u.ID, "owner_id": u.ID,
}, false) }, false)
db.AssertExists(t, "lists", map[string]interface{}{ db.AssertExists(t, "projects", map[string]interface{}{
"title": "Test list", "title": "Test project",
"owner_id": u.ID, "owner_id": u.ID,
}, false) }, false)
db.AssertExists(t, "lists", map[string]interface{}{ db.AssertExists(t, "projects", map[string]interface{}{
"title": "A list with a background", "title": "A project with a background",
"owner_id": u.ID, "owner_id": u.ID,
}, false) }, false)
db.AssertExists(t, "tasks", map[string]interface{}{ db.AssertExists(t, "tasks", map[string]interface{}{

File diff suppressed because one or more lines are too long

View File

@ -31,7 +31,7 @@ import (
type LinkShareToken struct { type LinkShareToken struct {
auth.Token auth.Token
*models.LinkSharing *models.LinkSharing
ListID int64 `json:"list_id"` ProjectID int64 `json:"project_id"`
} }
// LinkShareAuth represents everything required to authenticate a link share // LinkShareAuth represents everything required to authenticate a link share
@ -42,7 +42,7 @@ type LinkShareAuth struct {
// AuthenticateLinkShare gives a jwt auth token for valid share hashes // AuthenticateLinkShare gives a jwt auth token for valid share hashes
// @Summary Get an auth token for a share // @Summary Get an auth token for a share
// @Description Get a jwt auth token for a shared list from a share hash. // @Description Get a jwt auth token for a shared project from a share hash.
// @tags sharing // @tags sharing
// @Accept json // @Accept json
// @Produce json // @Produce json
@ -84,6 +84,6 @@ func AuthenticateLinkShare(c echo.Context) error {
return c.JSON(http.StatusOK, LinkShareToken{ return c.JSON(http.StatusOK, LinkShareToken{
Token: auth.Token{Token: t}, Token: auth.Token{Token: t},
LinkSharing: share, LinkSharing: share,
ListID: share.ListID, ProjectID: share.ProjectID,
}) })
} }

View File

@ -29,21 +29,21 @@ import (
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
) )
// GetListsByNamespaceID is the web handler to delete a namespace // GetProjectsByNamespaceID is the web handler to delete a namespace
// TODO: depricate this in favour of namespace.ReadOne() <-- should also return the lists // TODO: depricate this in favour of namespace.ReadOne() <-- should also return the projects
// @Summary Get all lists in a namespace // @Summary Get all projects in a namespace
// @Description Returns all lists inside of a namespace. // @Description Returns all projects inside of a namespace.
// @tags namespace // @tags namespace
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param id path int true "Namespace ID" // @Param id path int true "Namespace ID"
// @Security JWTKeyAuth // @Security JWTKeyAuth
// @Success 200 {array} models.List "The lists." // @Success 200 {array} models.Project "The projects."
// @Failure 403 {object} models.Message "No access to that namespace." // @Failure 403 {object} models.Message "No access to that namespace."
// @Failure 404 {object} models.Message "The namespace does not exist." // @Failure 404 {object} models.Message "The namespace does not exist."
// @Failure 500 {object} models.Message "Internal error" // @Failure 500 {object} models.Message "Internal error"
// @Router /namespaces/{id}/lists [get] // @Router /namespaces/{id}/projects [get]
func GetListsByNamespaceID(c echo.Context) error { func GetProjectsByNamespaceID(c echo.Context) error {
s := db.NewSession() s := db.NewSession()
defer s.Close() defer s.Close()
@ -53,17 +53,17 @@ func GetListsByNamespaceID(c echo.Context) error {
return handler.HandleHTTPError(err, c) return handler.HandleHTTPError(err, c)
} }
// Get the lists // Get the projects
doer, err := user.GetCurrentUser(c) doer, err := user.GetCurrentUser(c)
if err != nil { if err != nil {
return handler.HandleHTTPError(err, c) return handler.HandleHTTPError(err, c)
} }
lists, err := models.GetListsByNamespaceID(s, namespace.ID, doer) projects, err := models.GetProjectsByNamespaceID(s, namespace.ID, doer)
if err != nil { if err != nil {
return handler.HandleHTTPError(err, c) return handler.HandleHTTPError(err, c)
} }
return c.JSON(http.StatusOK, lists) return c.JSON(http.StatusOK, projects)
} }
func getNamespace(s *xorm.Session, c echo.Context) (namespace *models.Namespace, err error) { func getNamespace(s *xorm.Session, c echo.Context) (namespace *models.Namespace, err error) {
@ -76,7 +76,7 @@ func getNamespace(s *xorm.Session, c echo.Context) (namespace *models.Namespace,
} }
if namespaceID == -1 { if namespaceID == -1 {
namespace = &models.SharedListsPseudoNamespace namespace = &models.SharedProjectsPseudoNamespace
return return
} }

View File

@ -54,7 +54,7 @@ func GenerateCaldavToken(c echo.Context) (err error) {
return c.JSON(http.StatusCreated, token) return c.JSON(http.StatusCreated, token)
} }
// GetCaldavTokens is the handler to return a list of all caldav tokens for the current user // GetCaldavTokens is the handler to return a project of all caldav tokens for the current user
// @Summary Returns the caldav tokens for the current user // @Summary Returns the caldav tokens for the current user
// @Description Return the IDs and created dates of all caldav tokens for the current user. // @Description Return the IDs and created dates of all caldav tokens for the current user.
// @tags user // @tags user

View File

@ -29,7 +29,7 @@ import (
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
) )
// UserList gets all information about a user // UserProject gets all information about a user
// @Summary Get users // @Summary Get users
// @Description Search for a user by its username, name or full email. Name (not username) or email require that the user has enabled this in their settings. // @Description Search for a user by its username, name or full email. Name (not username) or email require that the user has enabled this in their settings.
// @tags user // @tags user
@ -41,13 +41,13 @@ import (
// @Failure 400 {object} web.HTTPError "Something's invalid." // @Failure 400 {object} web.HTTPError "Something's invalid."
// @Failure 500 {object} models.Message "Internal server error." // @Failure 500 {object} models.Message "Internal server error."
// @Router /users [get] // @Router /users [get]
func UserList(c echo.Context) error { func UserProject(c echo.Context) error {
search := c.QueryParam("s") search := c.QueryParam("s")
s := db.NewSession() s := db.NewSession()
defer s.Close() defer s.Close()
users, err := user.ListUsers(s, search, nil) users, err := user.ProjectUsers(s, search, nil)
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err, c) return handler.HandleHTTPError(err, c)
@ -66,27 +66,27 @@ func UserList(c echo.Context) error {
return c.JSON(http.StatusOK, users) return c.JSON(http.StatusOK, users)
} }
// ListUsersForList returns a list with all users who have access to a list, regardless of the method the list was shared with them. // ProjectUsersForProject returns a project with all users who have access to a project, regardless of the method the project was shared with them.
// @Summary Get users // @Summary Get users
// @Description Lists all users (without emailadresses). Also possible to search for a specific user. // @Description Projects all users (without emailadresses). Also possible to search for a specific user.
// @tags list // @tags project
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param s query string false "Search for a user by its name." // @Param s query string false "Search for a user by its name."
// @Security JWTKeyAuth // @Security JWTKeyAuth
// @Param id path int true "List ID" // @Param id path int true "Project ID"
// @Success 200 {array} user.User "All (found) users." // @Success 200 {array} user.User "All (found) users."
// @Failure 400 {object} web.HTTPError "Something's invalid." // @Failure 400 {object} web.HTTPError "Something's invalid."
// @Failure 401 {object} web.HTTPError "The user does not have the right to see the list." // @Failure 401 {object} web.HTTPError "The user does not have the right to see the project."
// @Failure 500 {object} models.Message "Internal server error." // @Failure 500 {object} models.Message "Internal server error."
// @Router /lists/{id}/listusers [get] // @Router /projects/{id}/projectusers [get]
func ListUsersForList(c echo.Context) error { func ProjectUsersForProject(c echo.Context) error {
listID, err := strconv.ParseInt(c.Param("list"), 10, 64) projectID, err := strconv.ParseInt(c.Param("project"), 10, 64)
if err != nil { if err != nil {
return handler.HandleHTTPError(err, c) return handler.HandleHTTPError(err, c)
} }
list := models.List{ID: listID} project := models.Project{ID: projectID}
auth, err := auth2.GetAuthFromClaims(c) auth, err := auth2.GetAuthFromClaims(c)
if err != nil { if err != nil {
return handler.HandleHTTPError(err, c) return handler.HandleHTTPError(err, c)
@ -95,7 +95,7 @@ func ListUsersForList(c echo.Context) error {
s := db.NewSession() s := db.NewSession()
defer s.Close() defer s.Close()
canRead, _, err := list.CanRead(s, auth) canRead, _, err := project.CanRead(s, auth)
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err, c) return handler.HandleHTTPError(err, c)
@ -105,7 +105,7 @@ func ListUsersForList(c echo.Context) error {
} }
search := c.QueryParam("s") search := c.QueryParam("s")
users, err := models.ListUsersFromList(s, &list, search) users, err := models.ProjectUsersFromProject(s, &project, search)
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err, c) return handler.HandleHTTPError(err, c)

Some files were not shown because too many files have changed in this diff Show More