mirror of
https://github.com/go-vikunja/vikunja.git
synced 2025-12-05 19:16:51 -06:00
Compare commits
5 Commits
cec8daba59
...
112df4a752
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
112df4a752 | ||
|
|
da0822c3f4 | ||
|
|
30104fb749 | ||
|
|
7cf2a6886e | ||
|
|
5bb53eaefa |
4
.github/workflows/issue-closed-comment.yml
vendored
4
.github/workflows/issue-closed-comment.yml
vendored
@@ -53,7 +53,9 @@ jobs:
|
||||
|
||||
core.setOutput('closed_by_commit', 'true');
|
||||
core.setOutput('commit_sha', commitId);
|
||||
core.setOutput('commit_message', commit.message);
|
||||
// Escape backslashes, backticks and ${ to prevent breaking JS template strings
|
||||
const escapedMessage = commit.message.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\$\{/g, '\\${');
|
||||
core.setOutput('commit_message', escapedMessage);
|
||||
core.setOutput('commit_url', closedEvent.commit_url);
|
||||
} else {
|
||||
console.log(`ℹ️ Issue #${issueNumber} was closed manually (not by commit)`);
|
||||
|
||||
2
go.mod
2
go.mod
@@ -62,7 +62,7 @@ require (
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/samedi/caldav-go v3.0.0+incompatible
|
||||
github.com/spf13/afero v1.15.0
|
||||
github.com/spf13/cobra v1.10.1
|
||||
github.com/spf13/cobra v1.10.2
|
||||
github.com/spf13/viper v1.21.0
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/swaggo/swag v1.16.6
|
||||
|
||||
12
go.sum
12
go.sum
@@ -57,14 +57,10 @@ github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL
|
||||
github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY=
|
||||
github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic=
|
||||
github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/clipperhouse/displaywidth v0.3.1 h1:k07iN9gD32177o1y4O1jQMzbLdCrsGJh+blirVYybsk=
|
||||
github.com/clipperhouse/displaywidth v0.3.1/go.mod h1:tgLJKKyaDOCadywag3agw4snxS5kYEuYR6Y9+qWDDYM=
|
||||
github.com/clipperhouse/displaywidth v0.6.0 h1:k32vueaksef9WIKCNcoqRNyKbyvkvkysNYnAWz2fN4s=
|
||||
github.com/clipperhouse/displaywidth v0.6.0/go.mod h1:R+kHuzaYWFkTm7xoMmK1lFydbci4X2CicfbGstSGg0o=
|
||||
github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=
|
||||
github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
|
||||
github.com/clipperhouse/uax29/v2 v2.2.0 h1:ChwIKnQN3kcZteTXMgb1wztSgaU+ZemkgWdohwgs8tY=
|
||||
github.com/clipperhouse/uax29/v2 v2.2.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
|
||||
github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4=
|
||||
github.com/clipperhouse/uax29/v2 v2.3.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
|
||||
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
|
||||
@@ -364,12 +360,8 @@ github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 h1:zrbMGy9YXpIeTnGj
|
||||
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6/go.mod h1:rEKTHC9roVVicUIfZK7DYrdIoM0EOr8mK1Hj5s3JjH0=
|
||||
github.com/olekukonko/errors v1.1.0 h1:RNuGIh15QdDenh+hNvKrJkmxxjV4hcS50Db478Ou5sM=
|
||||
github.com/olekukonko/errors v1.1.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y=
|
||||
github.com/olekukonko/ll v0.1.2 h1:lkg/k/9mlsy0SxO5aC+WEpbdT5K83ddnNhAepz7TQc0=
|
||||
github.com/olekukonko/ll v0.1.2/go.mod h1:b52bVQRRPObe+yyBl0TxNfhesL0nedD4Cht0/zx55Ew=
|
||||
github.com/olekukonko/ll v0.1.3 h1:sV2jrhQGq5B3W0nENUISCR6azIPf7UBUpVq0x/y70Fg=
|
||||
github.com/olekukonko/ll v0.1.3/go.mod h1:b52bVQRRPObe+yyBl0TxNfhesL0nedD4Cht0/zx55Ew=
|
||||
github.com/olekukonko/tablewriter v1.1.1 h1:b3reP6GCfrHwmKkYwNRFh2rxidGHcT6cgxj/sHiDDx0=
|
||||
github.com/olekukonko/tablewriter v1.1.1/go.mod h1:De/bIcTF+gpBDB3Alv3fEsZA+9unTsSzAg/ZGADCtn4=
|
||||
github.com/olekukonko/tablewriter v1.1.2 h1:L2kI1Y5tZBct/O/TyZK1zIE9GlBj/TVs+AY5tZDCDSc=
|
||||
github.com/olekukonko/tablewriter v1.1.2/go.mod h1:z7SYPugVqGVavWoA2sGsFIoOVNmEHxUAAMrhXONtfkg=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
@@ -404,8 +396,6 @@ github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9Z
|
||||
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
|
||||
github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0=
|
||||
github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw=
|
||||
github.com/redis/go-redis/v9 v9.17.1 h1:7tl732FjYPRT9H9aNfyTwKg9iTETjWjGKEJ2t/5iWTs=
|
||||
github.com/redis/go-redis/v9 v9.17.1/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370=
|
||||
github.com/redis/go-redis/v9 v9.17.2 h1:P2EGsA4qVIM3Pp+aPocCJ7DguDHhqrXNhVcEp4ViluI=
|
||||
github.com/redis/go-redis/v9 v9.17.2/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
@@ -442,6 +432,8 @@ github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
|
||||
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||
github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
|
||||
github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
|
||||
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
|
||||
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
|
||||
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
|
||||
@@ -265,6 +265,7 @@ func getHexColorFromCaldavColor(caldavColor string) string {
|
||||
return hexColor
|
||||
}
|
||||
|
||||
//nolint:gocyclo
|
||||
func ParseTaskFromVTODO(content string) (vTask *models.Task, err error) {
|
||||
parsed, err := ics.ParseCalendar(strings.NewReader(content))
|
||||
if err != nil {
|
||||
@@ -301,11 +302,23 @@ func ParseTaskFromVTODO(content string) (vTask *models.Task, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Get UID and SUMMARY (log warning if missing, but don't fail for backwards compatibility)
|
||||
uid, hasUID := task["UID"]
|
||||
if !hasUID {
|
||||
log.Warningf("[CALDAV] VTODO missing UID field")
|
||||
}
|
||||
|
||||
summary, hasSummary := task["SUMMARY"]
|
||||
if !hasSummary {
|
||||
log.Warningf("[CALDAV] VTODO missing SUMMARY field")
|
||||
}
|
||||
|
||||
// Parse the priority
|
||||
var priority int64
|
||||
if _, ok := task["PRIORITY"]; ok {
|
||||
priorityParsed, err := strconv.ParseInt(task["PRIORITY"].Value, 10, 64)
|
||||
if err != nil {
|
||||
log.Errorf("[CALDAV] Failed to parse PRIORITY: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -313,10 +326,16 @@ func ParseTaskFromVTODO(content string) (vTask *models.Task, err error) {
|
||||
}
|
||||
|
||||
// Parse the enddate
|
||||
duration, _ := time.ParseDuration(task["DURATION"].Value)
|
||||
var duration time.Duration
|
||||
if durationProp, ok := task["DURATION"]; ok {
|
||||
duration, _ = time.ParseDuration(durationProp.Value)
|
||||
}
|
||||
|
||||
description := strings.ReplaceAll(task["DESCRIPTION"].Value, "\\,", ",")
|
||||
description = strings.ReplaceAll(description, "\\n", "\n")
|
||||
description := ""
|
||||
if descProp, ok := task["DESCRIPTION"]; ok {
|
||||
description = strings.ReplaceAll(descProp.Value, "\\,", ",")
|
||||
description = strings.ReplaceAll(description, "\\n", "\n")
|
||||
}
|
||||
|
||||
var labels []*models.Label
|
||||
if val, ok := task["CATEGORIES"]; ok {
|
||||
@@ -329,9 +348,18 @@ func ParseTaskFromVTODO(content string) (vTask *models.Task, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Safely extract values
|
||||
var uidValue, titleValue string
|
||||
if hasUID {
|
||||
uidValue = uid.Value
|
||||
}
|
||||
if hasSummary {
|
||||
titleValue = summary.Value
|
||||
}
|
||||
|
||||
vTask = &models.Task{
|
||||
UID: task["UID"].Value,
|
||||
Title: task["SUMMARY"].Value,
|
||||
UID: uidValue,
|
||||
Title: titleValue,
|
||||
Description: description,
|
||||
Priority: priority,
|
||||
Labels: labels,
|
||||
@@ -371,7 +399,7 @@ func ParseTaskFromVTODO(content string) (vTask *models.Task, err error) {
|
||||
})
|
||||
}
|
||||
|
||||
if task["STATUS"].Value == "COMPLETED" {
|
||||
if status, ok := task["STATUS"]; ok && status.Value == "COMPLETED" {
|
||||
vTask.Done = true
|
||||
}
|
||||
|
||||
|
||||
@@ -17,14 +17,26 @@
|
||||
package caldav
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"gopkg.in/d4l3k/messagediff.v1"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
// Initialize logger for tests
|
||||
log.InitLogger()
|
||||
|
||||
// Run tests
|
||||
code := m.Run()
|
||||
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
func TestParseTaskFromVTODO(t *testing.T) {
|
||||
type args struct {
|
||||
content string
|
||||
|
||||
@@ -325,7 +325,7 @@ func duplicateProjectBackground(s *xorm.Session, pd *ProjectDuplicate, doer web.
|
||||
|
||||
log.Debugf("Duplicated project background from project %d into %d", pd.ProjectID, pd.Project.ID)
|
||||
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
func duplicateTasks(s *xorm.Session, doer web.Auth, ld *ProjectDuplicate) (newTaskIDs map[int64]int64, err error) {
|
||||
|
||||
@@ -28,18 +28,30 @@ import (
|
||||
)
|
||||
|
||||
func TestProjectDuplicate(t *testing.T) {
|
||||
t.Run("duplicate project", func(t *testing.T) {
|
||||
testProjectDuplicate(t, 1, 1)
|
||||
})
|
||||
|
||||
t.Run("duplicate project with uploaded background", func(t *testing.T) {
|
||||
// Project 35 has a background_file_id of 1, which is NOT an Unsplash photo
|
||||
// This tests the fix for issue #1745 where duplicating a project with an uploaded
|
||||
// (non-Unsplash) background would fail with an internal server error
|
||||
testProjectDuplicate(t, 35, 6)
|
||||
})
|
||||
}
|
||||
|
||||
func testProjectDuplicate(t *testing.T, projectID int64, userID int64) {
|
||||
files.InitTestFileFixtures(t)
|
||||
db.LoadAndAssertFixtures(t)
|
||||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
u := &user.User{
|
||||
ID: 1,
|
||||
ID: userID,
|
||||
}
|
||||
|
||||
l := &ProjectDuplicate{
|
||||
ProjectID: 1,
|
||||
ProjectID: projectID,
|
||||
}
|
||||
can, err := l.CanCreate(s, u)
|
||||
require.NoError(t, err)
|
||||
@@ -118,6 +130,7 @@ func TestProjectDuplicate(t *testing.T) {
|
||||
}
|
||||
|
||||
// Check that the kanban view in the duplicated project has a different default bucket than the original
|
||||
// (only if the original kanban view has a default bucket configured)
|
||||
var originalKanbanView *ProjectView
|
||||
var duplicatedKanbanView *ProjectView
|
||||
|
||||
@@ -135,10 +148,7 @@ func TestProjectDuplicate(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
require.NotNil(t, originalKanbanView, "Original project does not have a kanban view")
|
||||
require.NotNil(t, duplicatedKanbanView, "Duplicated project does not have a kanban view")
|
||||
|
||||
if originalKanbanView != nil && duplicatedKanbanView != nil {
|
||||
if originalKanbanView != nil && duplicatedKanbanView != nil && originalKanbanView.DefaultBucketID != 0 {
|
||||
assert.NotEqual(t, originalKanbanView.DefaultBucketID, duplicatedKanbanView.DefaultBucketID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,6 +288,8 @@ func (vcls *VikunjaCaldavProjectStorage) CreateResource(rpath, content string) (
|
||||
|
||||
vTask, err := caldav.ParseTaskFromVTODO(content)
|
||||
if err != nil {
|
||||
log.Errorf("[CALDAV] Failed to parse VTODO in CreateResource: %v", err)
|
||||
log.Debugf("[CALDAV] VTODO content that failed to parse: %s", content)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -296,15 +298,18 @@ func (vcls *VikunjaCaldavProjectStorage) CreateResource(rpath, content string) (
|
||||
// Check the permissions
|
||||
canCreate, err := vTask.CanCreate(s, vcls.user)
|
||||
if err != nil {
|
||||
log.Errorf("[CALDAV] Permission check failed in CreateResource for user %s, project %d: %v", vcls.user.Username, vcls.project.ID, err)
|
||||
return nil, err
|
||||
}
|
||||
if !canCreate {
|
||||
log.Warningf("[CALDAV] User %s does not have permission to create task in project %d", vcls.user.Username, vcls.project.ID)
|
||||
return nil, errs.ForbiddenError
|
||||
}
|
||||
|
||||
// Create the task
|
||||
err = vTask.Create(s, vcls.user)
|
||||
if err != nil {
|
||||
log.Errorf("[CALDAV] Failed to create task in CreateResource: %v, task: %+v", err, vTask)
|
||||
_ = s.Rollback()
|
||||
return nil, err
|
||||
}
|
||||
@@ -312,6 +317,7 @@ func (vcls *VikunjaCaldavProjectStorage) CreateResource(rpath, content string) (
|
||||
vcls.task.ID = vTask.ID
|
||||
err = persistLabels(s, vcls.user, vcls.task, vTask.Labels)
|
||||
if err != nil {
|
||||
log.Errorf("[CALDAV] Failed to persist labels in CreateResource: %v, labels: %+v", err, vTask.Labels)
|
||||
_ = s.Rollback()
|
||||
return nil, err
|
||||
}
|
||||
@@ -319,11 +325,13 @@ func (vcls *VikunjaCaldavProjectStorage) CreateResource(rpath, content string) (
|
||||
vcls.task.ProjectID = vcls.project.ID
|
||||
err = persistRelations(s, vcls.user, vcls.task, vTask.RelatedTasks)
|
||||
if err != nil {
|
||||
log.Errorf("[CALDAV] Failed to persist relations in CreateResource: %v, relations: %+v", err, vTask.RelatedTasks)
|
||||
_ = s.Rollback()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := s.Commit(); err != nil {
|
||||
log.Errorf("[CALDAV] Failed to commit transaction in CreateResource: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -341,6 +349,8 @@ func (vcls *VikunjaCaldavProjectStorage) UpdateResource(rpath, content string) (
|
||||
|
||||
vTask, err := caldav.ParseTaskFromVTODO(content)
|
||||
if err != nil {
|
||||
log.Errorf("[CALDAV] Failed to parse VTODO in UpdateResource: %v", err)
|
||||
log.Debugf("[CALDAV] VTODO content that failed to parse: %s", content)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -357,10 +367,12 @@ func (vcls *VikunjaCaldavProjectStorage) UpdateResource(rpath, content string) (
|
||||
// Check the permissions
|
||||
canUpdate, err := vTask.CanUpdate(s, vcls.user)
|
||||
if err != nil {
|
||||
log.Errorf("[CALDAV] Permission check failed in UpdateResource for user %s, task %d: %v", vcls.user.Username, vcls.task.ID, err)
|
||||
_ = s.Rollback()
|
||||
return nil, err
|
||||
}
|
||||
if !canUpdate {
|
||||
log.Warningf("[CALDAV] User %s does not have permission to update task %d", vcls.user.Username, vcls.task.ID)
|
||||
_ = s.Rollback()
|
||||
return nil, errs.ForbiddenError
|
||||
}
|
||||
@@ -368,23 +380,27 @@ func (vcls *VikunjaCaldavProjectStorage) UpdateResource(rpath, content string) (
|
||||
// Update the task
|
||||
err = vTask.Update(s, vcls.user)
|
||||
if err != nil {
|
||||
log.Errorf("[CALDAV] Failed to update task in UpdateResource: %v, task: %+v", err, vTask)
|
||||
_ = s.Rollback()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = persistLabels(s, vcls.user, vcls.task, vTask.Labels)
|
||||
if err != nil {
|
||||
log.Errorf("[CALDAV] Failed to persist labels in UpdateResource: %v, labels: %+v", err, vTask.Labels)
|
||||
_ = s.Rollback()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = persistRelations(s, vcls.user, vcls.task, vTask.RelatedTasks)
|
||||
if err != nil {
|
||||
log.Errorf("[CALDAV] Failed to persist relations in UpdateResource: %v, relations: %+v", err, vTask.RelatedTasks)
|
||||
_ = s.Rollback()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := s.Commit(); err != nil {
|
||||
log.Errorf("[CALDAV] Failed to commit transaction in UpdateResource: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user