snapshot of https://github.com/traggo/server.git (master, 2026-09-16; blobs > 131072B omitted)
Some checks failed
build / build (push) Failing after 3s
Some checks failed
build / build (push) Failing after 3s
This commit is contained in:
38
tag/create.go
Normal file
38
tag/create.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package tag
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/traggo/server/auth"
|
||||
"github.com/traggo/server/generated/gqlmodel"
|
||||
"github.com/traggo/server/model"
|
||||
)
|
||||
|
||||
// CreateTag creates a tag.
|
||||
func (r *ResolverForTag) CreateTag(ctx context.Context, key string, color string) (*gqlmodel.TagDefinition, error) {
|
||||
if strings.TrimSpace(key) == "" {
|
||||
return nil, fmt.Errorf("tag must not be empty")
|
||||
}
|
||||
if strings.Contains(key, " ") {
|
||||
return nil, fmt.Errorf("tag must not contain spaces")
|
||||
}
|
||||
|
||||
userID := auth.GetUser(ctx).ID
|
||||
definition := &model.TagDefinition{
|
||||
Key: strings.ToLower(key),
|
||||
Color: color,
|
||||
UserID: userID,
|
||||
}
|
||||
|
||||
if !r.DB.Where("user_id = ?", userID).Where("key = ?", strings.ToLower(key)).Find(new(model.TagDefinition)).RecordNotFound() {
|
||||
return nil, fmt.Errorf("tag with key '%s' does already exist", definition.Key)
|
||||
}
|
||||
|
||||
create := r.DB.Create(&definition)
|
||||
gqlTag := &gqlmodel.TagDefinition{}
|
||||
copier.Copy(gqlTag, definition)
|
||||
return gqlTag, create.Error
|
||||
}
|
||||
101
tag/create_test.go
Normal file
101
tag/create_test.go
Normal file
@@ -0,0 +1,101 @@
|
||||
package tag
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/traggo/server/generated/gqlmodel"
|
||||
"github.com/traggo/server/model"
|
||||
"github.com/traggo/server/test"
|
||||
"github.com/traggo/server/test/fake"
|
||||
)
|
||||
|
||||
func TestGQL_CreateTag_succeeds_addsTag(t *testing.T) {
|
||||
db := test.InMemoryDB(t)
|
||||
defer db.Close()
|
||||
db.User(5)
|
||||
|
||||
resolver := ResolverForTag{DB: db.DB}
|
||||
tag, err := resolver.CreateTag(fake.User(5), "new_tag", "#fff")
|
||||
|
||||
require.Nil(t, err)
|
||||
expected := &gqlmodel.TagDefinition{
|
||||
Key: "new_tag",
|
||||
Color: "#fff",
|
||||
}
|
||||
require.Equal(t, expected, tag)
|
||||
assertTagExist(t, db, model.TagDefinition{
|
||||
Key: "new_tag",
|
||||
Color: "#fff",
|
||||
UserID: 5,
|
||||
})
|
||||
assertTagCount(t, db, 1)
|
||||
}
|
||||
|
||||
func TestGQL_CreateTag_fails_tagAlreadyExists(t *testing.T) {
|
||||
db := test.InMemoryDB(t)
|
||||
defer db.Close()
|
||||
db.User(5)
|
||||
db.Create(&model.TagDefinition{Key: "existing_tag", Color: "#fff", UserID: 5})
|
||||
|
||||
resolver := ResolverForTag{DB: db.DB}
|
||||
_, err := resolver.CreateTag(fake.User(5), "existing_tag", "#fff")
|
||||
|
||||
require.EqualError(t, err, "tag with key 'existing_tag' does already exist")
|
||||
assertTagCount(t, db, 1)
|
||||
}
|
||||
|
||||
func TestGQL_CreateTag_fails_tagAlreadyExists_caseInsensitive(t *testing.T) {
|
||||
db := test.InMemoryDB(t)
|
||||
defer db.Close()
|
||||
db.User(5)
|
||||
db.Create(&model.TagDefinition{Key: "tag", Color: "#fff", UserID: 5})
|
||||
|
||||
resolver := ResolverForTag{DB: db.DB}
|
||||
_, err := resolver.CreateTag(fake.User(5), "Tag", "#fff")
|
||||
|
||||
require.EqualError(t, err, "tag with key 'tag' does already exist")
|
||||
assertTagCount(t, db, 1)
|
||||
}
|
||||
|
||||
func TestGQL_CreateTag_succeeds_existingTagForOtherUser(t *testing.T) {
|
||||
db := test.InMemoryDB(t)
|
||||
defer db.Close()
|
||||
db.User(4)
|
||||
db.User(5)
|
||||
db.Create(&model.TagDefinition{Key: "existing_tag", Color: "#fff", UserID: 4})
|
||||
|
||||
resolver := ResolverForTag{DB: db.DB}
|
||||
_, err := resolver.CreateTag(fake.User(5), "existing_tag", "#xxx")
|
||||
|
||||
assert.Nil(t, err)
|
||||
assertTagCount(t, db, 2)
|
||||
assertTagExist(t, db, model.TagDefinition{
|
||||
Key: "existing_tag",
|
||||
Color: "#xxx",
|
||||
UserID: 5,
|
||||
})
|
||||
}
|
||||
|
||||
func TestGQL_CreateTag_fails_empty(t *testing.T) {
|
||||
db := test.InMemoryDB(t)
|
||||
defer db.Close()
|
||||
db.User(5)
|
||||
resolver := ResolverForTag{DB: db.DB}
|
||||
_, err := resolver.CreateTag(fake.User(5), "", "#fff")
|
||||
|
||||
require.EqualError(t, err, "tag must not be empty")
|
||||
assertTagCount(t, db, 0)
|
||||
}
|
||||
|
||||
func TestGQL_CreateTag_fails_includes_space(t *testing.T) {
|
||||
db := test.InMemoryDB(t)
|
||||
defer db.Close()
|
||||
db.User(5)
|
||||
resolver := ResolverForTag{DB: db.DB}
|
||||
_, err := resolver.CreateTag(fake.User(5), "test tag", "#fff")
|
||||
|
||||
require.EqualError(t, err, "tag must not contain spaces")
|
||||
assertTagCount(t, db, 0)
|
||||
}
|
||||
30
tag/get.go
Normal file
30
tag/get.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package tag
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/traggo/server/auth"
|
||||
"github.com/traggo/server/generated/gqlmodel"
|
||||
"github.com/traggo/server/model"
|
||||
)
|
||||
|
||||
// Tags returns all tags.
|
||||
func (r *ResolverForTag) Tags(ctx context.Context) ([]*gqlmodel.TagDefinition, error) {
|
||||
var tags []model.TagDefinition
|
||||
userID := auth.GetUser(ctx).ID
|
||||
|
||||
timeSpansIdsOfUser := r.DB.Model(new(model.TimeSpan)).
|
||||
Select("id").
|
||||
Where(&model.TimeSpan{UserID: userID}).
|
||||
SubQuery()
|
||||
usages := r.DB.Select("COUNT (*)").Where("time_span_tags.time_span_id in ?", timeSpansIdsOfUser).
|
||||
Where("tag_definitions.key = time_span_tags.key").
|
||||
Model(new(model.TimeSpanTag)).
|
||||
Group("time_span_tags.key").
|
||||
SubQuery()
|
||||
find := r.DB.Select("tag_definitions.*, ? as usages", usages).Where("user_id = ?", userID).Order("usages desc").Find(&tags)
|
||||
result := []*gqlmodel.TagDefinition{}
|
||||
copier.Copy(&result, &tags)
|
||||
return result, find.Error
|
||||
}
|
||||
36
tag/get_test.go
Normal file
36
tag/get_test.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package tag
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/traggo/server/generated/gqlmodel"
|
||||
"github.com/traggo/server/test"
|
||||
"github.com/traggo/server/test/fake"
|
||||
)
|
||||
|
||||
func TestGQL_Tags(t *testing.T) {
|
||||
db := test.InMemoryDB(t)
|
||||
defer db.Close()
|
||||
left := db.User(5)
|
||||
right := db.User(2)
|
||||
left.NewTagDefinition("my tag")
|
||||
left.NewTagDefinition("my tag 2")
|
||||
left.TimeSpan("2009-06-30T18:30:00Z", "2009-06-30T18:40:00Z").Tag("my tag", "value")
|
||||
left.TimeSpan("2009-06-30T18:30:00Z", "2009-06-30T18:40:00Z").Tag("my tag", "value").Tag("my tag 2", "v")
|
||||
left.TimeSpan("2009-06-30T18:30:00Z", "2009-06-30T18:40:00Z").Tag("my tag", "value").Tag("my tag 2", "v")
|
||||
right.NewTagDefinition("my tag")
|
||||
right.NewTagDefinition("my tag 2")
|
||||
right.NewTagDefinition("my tag 5")
|
||||
right.TimeSpan("2009-06-30T18:30:00Z", "2009-06-30T18:40:00Z").Tag("my tag", "value").Tag("my tag 2", "v")
|
||||
|
||||
resolver := ResolverForTag{DB: db.DB}
|
||||
tags, err := resolver.Tags(fake.User(left.User.ID))
|
||||
|
||||
require.Nil(t, err)
|
||||
expected := []*gqlmodel.TagDefinition{
|
||||
{Key: "my tag", Usages: 3},
|
||||
{Key: "my tag 2", Usages: 2},
|
||||
}
|
||||
require.Equal(t, expected, tags)
|
||||
}
|
||||
60
tag/remove.go
Normal file
60
tag/remove.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package tag
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/traggo/server/auth"
|
||||
"github.com/traggo/server/generated/gqlmodel"
|
||||
"github.com/traggo/server/model"
|
||||
)
|
||||
|
||||
// RemoveTag removes a tag.
|
||||
func (r *ResolverForTag) RemoveTag(ctx context.Context, key string) (*gqlmodel.TagDefinition, error) {
|
||||
tag := model.TagDefinition{}
|
||||
userID := auth.GetUser(ctx).ID
|
||||
if r.DB.Where(&model.TagDefinition{UserID: userID, Key: key}).Find(&tag).RecordNotFound() {
|
||||
return nil, fmt.Errorf("tag with key '%s' does not exist", key)
|
||||
}
|
||||
|
||||
usedInEntries := []model.DashboardEntry{}
|
||||
if err := r.DB.
|
||||
Joins("INNER JOIN dashboards ON dashboard_entries.dashboard_id = dashboards.id").
|
||||
Where("dashboards.user_id = ?", userID).
|
||||
Where("keys LIKE ? OR keys LIKE ? OR keys LIKE ?", "%"+key, "%"+key+"%", key+"%").
|
||||
Find(&usedInEntries).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(usedInEntries) > 0 {
|
||||
dashboard := &model.Dashboard{ID: usedInEntries[0].DashboardID}
|
||||
r.DB.Find(dashboard)
|
||||
return nil, fmt.Errorf("tag '%s' is used in dashboard '%s' entry '%s', remove this reference before deleting the tag", key, dashboard.Name, usedInEntries[0].Title)
|
||||
}
|
||||
|
||||
tx := r.DB.Begin()
|
||||
if err := tx.Where(model.TagDefinition{Key: key, UserID: userID}).
|
||||
Delete(new(model.TagDefinition)).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
timeSpansIdsOfUser := tx.Model(new(model.TimeSpan)).
|
||||
Select("id").
|
||||
Where(&model.TimeSpan{UserID: userID}).
|
||||
SubQuery()
|
||||
|
||||
if err := tx.
|
||||
Where("time_span_id in ?", timeSpansIdsOfUser).
|
||||
Where(&model.TimeSpanTag{Key: key}).
|
||||
Delete(new(model.TimeSpanTag)).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
remove := tx.Commit()
|
||||
gqlTag := &gqlmodel.TagDefinition{}
|
||||
copier.Copy(gqlTag, &tag)
|
||||
return gqlTag, remove.Error
|
||||
}
|
||||
106
tag/remove_test.go
Normal file
106
tag/remove_test.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package tag
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/traggo/server/test"
|
||||
"github.com/traggo/server/test/fake"
|
||||
)
|
||||
|
||||
func TestGQL_RemoveTag_succeeds_removesTag(t *testing.T) {
|
||||
db := test.InMemoryDB(t)
|
||||
defer db.Close()
|
||||
user := db.User(3)
|
||||
user.NewTagDefinition("existing")
|
||||
|
||||
resolver := ResolverForTag{DB: db.DB}
|
||||
_, err := resolver.RemoveTag(fake.User(3), "existing")
|
||||
require.Nil(t, err)
|
||||
user.AssertHasTagDefinition("existing", false)
|
||||
}
|
||||
|
||||
func TestRemove_referencedInDashboardEntry(t *testing.T) {
|
||||
db := test.InMemoryDB(t)
|
||||
defer db.Close()
|
||||
left := db.User(5)
|
||||
left.NewTagDefinition("coolio")
|
||||
dashboard := left.Dashboard("yeah")
|
||||
dashboard.Entry("entry")
|
||||
entry := dashboard.Dashboard.Entries[0]
|
||||
entry.Keys = "abc,coolio,chicken"
|
||||
db.Save(&entry)
|
||||
|
||||
resolver := ResolverForTag{DB: db.DB}
|
||||
_, err := resolver.RemoveTag(fake.User(left.User.ID), "coolio")
|
||||
require.EqualError(t, err, "tag 'coolio' is used in dashboard 'yeah' entry 'entry', remove this reference before deleting the tag")
|
||||
}
|
||||
|
||||
func TestGQL_RemoveTag_succeeds_removesTimespans(t *testing.T) {
|
||||
db := test.InMemoryDB(t)
|
||||
defer db.Close()
|
||||
left := db.User(3)
|
||||
right := db.User(4)
|
||||
left.NewTagDefinition("tag")
|
||||
right.NewTagDefinition("tag")
|
||||
leftTs := left.TimeSpan("2009-06-30T18:30:00Z", "2009-06-30T18:40:00Z")
|
||||
leftTs.Tag("tag", "def")
|
||||
rightTs := right.TimeSpan("2009-06-30T18:30:00Z", "2009-06-30T18:40:00Z")
|
||||
rightTs.Tag("tag", "def")
|
||||
|
||||
resolver := ResolverForTag{DB: db.DB}
|
||||
_, err := resolver.RemoveTag(fake.User(left.User.ID), "tag")
|
||||
require.Nil(t, err)
|
||||
|
||||
assertTagCount(t, db, 1)
|
||||
|
||||
left.AssertHasTagDefinition("tag", false)
|
||||
right.AssertHasTagDefinition("tag", true)
|
||||
|
||||
leftTs.AssertExists(true).AssertHasTag("tag", "def", false)
|
||||
rightTs.AssertExists(true).AssertHasTag("tag", "def", true)
|
||||
}
|
||||
|
||||
func TestGQL_RemoveTag_fails_notExistingTag(t *testing.T) {
|
||||
db := test.InMemoryDB(t)
|
||||
defer db.Close()
|
||||
db.User(3)
|
||||
|
||||
resolver := ResolverForTag{DB: db.DB}
|
||||
_, err := resolver.RemoveTag(fake.User(3), "not existing")
|
||||
require.EqualError(t, err, "tag with key 'not existing' does not exist")
|
||||
}
|
||||
|
||||
func TestGQL_RemoveTag_fails_notPermission(t *testing.T) {
|
||||
db := test.InMemoryDB(t)
|
||||
defer db.Close()
|
||||
db.User(3).NewTagDefinition("existing")
|
||||
db.User(5)
|
||||
|
||||
resolver := ResolverForTag{DB: db.DB}
|
||||
_, err := resolver.RemoveTag(fake.User(5), "existing")
|
||||
require.EqualError(t, err, "tag with key 'existing' does not exist")
|
||||
}
|
||||
|
||||
func TestRemove_crossUserIsolation(t *testing.T) {
|
||||
db := test.InMemoryDB(t)
|
||||
defer db.Close()
|
||||
userA := db.User(5)
|
||||
userB := db.User(2)
|
||||
|
||||
userA.NewTagDefinition("project")
|
||||
userB.NewTagDefinition("project")
|
||||
|
||||
dashboardB := userB.Dashboard("secretDashboard")
|
||||
dashboardB.Entry("secretEntry")
|
||||
entryB := dashboardB.Dashboard.Entries[0]
|
||||
entryB.Keys = "project,secret"
|
||||
db.Save(&entryB)
|
||||
|
||||
resolver := ResolverForTag{DB: db.DB}
|
||||
_, err := resolver.RemoveTag(fake.User(userA.User.ID), "project")
|
||||
require.NoError(t, err, "removing user's own tag should succeed even if another user has a dashboard entry with that tag")
|
||||
|
||||
userA.AssertHasTagDefinition("project", false)
|
||||
userB.AssertHasTagDefinition("project", true)
|
||||
}
|
||||
19
tag/suggest.go
Normal file
19
tag/suggest.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package tag
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/traggo/server/auth"
|
||||
"github.com/traggo/server/generated/gqlmodel"
|
||||
"github.com/traggo/server/model"
|
||||
)
|
||||
|
||||
// SuggestTag suggests a tag.
|
||||
func (r *ResolverForTag) SuggestTag(ctx context.Context, query string) ([]*gqlmodel.TagDefinition, error) {
|
||||
var suggestions []model.TagDefinition
|
||||
find := r.DB.Where("user_id = ?", auth.GetUser(ctx).ID).Where("Key LIKE ?", query+"%").Find(&suggestions)
|
||||
var result []*gqlmodel.TagDefinition
|
||||
copier.Copy(&result, &suggestions)
|
||||
return result, find.Error
|
||||
}
|
||||
47
tag/suggest_test.go
Normal file
47
tag/suggest_test.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package tag
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/traggo/server/generated/gqlmodel"
|
||||
"github.com/traggo/server/model"
|
||||
"github.com/traggo/server/test"
|
||||
"github.com/traggo/server/test/fake"
|
||||
)
|
||||
|
||||
func TestGQL_SuggestTag_matchesTags(t *testing.T) {
|
||||
db := test.InMemoryDB(t)
|
||||
defer db.Close()
|
||||
db.User(1)
|
||||
db.User(2)
|
||||
resolver := ResolverForTag{DB: db.DB}
|
||||
db.Create(&model.TagDefinition{Key: "project", Color: "#fff", UserID: 1})
|
||||
db.Create(&model.TagDefinition{Key: "project2", Color: "#fff", UserID: 2})
|
||||
db.Create(&model.TagDefinition{Key: "priority", Color: "#fff", UserID: 1})
|
||||
db.Create(&model.TagDefinition{Key: "wood", Color: "#fff", UserID: 1})
|
||||
|
||||
tags, err := resolver.SuggestTag(fake.User(1), "pr")
|
||||
|
||||
require.Nil(t, err)
|
||||
expected := []*gqlmodel.TagDefinition{
|
||||
{Key: "project", Color: "#fff"},
|
||||
{Key: "priority", Color: "#fff"},
|
||||
}
|
||||
require.Equal(t, expected, tags)
|
||||
}
|
||||
|
||||
func TestGQL_SuggestTag_noMatchingTags(t *testing.T) {
|
||||
db := test.InMemoryDB(t)
|
||||
defer db.Close()
|
||||
db.User(1)
|
||||
resolver := ResolverForTag{DB: db.DB}
|
||||
db.Create(&model.TagDefinition{Key: "project", Color: "#fff", UserID: 1})
|
||||
db.Create(&model.TagDefinition{Key: "wood", Color: "#fff", UserID: 1})
|
||||
|
||||
tags, err := resolver.SuggestTag(fake.User(1), "fire")
|
||||
|
||||
require.Nil(t, err)
|
||||
expected := []*gqlmodel.TagDefinition{}
|
||||
require.Equal(t, expected, tags)
|
||||
}
|
||||
8
tag/tagresolver.go
Normal file
8
tag/tagresolver.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package tag
|
||||
|
||||
import "github.com/jinzhu/gorm"
|
||||
|
||||
// ResolverForTag resolves tag specific things.
|
||||
type ResolverForTag struct {
|
||||
DB *gorm.DB
|
||||
}
|
||||
86
tag/update.go
Normal file
86
tag/update.go
Normal file
@@ -0,0 +1,86 @@
|
||||
package tag
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/traggo/server/auth"
|
||||
"github.com/traggo/server/generated/gqlmodel"
|
||||
"github.com/traggo/server/model"
|
||||
)
|
||||
|
||||
// UpdateTag updates a tag.
|
||||
func (r *ResolverForTag) UpdateTag(ctx context.Context, key string, newKey *string, color string) (*gqlmodel.TagDefinition, error) {
|
||||
tag := model.TagDefinition{}
|
||||
userID := auth.GetUser(ctx).ID
|
||||
if r.DB.Where(&model.TagDefinition{UserID: userID, Key: key}).Find(&tag).RecordNotFound() {
|
||||
return nil, fmt.Errorf("tag with key '%s' does not exist", key)
|
||||
}
|
||||
|
||||
tx := r.DB.Begin()
|
||||
|
||||
newValue := model.TagDefinition{
|
||||
Key: strings.ToLower(key),
|
||||
Color: color,
|
||||
UserID: userID,
|
||||
}
|
||||
|
||||
if newKey != nil && *newKey != key {
|
||||
if strings.Contains(*newKey, " ") {
|
||||
tx.Rollback()
|
||||
return nil, fmt.Errorf("tag must not contain spaces")
|
||||
}
|
||||
*newKey = strings.ToLower(*newKey)
|
||||
newValue.Key = *newKey
|
||||
timeSpansIdsOfUser := tx.Model(new(model.TimeSpan)).
|
||||
Select("id").
|
||||
Where(&model.TimeSpan{UserID: userID}).
|
||||
SubQuery()
|
||||
|
||||
if err := tx.
|
||||
Model(new(model.TimeSpanTag)).
|
||||
Where("time_span_id in ?", timeSpansIdsOfUser).
|
||||
Where(&model.TimeSpanTag{Key: key}).
|
||||
Updates(&model.TimeSpanTag{Key: *newKey}).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return nil, err
|
||||
}
|
||||
usedInEntries := []model.DashboardEntry{}
|
||||
|
||||
if err := tx.
|
||||
Joins("INNER JOIN dashboards ON dashboard_entries.dashboard_id = dashboards.id").
|
||||
Where("dashboards.user_id = ?", userID).
|
||||
Where("keys LIKE ? OR keys LIKE ? OR keys LIKE ?", "%"+key, "%"+key+"%", key+"%").
|
||||
Find(&usedInEntries).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, entry := range usedInEntries {
|
||||
tags := strings.Split(entry.Keys, ",")
|
||||
for index, tagInEntry := range tags {
|
||||
if tagInEntry == key {
|
||||
tags[index] = *newKey
|
||||
}
|
||||
}
|
||||
entry.Keys = strings.Join(tags, ",")
|
||||
if err := tx.Save(&entry).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := tx.Model(new(model.TagDefinition)).Where(&model.TagDefinition{UserID: userID, Key: key}).Updates(&newValue).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
update := tx.Commit()
|
||||
|
||||
gqlTag := &gqlmodel.TagDefinition{}
|
||||
copier.Copy(gqlTag, &newValue)
|
||||
return gqlTag, update.Error
|
||||
}
|
||||
158
tag/update_test.go
Normal file
158
tag/update_test.go
Normal file
@@ -0,0 +1,158 @@
|
||||
package tag
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/magiconair/properties/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/traggo/server/generated/gqlmodel"
|
||||
"github.com/traggo/server/test"
|
||||
"github.com/traggo/server/test/fake"
|
||||
)
|
||||
|
||||
func TestUpdate_withKey(t *testing.T) {
|
||||
db := test.InMemoryDB(t)
|
||||
defer db.Close()
|
||||
left := db.User(5)
|
||||
right := db.User(2)
|
||||
left.NewTagDefinition("coolio")
|
||||
right.NewTagDefinition("coolio")
|
||||
leftTs := left.TimeSpan("2009-06-30T18:30:00Z", "2009-06-30T18:40:00Z")
|
||||
leftTs.Tag("coolio", "mama")
|
||||
rightTs := right.TimeSpan("2009-06-30T18:30:00Z", "2009-06-30T18:40:00Z")
|
||||
rightTs.Tag("coolio", "mama")
|
||||
|
||||
resolver := ResolverForTag{DB: db.DB}
|
||||
newTagName := "mega"
|
||||
tag, err := resolver.UpdateTag(fake.User(left.User.ID), "coolio", &newTagName, "#abc")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, &gqlmodel.TagDefinition{
|
||||
Color: "#abc",
|
||||
Key: "mega",
|
||||
}, tag)
|
||||
left.AssertHasTagDefinition("coolio", false).AssertHasTagDefinition("mega", true)
|
||||
right.AssertHasTagDefinition("coolio", true).AssertHasTagDefinition("mega", false)
|
||||
leftTs.AssertHasTag("mega", "mama", true).AssertHasTag("coolio", "mama", false)
|
||||
rightTs.AssertHasTag("coolio", "mama", true).AssertHasTag("mega", "mama", false)
|
||||
}
|
||||
|
||||
func TestUpdate_lowercases(t *testing.T) {
|
||||
db := test.InMemoryDB(t)
|
||||
defer db.Close()
|
||||
user := db.User(5)
|
||||
user.NewTagDefinition("coolio")
|
||||
ts := user.TimeSpan("2009-06-30T18:30:00Z", "2009-06-30T18:40:00Z")
|
||||
ts.Tag("coolio", "mama")
|
||||
|
||||
resolver := ResolverForTag{DB: db.DB}
|
||||
newTagName := "Mega"
|
||||
tag, err := resolver.UpdateTag(fake.User(user.User.ID), "coolio", &newTagName, "#abc")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, &gqlmodel.TagDefinition{
|
||||
Color: "#abc",
|
||||
Key: "mega",
|
||||
}, tag)
|
||||
user.AssertHasTagDefinition("coolio", false).AssertHasTagDefinition("mega", true)
|
||||
ts.AssertHasTag("mega", "mama", true).AssertHasTag("coolio", "mama", false)
|
||||
}
|
||||
|
||||
func TestUpdate_disallow_space(t *testing.T) {
|
||||
db := test.InMemoryDB(t)
|
||||
defer db.Close()
|
||||
user := db.User(5)
|
||||
user.NewTagDefinition("coolio")
|
||||
|
||||
resolver := ResolverForTag{DB: db.DB}
|
||||
newTagName := "the coolio"
|
||||
_, err := resolver.UpdateTag(fake.User(user.User.ID), "coolio", &newTagName, "#abc")
|
||||
require.EqualError(t, err, "tag must not contain spaces")
|
||||
}
|
||||
|
||||
func TestUpdate_withoutKey(t *testing.T) {
|
||||
db := test.InMemoryDB(t)
|
||||
defer db.Close()
|
||||
left := db.User(5)
|
||||
right := db.User(2)
|
||||
left.NewTagDefinition("coolio")
|
||||
right.NewTagDefinition("coolio")
|
||||
leftTs := left.TimeSpan("2009-06-30T18:30:00Z", "2009-06-30T18:40:00Z")
|
||||
leftTs.Tag("coolio", "mama")
|
||||
rightTs := right.TimeSpan("2009-06-30T18:30:00Z", "2009-06-30T18:40:00Z")
|
||||
rightTs.Tag("coolio", "mama")
|
||||
|
||||
resolver := ResolverForTag{DB: db.DB}
|
||||
tag, err := resolver.UpdateTag(fake.User(left.User.ID), "coolio", nil, "#abc")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, &gqlmodel.TagDefinition{
|
||||
Color: "#abc",
|
||||
Key: "coolio",
|
||||
}, tag)
|
||||
}
|
||||
|
||||
func TestUpdate_dashboardEntryKey(t *testing.T) {
|
||||
db := test.InMemoryDB(t)
|
||||
defer db.Close()
|
||||
left := db.User(5)
|
||||
left.NewTagDefinition("coolio")
|
||||
dashboard := left.Dashboard("yeah")
|
||||
dashboard.Entry("entry")
|
||||
entry := dashboard.Dashboard.Entries[0]
|
||||
entry.Keys = "abc,coolio,chicken"
|
||||
db.Save(&entry)
|
||||
|
||||
newTag := "yes"
|
||||
resolver := ResolverForTag{DB: db.DB}
|
||||
_, err := resolver.UpdateTag(fake.User(left.User.ID), "coolio", &newTag, "#abc")
|
||||
require.NoError(t, err)
|
||||
|
||||
db.Find(&entry)
|
||||
require.Equal(t, "abc,yes,chicken", entry.Keys)
|
||||
}
|
||||
|
||||
func TestUpdate_noPermissions(t *testing.T) {
|
||||
db := test.InMemoryDB(t)
|
||||
defer db.Close()
|
||||
left := db.User(5)
|
||||
right := db.User(2)
|
||||
right.NewTagDefinition("coolio")
|
||||
rightTs := right.TimeSpan("2009-06-30T18:30:00Z", "2009-06-30T18:40:00Z")
|
||||
rightTs.Tag("coolio", "mama")
|
||||
|
||||
resolver := ResolverForTag{DB: db.DB}
|
||||
_, err := resolver.UpdateTag(fake.User(left.User.ID), "coolio", nil, "#abc")
|
||||
require.EqualError(t, err, "tag with key 'coolio' does not exist")
|
||||
right.AssertHasTagDefinition("coolio", true)
|
||||
}
|
||||
|
||||
func TestUpdate_dashboardEntryKey_crossUserIsolation(t *testing.T) {
|
||||
db := test.InMemoryDB(t)
|
||||
defer db.Close()
|
||||
userA := db.User(5)
|
||||
userB := db.User(2)
|
||||
|
||||
userA.NewTagDefinition("project")
|
||||
userB.NewTagDefinition("project")
|
||||
|
||||
dashboardA := userA.Dashboard("dashboardA")
|
||||
dashboardA.Entry("entryA")
|
||||
entryA := dashboardA.Dashboard.Entries[0]
|
||||
entryA.Keys = "project,task1"
|
||||
db.Save(&entryA)
|
||||
|
||||
dashboardB := userB.Dashboard("dashboardB")
|
||||
dashboardB.Entry("entryB")
|
||||
entryB := dashboardB.Dashboard.Entries[0]
|
||||
entryB.Keys = "project,task2"
|
||||
db.Save(&entryB)
|
||||
|
||||
newTag := "newproject"
|
||||
resolver := ResolverForTag{DB: db.DB}
|
||||
_, err := resolver.UpdateTag(fake.User(userA.User.ID), "project", &newTag, "#abc")
|
||||
require.NoError(t, err)
|
||||
|
||||
db.Find(&entryA)
|
||||
require.Equal(t, "newproject,task1", entryA.Keys, "user A's entry should be updated")
|
||||
|
||||
db.Find(&entryB)
|
||||
require.Equal(t, "project,task2", entryB.Keys, "user B's entry should NOT be modified")
|
||||
}
|
||||
23
tag/utils_test.go
Normal file
23
tag/utils_test.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package tag
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/traggo/server/model"
|
||||
"github.com/traggo/server/test"
|
||||
)
|
||||
|
||||
func assertTagExist(t *testing.T, db *test.Database, expected model.TagDefinition) {
|
||||
foundUser := new(model.TagDefinition)
|
||||
find := db.Where("key = ?", expected.Key).Find(foundUser)
|
||||
require.Nil(t, find.Error)
|
||||
require.NotNil(t, foundUser)
|
||||
require.Equal(t, expected, *foundUser)
|
||||
}
|
||||
|
||||
func assertTagCount(t *testing.T, db *test.Database, expected int) {
|
||||
count := new(int)
|
||||
db.Model(new(model.TagDefinition)).Count(count)
|
||||
require.Equal(t, expected, *count)
|
||||
}
|
||||
Reference in New Issue
Block a user