Compare commits

...

24 Commits

Author SHA1 Message Date
feacbbff74 fix(caldav): do not update dates of tasks when repositioning them (#1605)
When a task is updated, the position of the tasks of the whole project/bucket are updated. This leads to column "updated" of model Task to be updated quite often. However, that column is used for the ETag field of CALDAV.
Thus, changing a task marks all the other tasks as updated, which prevents clients from synchronizing their edited tasks.

Co-authored-by: Erwan Martin <erwan@pepper.com>
Reviewed-on: vikunja/api#1605
Co-authored-by: Erwan Martin <public@fzwte.net>
Co-committed-by: Erwan Martin <public@fzwte.net>
2023-09-27 16:17:52 +00:00
f065dcf4ad
fix(typesense): add typesense sync to initial structs 2023-09-15 12:53:29 +02:00
addcbdd8ca
fix(test): don't check for error 2023-09-13 12:52:42 +02:00
054f21821c
fix(typesense): don't try to index tasks if there are none 2023-09-13 12:19:09 +02:00
38a3a5c6e8
fix(typesense): explicitely create typesense sync table 2023-09-13 12:05:37 +02:00
1ee243f2bd
fix(project background): add more checks for whether a background file exists when duplicating or deleting a project
Related discussion: https://community.vikunja.io/t/500-internal-server-error-when-selecting-unsplash-background-image/778/18
2023-09-13 11:20:59 +02:00
191c154150
chore(build): use our own goproxy to prevent issues with packages not found 2023-09-12 13:34:35 +02:00
378759e06d
fix(build): don't remove swagger files when running build:clean step 2023-09-12 13:12:30 +02:00
c5c74e9537
chore(caldav): improve trimming .ics file ending 2023-09-07 15:52:37 +02:00
e34f503674
fix: lint 2023-09-07 11:31:35 +02:00
bfcefa0217
fix(caldav): check for related tasks synced back from a caldav client
Related discussion https://community.vikunja.io/t/tasks-org-subtask-syncing-issue/737/9
2023-09-07 11:29:44 +02:00
c6bdb5752a
fix(import): create related tasks without an id 2023-09-07 11:16:04 +02:00
68d4dcd7e6
fix(projects): don't limit results to top-level projects when searching
Resolves https://github.com/go-vikunja/api/issues/82
2023-09-07 10:56:59 +02:00
b2f3a23cb3
fix(import): correctly set child project relations 2023-09-07 10:45:15 +02:00
93795d2f29
fix(import): resolve task relations by old task ids 2023-09-07 10:24:15 +02:00
adf4b95ed3
fix(import): ignore duplicate project identifier 2023-09-07 10:12:15 +02:00
ce3a06f03b
fix(import): don't fail when importing from dev exports 2023-09-07 10:11:59 +02:00
2c0c3ea24e
fix(build): don't require swagger to build 2023-09-06 21:08:09 +02:00
2d9cf672b8
fix(ci): don't generate swagger docs in ci 2023-09-06 20:52:19 +02:00
ae766f52c7
fix(build): don't generate swagger files when building 2023-09-06 18:36:08 +02:00
107b0b791f
fix(swagger): add generated swagger docs to repo 2023-09-06 18:01:45 +02:00
985233ac38
fix(build): don't run go mod commands when generating swagger docs 2023-09-06 16:41:39 +02:00
424bf7647b
fix: lint 2023-09-06 14:56:25 +02:00
06bc92556e
fix(docs): add empty swagger file so that the package exists 2023-09-06 13:13:51 +02:00
23 changed files with 23541 additions and 229 deletions

View File

@ -121,23 +121,12 @@ steps:
when:
event: [ push, tag, pull_request ]
- name: prepare-build
image: vikunja/golang-build:latest
pull: always
environment:
GOPROXY: 'https://goproxy.kolaente.de'
depends_on: [ mage ]
commands:
- ./mage-static do-the-swag
when:
event: [ push, tag, pull_request ]
- name: build
image: vikunja/golang-build:latest
pull: always
environment:
GOPROXY: 'https://goproxy.kolaente.de'
depends_on: [ prepare-build ]
depends_on: [ mage ]
commands:
- ./mage-static build:build
when:
@ -230,7 +219,7 @@ steps:
GOPROXY: 'https://goproxy.kolaente.de'
commands:
- ./mage-static test:unit
depends_on: [ fetch-tags, prepare-build ]
depends_on: [ fetch-tags, mage ]
when:
event: [ push, tag, pull_request ]
@ -247,7 +236,7 @@ steps:
path: /db
commands:
- ./mage-static test:unit
depends_on: [ fetch-tags, prepare-build ]
depends_on: [ fetch-tags, mage ]
when:
event: [ push, tag, pull_request ]
@ -264,7 +253,7 @@ steps:
VIKUNJA_DATABASE_DATABASE: vikunjatest
commands:
- ./mage-static test:unit
depends_on: [ fetch-tags, prepare-build ]
depends_on: [ fetch-tags, mage ]
when:
event: [ push, tag, pull_request ]
@ -282,7 +271,7 @@ steps:
VIKUNJA_DATABASE_SSLMODE: disable
commands:
- ./mage-static test:unit
depends_on: [ fetch-tags, prepare-build ]
depends_on: [ fetch-tags, mage ]
when:
event: [ push, tag, pull_request ]
@ -293,7 +282,7 @@ steps:
GOPROXY: 'https://goproxy.kolaente.de'
commands:
- ./mage-static test:integration
depends_on: [ fetch-tags, prepare-build ]
depends_on: [ fetch-tags, mage ]
when:
event: [ push, tag, pull_request ]
@ -310,7 +299,7 @@ steps:
path: /db
commands:
- ./mage-static test:integration
depends_on: [ fetch-tags, prepare-build ]
depends_on: [ fetch-tags, mage ]
when:
event: [ push, tag, pull_request ]
@ -327,7 +316,7 @@ steps:
VIKUNJA_DATABASE_DATABASE: vikunjatest
commands:
- ./mage-static test:integration
depends_on: [ fetch-tags, prepare-build ]
depends_on: [ fetch-tags, mage ]
when:
event: [ push, tag, pull_request ]
@ -345,10 +334,54 @@ steps:
VIKUNJA_DATABASE_SSLMODE: disable
commands:
- ./mage-static test:integration
depends_on: [ fetch-tags, prepare-build ]
depends_on: [ fetch-tags, mage ]
when:
event: [ push, tag, pull_request ]
---
kind: pipeline
type: docker
name: generate-swagger-docs
depends_on:
- testing
workspace:
base: /go
path: src/code.vikunja.io/api
trigger:
branch:
include:
- main
event:
include:
- push
steps:
- name: generate-swagger-docs
image: vikunja/golang-build:latest
pull: always
environment:
GOPROXY: 'https://goproxy.kolaente.de'
commands:
- mage do-the-swag
- name: push
pull: always
image: appleboy/drone-git-push
depends_on:
- generate-swagger-docs
settings:
author_email: "frederik@vikunja.io"
author_name: Frederick [Bot]
branch: main
commit: true
commit_message: "[skip ci] Updated swagger docs"
remote: "ssh://git@kolaente.dev:9022/vikunja/api.git"
ssh_key:
from_secret: git_push_ssh_key
---
########
# Build a release when tagging
@ -396,7 +429,6 @@ steps:
- export PATH=$PATH:$GOPATH/bin
- go install github.com/magefile/mage
- ./mage-static release:dirs
- ./mage-static do-the-swag
depends_on: [ fetch-tags, mage ]
- name: static-build-windows
@ -406,6 +438,7 @@ steps:
# This path does not exist. However, when we set the gopath to /go, the build fails. Not sure why.
# Leaving this here until we know how to resolve this properly.
GOPATH: /srv/app
GOPROXY: https://goproxy.kolaente.de
commands:
- export PATH=$PATH:$GOPATH/bin
- go install github.com/magefile/mage
@ -419,6 +452,7 @@ steps:
# This path does not exist. However, when we set the gopath to /go, the build fails. Not sure why.
# Leaving this here until we know how to resolve this properly.
GOPATH: /srv/app
GOPROXY: https://goproxy.kolaente.de
commands:
- export PATH=$PATH:$GOPATH/bin
- go install github.com/magefile/mage
@ -432,6 +466,7 @@ steps:
# This path does not exist. However, when we set the gopath to /go, the build fails. Not sure why.
# Leaving this here until we know how to resolve this properly.
GOPATH: /srv/app
GOPROXY: https://goproxy.kolaente.de
commands:
- export PATH=$PATH:$GOPATH/bin
- go install github.com/magefile/mage
@ -743,6 +778,6 @@ steps:
- failure
---
kind: signature
hmac: d47bd1cf6f3e9be2ff3eed2039e65c8b6de2b16c1e636699f66382f941277411
hmac: 6bc74f5b7e9c51e725100e05f07cdac656d6c3d49d19c2b112aed812c86e7a9a
...

1
.gitignore vendored
View File

@ -27,4 +27,3 @@ vikunja-dump*
vendor/
os-packages/
mage_output_file.go
pkg/swagger/*

View File

@ -13,6 +13,7 @@ COPY . ./
ARG TARGETOS TARGETARCH TARGETVARIANT
ENV GOPROXY https://goproxy.kolaente.de
RUN export PATH=$PATH:$GOPATH/bin && \
mage build:clean && \
mage release:xgo "${TARGETOS}/${TARGETARCH}/${TARGETVARIANT}"

View File

@ -177,9 +177,6 @@ func init() {
// Some variables have external dependencies (like git) which may not always be available.
func initVars() {
Tags = os.Getenv("TAGS")
if !strings.Contains(Tags, "swagger") {
Tags += " swagger"
}
setVersion()
setBinLocation()
setPkgVersion()
@ -349,10 +346,6 @@ const swaggerDocsFolderLocation = `./pkg/swagger/`
// Generates the swagger docs from the code annotations
func DoTheSwag() {
mg.Deps(initVars)
if _, err := os.Stat(swaggerDocsFolderLocation + "swagger.json"); err == nil {
fmt.Println("Swagger docs already generated, not generating. Remove the files in " + swaggerDocsFolderLocation + " and run this command again to regenerate them.")
return
}
checkAndInstallGoTool("swag", "github.com/swaggo/swag/cmd/swag")
runAndStreamOutput("swag", "init", "-g", "./pkg/routes/routes.go", "--parseDependency", "-d", RootPath, "-o", RootPath+"/pkg/swagger")
@ -460,16 +453,12 @@ func (Build) Clean() error {
if err := os.RemoveAll(BinLocation); err != nil && !os.IsNotExist(err) {
return err
}
if err := os.RemoveAll(swaggerDocsFolderLocation); err != nil && !os.IsNotExist(err) {
return err
}
return nil
}
// Builds a vikunja binary, ready to run
func (Build) Build() {
mg.Deps(initVars)
mg.Deps(DoTheSwag)
runAndStreamOutput("go", "build", Goflags[0], "-tags", Tags, "-ldflags", "-s -w "+Ldflags, "-o", Executable)
}
@ -479,7 +468,6 @@ type Release mg.Namespace
func (Release) Release(ctx context.Context) error {
mg.Deps(initVars)
mg.Deps(Release.Dirs)
mg.Deps(DoTheSwag)
// Run compiling in parallel to speed it up
errs, _ := errgroup.WithContext(ctx)
@ -521,7 +509,6 @@ func (Release) Dirs() error {
func runXgo(targets string) error {
mg.Deps(initVars)
mg.Deps(DoTheSwag)
checkAndInstallGoTool("xgo", "src.techknowlogick.com/xgo")
extraLdflags := `-linkmode external -extldflags "-static" `

View File

@ -23,6 +23,7 @@ import (
"time"
"code.vikunja.io/api/pkg/config"
"code.vikunja.io/api/pkg/db"
"code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/utils"
@ -90,8 +91,12 @@ func ParseTaskFromVTODO(content string) (vTask *models.Task, err error) {
}
// We put the vTodo details in a map to be able to handle them more easily
task := make(map[string]ics.IANAProperty)
var relation ics.IANAProperty
for _, c := range vTodo.UnknownPropertiesIANAProperties() {
task[c.IANAToken] = c
if strings.HasPrefix(c.IANAToken, "RELATED-TO") {
relation = c
}
}
// Parse the priority
@ -134,6 +139,19 @@ func ParseTaskFromVTODO(content string) (vTask *models.Task, err error) {
DoneAt: caldavTimeToTimestamp(task["COMPLETED"]),
}
if relation.Value != "" {
s := db.NewSession()
defer s.Close()
subtask, err := models.GetTaskSimpleByUUID(s, relation.Value)
if err != nil {
return nil, err
}
vTask.RelatedTasks = make(map[models.RelationKind][]*models.Task)
vTask.RelatedTasks[models.RelationKindSubtask] = []*models.Task{subtask}
}
if task["STATUS"].Value == "COMPLETED" {
vTask.Done = true
}

View File

@ -38,7 +38,7 @@ func init() {
ID: "20230828125443",
Description: "",
Migrate: func(tx *xorm.Engine) error {
return tx.Sync2(typesenseSync20230828125443{})
return tx.CreateTables(typesenseSync20230828125443{})
},
Rollback: func(tx *xorm.Engine) error {
return nil

View File

@ -59,6 +59,7 @@ func GetTables() []interface{} {
&Subscription{},
&Favorite{},
&APIToken{},
&TypesenseSync{},
}
}

View File

@ -368,18 +368,20 @@ func getUserProjectsStatement(parentProjectIDs []int64, userID int64, search str
}
var parentCondition builder.Cond
parentCondition = builder.Or(
builder.IsNull{"l.parent_project_id"},
builder.Eq{"l.parent_project_id": 0},
// else check for shared sub projects with a parent
builder.And(
builder.Or(
builder.NotNull{"tm2.user_id"},
builder.NotNull{"ul.user_id"},
if search == "" {
parentCondition = builder.Or(
builder.IsNull{"l.parent_project_id"},
builder.Eq{"l.parent_project_id": 0},
// else check for shared sub projects with a parent
builder.And(
builder.Or(
builder.NotNull{"tm2.user_id"},
builder.NotNull{"ul.user_id"},
),
builder.NotNull{"l.parent_project_id"},
),
builder.NotNull{"l.parent_project_id"},
),
)
)
}
projectCol := "id"
if len(parentProjectIDs) > 0 {
parentCondition = builder.In("l.parent_project_id", parentProjectIDs)
@ -1028,7 +1030,12 @@ func (p *Project) DeleteBackgroundFileIfExists() (err error) {
}
file := files.File{ID: p.BackgroundFileID}
return file.Delete()
err = file.Delete()
if err != nil && files.IsErrFileDoesNotExist(err) {
return nil
}
return err
}
// SetProjectBackground sets a background file as project background in the db

View File

@ -118,43 +118,9 @@ func (pd *ProjectDuplicate) Create(s *xorm.Session, doer web.Auth) (err error) {
return
}
// Background files + unsplash info
if pd.Project.BackgroundFileID != 0 {
log.Debugf("Duplicating background %d from project %d into %d", pd.Project.BackgroundFileID, pd.ProjectID, pd.Project.ID)
f := &files.File{ID: pd.Project.BackgroundFileID}
if err := f.LoadFileMetaByID(); err != nil {
return err
}
if err := f.LoadFileByID(); err != nil {
return err
}
defer f.File.Close()
file, err := files.Create(f.File, f.Name, f.Size, doer)
if err != nil {
return err
}
// Get unsplash info if applicable
up, err := GetUnsplashPhotoByFileID(s, pd.Project.BackgroundFileID)
if err != nil && files.IsErrFileIsNotUnsplashFile(err) {
return err
}
if up != nil {
up.ID = 0
up.FileID = file.ID
if err := up.Save(s); err != nil {
return err
}
}
if err := SetProjectBackground(s, pd.Project.ID, file, pd.Project.BackgroundBlurHash); err != nil {
return err
}
log.Debugf("Duplicated project background from project %d into %d", pd.ProjectID, pd.Project.ID)
err = duplicateProjectBackground(s, pd, doer)
if err != nil {
return
}
// Rights / Shares
@ -207,6 +173,54 @@ func (pd *ProjectDuplicate) Create(s *xorm.Session, doer web.Auth) (err error) {
return
}
func duplicateProjectBackground(s *xorm.Session, pd *ProjectDuplicate, doer web.Auth) (err error) {
if pd.Project.BackgroundFileID == 0 {
return
}
log.Debugf("Duplicating background %d from project %d into %d", pd.Project.BackgroundFileID, pd.ProjectID, pd.Project.ID)
f := &files.File{ID: pd.Project.BackgroundFileID}
err = f.LoadFileMetaByID()
if err != nil && files.IsErrFileDoesNotExist(err) {
pd.Project.BackgroundFileID = 0
return nil
}
if err != nil {
return err
}
if err := f.LoadFileByID(); err != nil {
return err
}
defer f.File.Close()
file, err := files.Create(f.File, f.Name, f.Size, doer)
if err != nil {
return err
}
// Get unsplash info if applicable
up, err := GetUnsplashPhotoByFileID(s, pd.Project.BackgroundFileID)
if err != nil && !files.IsErrFileIsNotUnsplashFile(err) {
return err
}
if up != nil {
up.ID = 0
up.FileID = file.ID
if err := up.Save(s); err != nil {
return err
}
}
if err := SetProjectBackground(s, pd.Project.ID, file, pd.Project.BackgroundBlurHash); err != nil {
return err
}
log.Debugf("Duplicated project background from project %d into %d", pd.ProjectID, pd.Project.ID)
return
}
func duplicateTasks(s *xorm.Session, doer web.Auth, ld *ProjectDuplicate, bucketMap map[int64]int64) (err error) {
// Get all tasks + all task details
tasks, _, _, err := getTasksForProjects(s, []*Project{{ID: ld.ProjectID}}, doer, &taskSearchOptions{})

View File

@ -329,8 +329,7 @@ func TestProject_DeleteBackgroundFileIfExists(t *testing.T) {
err := SetProjectBackground(s, project.ID, file, "")
assert.NoError(t, err)
err = project.DeleteBackgroundFileIfExists()
assert.Error(t, err)
assert.True(t, files.IsErrFileDoesNotExist(err))
assert.NoError(t, err)
})
t.Run("project without background", func(t *testing.T) {
db.LoadAndAssertFixtures(t)

View File

@ -371,6 +371,11 @@ func (bt *BulkTask) GetTasksByIDs(s *xorm.Session) (err error) {
return
}
func GetTaskSimpleByUUID(s *xorm.Session, uid string) (task *Task, err error) {
_, err = s.In("uid", uid).Get(task)
return
}
// GetTasksByUIDs gets all tasks from a bunch of uids
func GetTasksByUIDs(s *xorm.Session, uids []string, a web.Auth) (tasks []*Task, err error) {
tasks = []*Task{}
@ -1076,8 +1081,13 @@ func recalculateTaskKanbanPositions(s *xorm.Session, bucketID int64) (err error)
currentPosition := maxPosition / float64(len(allTasks)) * (float64(i + 1))
// Here we use "NoAutoTime() to prevent the ORM from updating column "updated" automatically.
// Otherwise, this signals to CalDAV clients that the task has changed, which is not the case.
// Consequence: when synchronizing a list of tasks, the first one immediately changes the date of all the
// following ones from the same batch, which are then unable to be updated.
_, err = s.Cols("kanban_position").
Where("id = ?", task.ID).
NoAutoTime().
Update(&Task{KanbanPosition: currentPosition})
if err != nil {
return
@ -1104,8 +1114,13 @@ func recalculateTaskPositions(s *xorm.Session, projectID int64) (err error) {
currentPosition := maxPosition / float64(len(allTasks)) * (float64(i + 1))
// Here we use "NoAutoTime() to prevent the ORM from updating column "updated" automatically.
// Otherwise, this signals to CalDAV clients that the task has changed, which is not the case.
// Consequence: when synchronizing a list of tasks, the first one immediately changes the date of all the
// following ones from the same batch, which are then unable to be updated.
_, err = s.Cols("position").
Where("id = ?", task.ID).
NoAutoTime().
Update(&Task{Position: currentPosition})
if err != nil {
return

View File

@ -243,6 +243,12 @@ func ReindexAllTasks() (err error) {
}
func reindexTasks(s *xorm.Session, tasks map[int64]*Task) (err error) {
if len(tasks) == 0 {
log.Infof("No tasks to index")
return
}
err = addMoreInfoToTasks(s, tasks, &user.User{ID: 1})
if err != nil {
return fmt.Errorf("could not fetch more task info: %s", err.Error())

View File

@ -291,11 +291,13 @@ func (p *Provider) Set(s *xorm.Session, image *background.Image, project *models
// Remove the old background if one exists
if project.BackgroundFileID != 0 {
file := files.File{ID: project.BackgroundFileID}
if err := file.Delete(); err != nil {
err = file.Delete()
if err != nil && !files.IsErrFileDoesNotExist(err) {
return err
}
if err := models.RemoveUnsplashPhoto(s, project.BackgroundFileID); err != nil {
err = models.RemoveUnsplashPhoto(s, project.BackgroundFileID)
if err != nil && !files.IsErrFileDoesNotExist(err) {
return err
}
}

View File

@ -56,7 +56,8 @@ func (p *Provider) Set(s *xorm.Session, img *background.Image, project *models.P
// Remove the old background if one exists
if project.BackgroundFileID != 0 {
file := files.File{ID: project.BackgroundFileID}
if err := file.Delete(); err != nil {
err := file.Delete()
if err != nil && !files.IsErrFileDoesNotExist(err) {
return err
}
}

View File

@ -52,13 +52,44 @@ func insertFromStructure(s *xorm.Session, str []*models.ProjectWithTasksAndBucke
labels := make(map[string]*models.Label)
archivedProjects := []int64{}
childRelations := make(map[int64][]int64) // old id is the key, slice of old children ids
projectsByOldID := make(map[int64]*models.Project) // old id is the key
// Create all projects
for _, p := range str {
oldID := p.ID
if p.ParentProjectID != 0 {
childRelations[p.ParentProjectID] = append(childRelations[p.ParentProjectID], oldID)
}
p.ID = 0
err = createProjectWithChildren(s, p, 0, &archivedProjects, labels, user)
err = createProject(s, p, &archivedProjects, labels, user)
if err != nil {
return err
}
projectsByOldID[oldID] = &p.Project
}
// parent / child relations
for parentID, children := range childRelations {
parent, has := projectsByOldID[parentID]
if !has {
log.Debugf("[creating structure] could not find parentID project with old id %d", parentID)
continue
}
for _, childID := range children {
child, has := projectsByOldID[childID]
if !has {
log.Debugf("[creating structure] could not find child project with old id %d for parent project with old id %d", childID, parentID)
continue
}
child.ParentProjectID = parent.ID
err = child.Update(s, user)
if err != nil {
return err
}
}
}
if len(archivedProjects) > 0 {
@ -76,30 +107,18 @@ func insertFromStructure(s *xorm.Session, str []*models.ProjectWithTasksAndBucke
return nil
}
func createProjectWithChildren(s *xorm.Session, project *models.ProjectWithTasksAndBuckets, parentProjectID int64, archivedProjectIDs *[]int64, labels map[string]*models.Label, user *user.User) (err error) {
err = createProjectWithEverything(s, project, parentProjectID, archivedProjectIDs, labels, user)
func createProject(s *xorm.Session, project *models.ProjectWithTasksAndBuckets, archivedProjectIDs *[]int64, labels map[string]*models.Label, user *user.User) (err error) {
err = createProjectWithEverything(s, project, archivedProjectIDs, labels, user)
if err != nil {
return err
}
log.Debugf("[creating structure] Created project %d", project.ID)
if len(project.ChildProjects) > 0 {
log.Debugf("[creating structure] Creating %d projects", len(project.ChildProjects))
// Create all projects
for _, cp := range project.ChildProjects {
err = createProjectWithChildren(s, cp, project.ID, archivedProjectIDs, labels, user)
if err != nil {
return err
}
}
}
return
}
func createProjectWithEverything(s *xorm.Session, project *models.ProjectWithTasksAndBuckets, parentProjectID int64, archivedProjects *[]int64, labels map[string]*models.Label, user *user.User) (err error) {
func createProjectWithEverything(s *xorm.Session, project *models.ProjectWithTasksAndBuckets, archivedProjects *[]int64, labels map[string]*models.Label, user *user.User) (err error) {
// 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 project was created.
tasks := project.Tasks
@ -114,9 +133,12 @@ func createProjectWithEverything(s *xorm.Session, project *models.ProjectWithTas
project.IsArchived = false
}
project.ParentProjectID = parentProjectID
project.ID = 0
err = project.Create(s, user)
if err != nil && models.IsErrProjectIdentifierIsNotUnique(err) {
project.Identifier = ""
err = project.Create(s, user)
}
if err != nil {
return
}
@ -174,15 +196,18 @@ func createProjectWithEverything(s *xorm.Session, project *models.ProjectWithTas
}
}
tasksByOldID := make(map[int64]*models.TaskWithComments, len(tasks))
// Create all tasks
for _, t := range tasks {
setBucketOrDefault(&t.Task)
oldid := t.ID
t.ProjectID = project.ID
err = t.Create(s, user)
if err != nil {
return
}
tasksByOldID[oldid] = t
log.Debugf("[creating structure] Created task %d", t.ID)
if len(t.RelatedTasks) > 0 {
@ -198,13 +223,15 @@ func createProjectWithEverything(s *xorm.Session, project *models.ProjectWithTas
for _, rt := range tasks {
// First create the related tasks if they do not exist
if rt.ID == 0 {
if _, exists := tasksByOldID[rt.ID]; !exists || rt.ID == 0 {
oldid := rt.ID
setBucketOrDefault(rt)
rt.ProjectID = t.ProjectID
err = rt.Create(s, user)
if err != nil {
return
}
tasksByOldID[oldid] = &models.TaskWithComments{Task: *rt}
log.Debugf("[creating structure] Created related task %d", rt.ID)
}
@ -214,8 +241,11 @@ func createProjectWithEverything(s *xorm.Session, project *models.ProjectWithTas
OtherTaskID: rt.ID,
RelationKind: kind,
}
if ttt, exists := tasksByOldID[rt.ID]; exists {
taskRel.OtherTaskID = ttt.ID
}
err = taskRel.Create(s, user)
if err != nil {
if err != nil && !models.IsErrRelationAlreadyExists(err) {
return
}

View File

@ -35,6 +35,7 @@ func TestInsertFromStructure(t *testing.T) {
testStructure := []*models.ProjectWithTasksAndBuckets{
{
Project: models.Project{
ID: 1,
Title: "Test1",
Description: "Lorem Ipsum",
},
@ -45,113 +46,112 @@ func TestInsertFromStructure(t *testing.T) {
},
},
},
ChildProjects: []*models.ProjectWithTasksAndBuckets{
},
{
Project: models.Project{
Title: "Testproject1",
Description: "Something",
ParentProjectID: 1,
},
Buckets: []*models.Bucket{
{
Project: models.Project{
Title: "Testproject1",
Description: "Something",
ID: 1234,
Title: "Test Bucket",
},
},
Tasks: []*models.TaskWithComments{
{
Task: models.Task{
Title: "Task1",
Description: "Lorem",
},
Buckets: []*models.Bucket{
{
ID: 1234,
Title: "Test Bucket",
},
{
Task: models.Task{
Title: "Task with related tasks",
RelatedTasks: map[models.RelationKind][]*models.Task{
models.RelationKindSubtask: {
{
Title: "Related to task with related task",
Description: "As subtask",
},
},
},
},
Tasks: []*models.TaskWithComments{
{
Task: models.Task{
Title: "Task1",
Description: "Lorem",
},
},
{
Task: models.Task{
Title: "Task with related tasks",
RelatedTasks: map[models.RelationKind][]*models.Task{
models.RelationKindSubtask: {
{
Title: "Related to task with related task",
Description: "As subtask",
},
},
},
{
Task: models.Task{
Title: "Task with attachments",
Attachments: []*models.TaskAttachment{
{
File: &files.File{
Name: "testfile",
Size: 4,
FileContent: []byte{1, 2, 3, 4},
},
},
},
{
Task: models.Task{
Title: "Task with attachments",
Attachments: []*models.TaskAttachment{
{
File: &files.File{
Name: "testfile",
Size: 4,
FileContent: []byte{1, 2, 3, 4},
},
},
},
},
},
{
Task: models.Task{
Title: "Task with labels",
Labels: []*models.Label{
{
Title: "Label1",
HexColor: "ff00ff",
},
{
Title: "Label2",
HexColor: "ff00ff",
},
},
{
Task: models.Task{
Title: "Task with labels",
Labels: []*models.Label{
{
Title: "Label1",
HexColor: "ff00ff",
},
{
Title: "Label2",
HexColor: "ff00ff",
},
},
},
},
{
Task: models.Task{
Title: "Task with same label",
Labels: []*models.Label{
{
Title: "Label1",
HexColor: "ff00ff",
},
},
},
},
{
Task: models.Task{
Title: "Task in a bucket",
BucketID: 1234,
},
},
{
Task: models.Task{
Title: "Task in a nonexisting bucket",
BucketID: 1111,
},
},
{
Task: models.Task{
Title: "Task with same label",
Labels: []*models.Label{
{
Title: "Label1",
HexColor: "ff00ff",
},
},
},
},
{
Task: models.Task{
Title: "Task in a bucket",
BucketID: 1234,
},
},
{
Task: models.Task{
Title: "Task in a nonexisting bucket",
BucketID: 1111,
},
},
},
},
}
err := InsertFromStructure(testStructure, u)
assert.NoError(t, err)
db.AssertExists(t, "projects", map[string]interface{}{
"title": testStructure[0].ChildProjects[0].Title,
"description": testStructure[0].ChildProjects[0].Description,
"title": testStructure[1].Title,
"description": testStructure[1].Description,
}, false)
db.AssertExists(t, "tasks", map[string]interface{}{
"title": testStructure[0].ChildProjects[0].Tasks[5].Title,
"bucket_id": testStructure[0].ChildProjects[0].Buckets[0].ID,
"title": testStructure[1].Tasks[5].Title,
"bucket_id": testStructure[1].Buckets[0].ID,
}, false)
db.AssertMissing(t, "tasks", map[string]interface{}{
"title": testStructure[0].ChildProjects[0].Tasks[6].Title,
"title": testStructure[1].Tasks[6].Title,
"bucket_id": 1111, // No task with that bucket should exist
})
db.AssertExists(t, "tasks", map[string]interface{}{
"title": testStructure[0].Tasks[0].Title,
}, false)
assert.NotEqual(t, 0, testStructure[0].ChildProjects[0].Tasks[0].BucketID) // Should get the default bucket
assert.NotEqual(t, 0, testStructure[0].ChildProjects[0].Tasks[6].BucketID) // Should get the default bucket
assert.NotEqual(t, 0, testStructure[1].Tasks[0].BucketID) // Should get the default bucket
assert.NotEqual(t, 0, testStructure[1].Tasks[6].BucketID) // Should get the default bucket
})
}

View File

@ -30,6 +30,7 @@ import (
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/modules/migration"
"code.vikunja.io/api/pkg/user"
vversion "code.vikunja.io/api/pkg/version"
"github.com/hashicorp/go-version"
)
@ -119,17 +120,22 @@ func (v *FileMigrator) Migrate(user *user.User, file io.ReaderAt, size int64) er
return fmt.Errorf("could not read version file: %w", err)
}
dumpedVersion, err := version.NewVersion(bufVersion.String())
if err != nil {
return err
}
minVersion, err := version.NewVersion("0.20.1+61")
if err != nil {
return err
}
versionString := bufVersion.String()
if versionString == "dev" && vversion.Version == "dev" {
log.Debugf(logPrefix + "Importing from dev version")
} else {
dumpedVersion, err := version.NewVersion(bufVersion.String())
if err != nil {
return err
}
minVersion, err := version.NewVersion("0.20.1+61")
if err != nil {
return err
}
if dumpedVersion.LessThan(minVersion) {
return fmt.Errorf("export was created with an older version, need at least %s but the export needs at least %s", dumpedVersion, minVersion)
if dumpedVersion.LessThan(minVersion) {
return fmt.Errorf("export was created with an older version, need at least %s but the export needs at least %s", dumpedVersion, minVersion)
}
}
//////

View File

@ -133,9 +133,7 @@ func (vcls *VikunjaCaldavProjectStorage) GetResourcesByList(rpaths []string) ([]
var uids []string
for _, path := range rpaths {
parts := strings.Split(path, "/")
uid := []rune(parts[4]) // The 4th part is the id with ".ics" suffix
endlen := len(uid) - len(".ics") // ".ics" are 4 bytes
uids = append(uids, string(uid[:endlen]))
uids = append(uids, strings.TrimSuffix(parts[4], ".ics"))
}
s := db.NewSession()

8663
pkg/swagger/docs.go Normal file

File diff suppressed because it is too large Load Diff

8638
pkg/swagger/swagger.json Normal file

File diff suppressed because it is too large Load Diff

5911
pkg/swagger/swagger.yaml Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,26 +0,0 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present2023 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public Licensee for more details.
//
// You should have received a copy of the GNU Affero General Public Licensee
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//go:build swagger
// +build swagger
package version
import "code.vikunja.io/api/pkg/swagger"
func init() {
// Additional swagger information
swagger.SwaggerInfo.Version = Version
}

View File

@ -16,8 +16,15 @@
package version
import "code.vikunja.io/api/pkg/swagger"
// This package holds the version info
// It is an own package to avoid import cycles
// Version sets the version to be printed to the user. Gets overwritten by "make release" or "make build" with last git commit or tag.
var Version = "dev"
func init() {
// Additional swagger information
swagger.SwaggerInfo.Version = Version
}