feat: remove namespaces, make projects infinitely nestable #1362

Merged
konrad merged 68 commits from feature/namespaces-be-gone into main 2023-05-24 14:14:03 +00:00
8 changed files with 76 additions and 73 deletions
Showing only changes of commit 96a0f5e169 - Show all commits

View File

@ -26,12 +26,12 @@ import (
func TestUserProject(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

@ -54,9 +54,11 @@ func (p *Provider) Search(_ *xorm.Session, _ string, _ int64) (result []*backgro
// @Router /projects/{id}/backgrounds/upload [put] // @Router /projects/{id}/backgrounds/upload [put]
func (p *Provider) Set(s *xorm.Session, img *background.Image, project *models.Project, _ web.Auth) (err error) { func (p *Provider) Set(s *xorm.Session, img *background.Image, project *models.Project, _ web.Auth) (err error) {
// Remove the old background if one exists // Remove the old background if one exists
err = project.DeleteBackgroundFileIfExists() if project.BackgroundFileID != 0 {
if err != nil { file := files.File{ID: project.BackgroundFileID}
return err if err := file.Delete(); err != nil {
return err
}
} }
file := &files.File{} file := &files.File{}

View File

@ -39,26 +39,26 @@ type Migrator struct {
} }
type tickTickTask struct { type tickTickTask struct {
FolderName string `csv:"Folder Name"` FolderName string`csv:"Folder Name"`
ListName string `csv:"List Name"` ProjectName string`csv:"List Name"`
Title string `csv:"Title"` Title string`csv:"Title"`
TagsList string `csv:"Tags"` TagsList string `csv:"Tags"`
Tags []string `csv:"-"` Tags []string`csv:"-"`
Content string `csv:"Content"` Content string`csv:"Content"`
IsChecklistString string `csv:"Is Check list"` IsChecklistString string `csv:"Is Check list"`
IsChecklist bool `csv:"-"` IsCheckproject bool`csv:"-"`
StartDate tickTickTime `csv:"Start Date"` StartDate tickTickTime `csv:"Start Date"`
DueDate tickTickTime `csv:"Due Date"` DueDate tickTickTime `csv:"Due Date"`
ReminderDuration string `csv:"Reminder"` ReminderDuration string `csv:"Reminder"`
Reminder time.Duration `csv:"-"` Reminder time.Duration`csv:"-"`
Repeat string `csv:"Repeat"` Repeat string`csv:"Repeat"`
Priority int `csv:"Priority"` Priority int`csv:"Priority"`
Status string `csv:"Status"` Status string`csv:"Status"`
CreatedTime tickTickTime `csv:"Created Time"` CreatedTime tickTickTime `csv:"Created Time"`
CompletedTime tickTickTime `csv:"Completed Time"` CompletedTime tickTickTime `csv:"Completed Time"`
Order float64 `csv:"Order"` Order float64`csv:"Order"`
TaskID int64 `csv:"taskId"` TaskID int64`csv:"taskId"`
ParentID int64 `csv:"parentId"` ParentID int64`csv:"parentId"`
} }
type tickTickTime struct { type tickTickTime struct {
@ -84,11 +84,11 @@ func convertTickTickToVikunja(tasks []*tickTickTask) (result []*models.Namespace
projects := make(map[string]*models.ProjectWithTasksAndBuckets) projects := make(map[string]*models.ProjectWithTasksAndBuckets)
for _, t := range tasks { for _, t := range tasks {
_, has := projects[t.ListName] _, has := projects[t.ProjectName]
if !has { if !has {
projects[t.ListName] = &models.ProjectWithTasksAndBuckets{ projects[t.ProjectName] = &models.ProjectWithTasksAndBuckets{
Project: models.Project{ Project: models.Project{
Title: t.ListName, Title: t.ProjectName,
}, },
} }
} }
@ -130,7 +130,7 @@ func convertTickTickToVikunja(tasks []*tickTickTask) (result []*models.Namespace
} }
} }
projects[t.ListName].Tasks = append(projects[t.ListName].Tasks, task) projects[t.ProjectName].Tasks = append(projects[t.ProjectName].Tasks, task)
} }
for _, l := range projects { for _, l := range projects {

View File

@ -40,47 +40,47 @@ func TestConvertTicktickTasksToVikunja(t *testing.T) {
tickTickTasks := []*tickTickTask{ tickTickTasks := []*tickTickTask{
{ {
TaskID: 1, TaskID: 1,
ParentID: 0, ParentID: 0,
ListName: "Project 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: "Project 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: "Project 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: "Project 2", ProjectName: "Project 2",
Title: "Test task 4", Title: "Test task 4",
Status: "0", Status: "0",
Order: -109951627777, Order: -109951627777,
}, },
} }
@ -90,7 +90,7 @@ func TestConvertTicktickTasksToVikunja(t *testing.T) {
assert.Len(t, vikunjaTasks[0].Projects, 2) assert.Len(t, vikunjaTasks[0].Projects, 2)
assert.Len(t, vikunjaTasks[0].Projects[0].Tasks, 3) assert.Len(t, vikunjaTasks[0].Projects[0].Tasks, 3)
assert.Equal(t, vikunjaTasks[0].Projects[0].Title, tickTickTasks[0].ListName) assert.Equal(t, vikunjaTasks[0].Projects[0].Title, tickTickTasks[0].ProjectName)
assert.Equal(t, vikunjaTasks[0].Projects[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].Projects[0].Tasks[0].Description, tickTickTasks[0].Content) assert.Equal(t, vikunjaTasks[0].Projects[0].Tasks[0].Description, tickTickTasks[0].Content)
@ -134,7 +134,7 @@ func TestConvertTicktickTasksToVikunja(t *testing.T) {
assert.Equal(t, vikunjaTasks[0].Projects[0].Tasks[2].Done, false) assert.Equal(t, vikunjaTasks[0].Projects[0].Tasks[2].Done, false)
assert.Len(t, vikunjaTasks[0].Projects[1].Tasks, 1) assert.Len(t, vikunjaTasks[0].Projects[1].Tasks, 1)
assert.Equal(t, vikunjaTasks[0].Projects[1].Title, tickTickTasks[3].ListName) assert.Equal(t, vikunjaTasks[0].Projects[1].Title, tickTickTasks[3].ProjectName)
assert.Equal(t, vikunjaTasks[0].Projects[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].Projects[1].Tasks[0].Position, tickTickTasks[3].Order) assert.Equal(t, vikunjaTasks[0].Projects[1].Tasks[0].Position, tickTickTasks[3].Order)

View File

@ -44,7 +44,7 @@ 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 Project 1", Name: "Test Project 1",
Cards: []*trello.Card{ Cards: []*trello.Card{
@ -77,7 +77,7 @@ func TestConvertTrelloToVikunja(t *testing.T) {
{ {
Name: "Test Card 2", Name: "Test Card 2",
Pos: 124, Pos: 124,
Checklists: []*trello.Checklist{ Checkprojects: []*trello.Checkproject{
{ {
Name: "Checkproject 1", Name: "Checkproject 1",
CheckItems: []trello.CheckItem{ CheckItems: []trello.CheckItem{
@ -157,7 +157,7 @@ func TestConvertTrelloToVikunja(t *testing.T) {
{ {
Name: "TestBoard 2", Name: "TestBoard 2",
Closed: false, Closed: false,
Lists: []*trello.List{ Projects: []*trello.Project{
{ {
Name: "Test Project 4", Name: "Test Project 4",
Cards: []*trello.Card{ Cards: []*trello.Card{
@ -172,7 +172,7 @@ func TestConvertTrelloToVikunja(t *testing.T) {
{ {
Name: "TestBoard Archived", Name: "TestBoard Archived",
Closed: true, Closed: true,
Lists: []*trello.List{ Projects: []*trello.Project{
{ {
Name: "Test Project 5", Name: "Test Project 5",
Cards: []*trello.Card{ Cards: []*trello.Card{

View File

@ -47,6 +47,7 @@
package routes package routes
import ( import (
"code.vikunja.io/api/pkg/modules/migration/wunderlist"
"errors" "errors"
"net/url" "net/url"
"strings" "strings"
@ -286,7 +287,7 @@ func registerAPIRoutes(a *echo.Group) {
u.GET("", apiv1.UserShow) u.GET("", apiv1.UserShow)
u.POST("/password", apiv1.UserChangePassword) u.POST("/password", apiv1.UserChangePassword)
u.GET("s", apiv1.UserList) u.GET("s", apiv1.UserProject)
u.POST("/token", apiv1.RenewToken) u.POST("/token", apiv1.RenewToken)
u.POST("/settings/email", apiv1.UpdateUserEmail) u.POST("/settings/email", apiv1.UpdateUserEmail)
u.GET("/settings/avatar", apiv1.GetUserAvatarProvider) u.GET("/settings/avatar", apiv1.GetUserAvatarProvider)

View File

@ -377,7 +377,7 @@ func TestProjectUsers(t *testing.T) {
s := db.NewSession() s := db.NewSession()
defer s.Close() defer s.Close()
all, err := ListUsers(s, "user1", nil) all, err := ProjectUsers(s, "user1", nil)
assert.NoError(t, err) assert.NoError(t, err)
assert.True(t, len(all) > 0) assert.True(t, len(all) > 0)
assert.Equal(t, all[0].Username, "user1") assert.Equal(t, all[0].Username, "user1")
@ -387,7 +387,7 @@ func TestProjectUsers(t *testing.T) {
s := db.NewSession() s := db.NewSession()
defer s.Close() defer s.Close()
all, err := ListUsers(s, "uSEr1", nil) all, err := ProjectUsers(s, "uSEr1", nil)
assert.NoError(t, err) assert.NoError(t, err)
assert.True(t, len(all) > 0) assert.True(t, len(all) > 0)
assert.Equal(t, all[0].Username, "user1") assert.Equal(t, all[0].Username, "user1")
@ -406,7 +406,7 @@ func TestProjectUsers(t *testing.T) {
s := db.NewSession() s := db.NewSession()
defer s.Close() defer s.Close()
all, err := ListUsers(s, "", nil) all, err := ProjectUsers(s, "", nil)
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, all, 0) assert.Len(t, all, 0)
}) })
@ -415,7 +415,7 @@ func TestProjectUsers(t *testing.T) {
s := db.NewSession() s := db.NewSession()
defer s.Close() defer s.Close()
all, err := ListUsers(s, "user1@example.com", nil) all, err := ProjectUsers(s, "user1@example.com", nil)
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, all, 0) assert.Len(t, all, 0)
db.AssertExists(t, "users", map[string]interface{}{ db.AssertExists(t, "users", map[string]interface{}{
@ -428,7 +428,7 @@ func TestProjectUsers(t *testing.T) {
s := db.NewSession() s := db.NewSession()
defer s.Close() defer s.Close()
all, err := ListUsers(s, "one else", nil) all, err := ProjectUsers(s, "one else", nil)
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, all, 0) assert.Len(t, all, 0)
db.AssertExists(t, "users", map[string]interface{}{ db.AssertExists(t, "users", map[string]interface{}{
@ -441,7 +441,7 @@ func TestProjectUsers(t *testing.T) {
s := db.NewSession() s := db.NewSession()
defer s.Close() defer s.Close()
all, err := ListUsers(s, "user7@example.com", nil) all, err := ProjectUsers(s, "user7@example.com", nil)
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, all, 1) assert.Len(t, all, 1)
assert.Equal(t, int64(7), all[0].ID) assert.Equal(t, int64(7), all[0].ID)
@ -455,7 +455,7 @@ func TestProjectUsers(t *testing.T) {
s := db.NewSession() s := db.NewSession()
defer s.Close() defer s.Close()
all, err := ListUsers(s, "with space", nil) all, err := ProjectUsers(s, "with space", nil)
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, all, 1) assert.Len(t, all, 1)
assert.Equal(t, int64(12), all[0].ID) assert.Equal(t, int64(12), all[0].ID)
@ -483,7 +483,7 @@ func TestProjectUsers(t *testing.T) {
s := db.NewSession() s := db.NewSession()
defer s.Close() defer s.Close()
all, err := ListUsers(s, "user7", nil) all, err := ProjectUsers(s, "user7", nil)
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, all, 1) assert.Len(t, all, 1)
assert.Equal(t, int64(7), all[0].ID) assert.Equal(t, int64(7), all[0].ID)
@ -496,7 +496,7 @@ func TestProjectUsers(t *testing.T) {
s := db.NewSession() s := db.NewSession()
defer s.Close() defer s.Close()
all, err := ListUsers(s, "user", nil) all, err := ProjectUsers(s, "user", nil)
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, all, 0) assert.Len(t, all, 0)
db.AssertExists(t, "users", map[string]interface{}{ db.AssertExists(t, "users", map[string]interface{}{

View File

@ -32,8 +32,8 @@ type ProjectUserOpts struct {
MatchFuzzily bool MatchFuzzily bool
} }
// ListUsers returns a project with all users, filtered by an optional search string // ProjectUsers returns a project with all users, filtered by an optional search string
func ListUsers(s *xorm.Session, search string, opts *ProjectUserOpts) (users []*User, err error) { func ProjectUsers(s *xorm.Session, search string, opts *ProjectUserOpts) (users []*User, err error) {
if opts == nil { if opts == nil {
opts = &ProjectUserOpts{} opts = &ProjectUserOpts{}
} }