From 0a3f45ab11460da6c867aae1a92be67242a44950 Mon Sep 17 00:00:00 2001 From: kolaente Date: Fri, 15 Mar 2024 10:32:38 +0100 Subject: [PATCH] feat(views): decouple buckets from projects --- pkg/migration/20240315093418.go | 119 ++++++++++++++++++++++++++++++++ pkg/models/kanban.go | 16 +++-- pkg/models/kanban_rights.go | 8 +-- pkg/models/project.go | 14 +--- pkg/models/project_view.go | 15 +++- pkg/models/saved_filters.go | 2 +- 6 files changed, 148 insertions(+), 26 deletions(-) create mode 100644 pkg/migration/20240315093418.go diff --git a/pkg/migration/20240315093418.go b/pkg/migration/20240315093418.go new file mode 100644 index 000000000..0fa146075 --- /dev/null +++ b/pkg/migration/20240315093418.go @@ -0,0 +1,119 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public Licensee as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public Licensee for more details. +// +// You should have received a copy of the GNU Affero General Public Licensee +// along with this program. If not, see . + +package migration + +import ( + "code.vikunja.io/api/pkg/config" + "src.techknowlogick.com/xormigrate" + "xorm.io/xorm" +) + +type buckets20240315093418 struct { + ID int64 `xorm:"bigint autoincr not null unique pk"` + ProjectID int64 `xorm:"bigint not null"` + ProjectViewID int64 `xorm:"bigint not null default 0"` +} + +func (buckets20240315093418) TableName() string { + return "buckets" +} + +func init() { + migrations = append(migrations, &xormigrate.Migration{ + ID: "20240315093418", + Description: "Relate buckets to views instead of projects", + Migrate: func(tx *xorm.Engine) (err error) { + err = tx.Sync2(buckets20240315093418{}) + if err != nil { + return + } + + buckets := []*buckets20240315093418{} + err = tx.Find(&buckets) + if err != nil { + return err + } + + views := []*projectView20240313230538{} + err = tx.Find(&views) + if err != nil { + return err + } + + viewMap := make(map[int64][]*projectView20240313230538) + for _, view := range views { + if _, has := viewMap[view.ProjectID]; !has { + viewMap[view.ProjectID] = []*projectView20240313230538{} + } + + viewMap[view.ProjectID] = append(viewMap[view.ProjectID], view) + } + + for _, bucket := range buckets { + for _, view := range viewMap[bucket.ProjectID] { + if view.ViewKind == 3 { // Kanban view + + bucket.ProjectViewID = view.ID + + _, err = tx. + Where("id = ?", bucket.ID). + Cols("project_view_id"). + Update(bucket) + if err != nil { + return err + } + } + } + } + + if config.DatabaseType.GetString() == "sqlite" { + _, err = tx.Exec(` +create table buckets_dg_tmp +( + id INTEGER not null + primary key autoincrement, + title TEXT not null, + "limit" INTEGER default 0, + position REAL, + created DATETIME not null, + updated DATETIME not null, + created_by_id INTEGER not null, + project_view_id INTEGER not null default 0 +); + +insert into buckets_dg_tmp(id, title, "limit", position, created, updated, created_by_id, project_view_id) +select id, title, "limit", position, created, updated, created_by_id, project_view_id +from buckets; + +drop table buckets; + +alter table buckets_dg_tmp + rename to buckets; + +create unique index UQE_buckets_id + on buckets (id); +`) + return err + } + + return dropTableColum(tx, "buckets", "project_id") + }, + Rollback: func(tx *xorm.Engine) error { + return nil + }, + }) +} diff --git a/pkg/models/kanban.go b/pkg/models/kanban.go index 671d23fbc..35fe1b087 100644 --- a/pkg/models/kanban.go +++ b/pkg/models/kanban.go @@ -35,6 +35,8 @@ type Bucket struct { Title string `xorm:"text not null" valid:"required" minLength:"1" json:"title"` // The project this bucket belongs to. ProjectID int64 `xorm:"bigint not null" json:"project_id" param:"project"` + // The project view this bucket belongs to. + ProjectViewID int64 `xorm:"bigint not null" json:"project_view_id" param:"view"` // All tasks which belong to this bucket. Tasks []*Task `xorm:"-" json:"tasks"` @@ -167,13 +169,13 @@ func GetTasksInBucketsForView(s *xorm.Session, view *ProjectView, opts *taskSear if view.BucketConfigurationMode == BucketConfigurationModeFilter { for id, bc := range view.BucketConfiguration { buckets = append(buckets, &Bucket{ - ID: int64(id), - Title: bc.Title, - ProjectID: view.ProjectID, - Position: float64(id), - CreatedByID: auth.GetID(), - Created: time.Now(), - Updated: time.Now(), + ID: int64(id), + Title: bc.Title, + ProjectViewID: view.ID, + Position: float64(id), + CreatedByID: auth.GetID(), + Created: time.Now(), + Updated: time.Now(), }) } } diff --git a/pkg/models/kanban_rights.go b/pkg/models/kanban_rights.go index 53d18d4e4..07b096acc 100644 --- a/pkg/models/kanban_rights.go +++ b/pkg/models/kanban_rights.go @@ -23,8 +23,8 @@ import ( // CanCreate checks if a user can create a new bucket func (b *Bucket) CanCreate(s *xorm.Session, a web.Auth) (bool, error) { - l := &Project{ID: b.ProjectID} - return l.CanWrite(s, a) + pv := &ProjectView{ID: b.ProjectViewID} + return pv.CanUpdate(s, a) } // CanUpdate checks if a user can update an existing bucket @@ -43,6 +43,6 @@ func (b *Bucket) canDoBucket(s *xorm.Session, a web.Auth) (bool, error) { if err != nil { return false, err } - l := &Project{ID: bb.ProjectID} - return l.CanWrite(s, a) + pv := &ProjectView{ID: bb.ProjectViewID} + return pv.CanUpdate(s, a) } diff --git a/pkg/models/project.go b/pkg/models/project.go index 5e36e76bb..3cf13d600 100644 --- a/pkg/models/project.go +++ b/pkg/models/project.go @@ -777,19 +777,7 @@ func CreateProject(s *xorm.Session, project *Project, auth web.Auth, createBackl } } - if createBacklogBucket { - // Create a new first bucket for this project - b := &Bucket{ - ProjectID: project.ID, - Title: "Backlog", - } - err = b.Create(s, auth) - if err != nil { - return - } - } - - err = CreateDefaultViewsForProject(s, project, auth) + err = CreateDefaultViewsForProject(s, project, auth, createBacklogBucket) if err != nil { return } diff --git a/pkg/models/project_view.go b/pkg/models/project_view.go index ab926c42a..f26a8df76 100644 --- a/pkg/models/project_view.go +++ b/pkg/models/project_view.go @@ -295,7 +295,7 @@ func GetProjectViewByID(s *xorm.Session, id, projectID int64) (view *ProjectView return } -func CreateDefaultViewsForProject(s *xorm.Session, project *Project, a web.Auth) (err error) { +func CreateDefaultViewsForProject(s *xorm.Session, project *Project, a web.Auth, createBacklogBucket bool) (err error) { list := &ProjectView{ ProjectID: project.ID, Title: "List", @@ -336,5 +336,18 @@ func CreateDefaultViewsForProject(s *xorm.Session, project *Project, a web.Auth) Position: 400, } err = kanban.Create(s, a) + if err != nil { + return + } + + if createBacklogBucket { + // Create a new first bucket for this project + b := &Bucket{ + ProjectViewID: kanban.ID, + Title: "Backlog", + } + err = b.Create(s, a) + } + return } diff --git a/pkg/models/saved_filters.go b/pkg/models/saved_filters.go index 49fd613a5..7cc8c76af 100644 --- a/pkg/models/saved_filters.go +++ b/pkg/models/saved_filters.go @@ -123,7 +123,7 @@ func (sf *SavedFilter) Create(s *xorm.Session, auth web.Auth) (err error) { return } - err = CreateDefaultViewsForProject(s, &Project{ID: getProjectIDFromSavedFilterID(sf.ID)}, auth) + err = CreateDefaultViewsForProject(s, &Project{ID: getProjectIDFromSavedFilterID(sf.ID)}, auth, false) return err }