Compare commits
7 Commits
main
...
release/0.
Author | SHA1 | Date | |
---|---|---|---|
f93362defa | |||
b1c750373e | |||
960258a8c2 | |||
75f95e2afc | |||
13bed6f749 | |||
9024fa6c3a | |||
d2d8c91c43 |
13
CHANGELOG.md
13
CHANGELOG.md
|
@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
All releases can be found on https://code.vikunja.io/api/releases.
|
All releases can be found on https://code.vikunja.io/api/releases.
|
||||||
|
|
||||||
|
## [0.14.1] - 2020-07-07
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* Fix creating lists with non ascii characters (#607)
|
||||||
|
* Fix decoding active users from redis
|
||||||
|
* Fix parsing todoist reminder dates
|
||||||
|
* Make sure the metrics map accesses only happen explicitly
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
* Update docs theme
|
||||||
|
|
||||||
## [0.14.0] - 2020-07-01
|
## [0.14.0] - 2020-07-01
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
[![Build Status](https://drone1.kolaente.de/api/badges/vikunja/api/status.svg)](https://drone1.kolaente.de/vikunja/api)
|
[![Build Status](https://drone1.kolaente.de/api/badges/vikunja/api/status.svg)](https://drone1.kolaente.de/vikunja/api)
|
||||||
[![License: GPL v3](https://img.shields.io/badge/License-GPL%20v3-blue.svg)](LICENSE)
|
[![License: GPL v3](https://img.shields.io/badge/License-GPL%20v3-blue.svg)](LICENSE)
|
||||||
[![Download](https://img.shields.io/badge/download-v0.14.0-brightgreen.svg)](https://dl.vikunja.io)
|
[![Download](https://img.shields.io/badge/download-v0.14.1-brightgreen.svg)](https://dl.vikunja.io)
|
||||||
[![Docker Pulls](https://img.shields.io/docker/pulls/vikunja/api.svg)](https://hub.docker.com/r/vikunja/api/)
|
[![Docker Pulls](https://img.shields.io/docker/pulls/vikunja/api.svg)](https://hub.docker.com/r/vikunja/api/)
|
||||||
[![Swagger Docs](https://img.shields.io/badge/swagger-docs-brightgreen.svg)](https://try.vikunja.io/api/v1/docs)
|
[![Swagger Docs](https://img.shields.io/badge/swagger-docs-brightgreen.svg)](https://try.vikunja.io/api/v1/docs)
|
||||||
[![Go Report Card](https://goreportcard.com/badge/git.kolaente.de/vikunja/api)](https://goreportcard.com/report/git.kolaente.de/vikunja/api)
|
[![Go Report Card](https://goreportcard.com/badge/git.kolaente.de/vikunja/api)](https://goreportcard.com/report/git.kolaente.de/vikunja/api)
|
||||||
|
|
2
docs/themes/vikunja
vendored
2
docs/themes/vikunja
vendored
|
@ -1 +1 @@
|
||||||
Subproject commit f50566db25df9fa03243ba06d17511e050d4be95
|
Subproject commit a17ba5976906ee431943798c08e4d3c38689590d
|
|
@ -23,6 +23,7 @@ import (
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -38,24 +39,35 @@ type ActiveUser struct {
|
||||||
LastSeen time.Time
|
LastSeen time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type activeUsersMap map[int64]*ActiveUser
|
||||||
|
|
||||||
// ActiveUsersMap is the type used to save active users
|
// ActiveUsersMap is the type used to save active users
|
||||||
type ActiveUsersMap map[int64]*ActiveUser
|
type ActiveUsers struct {
|
||||||
|
users activeUsersMap
|
||||||
|
mutex *sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
// activeUsers holds a map with all active users
|
// activeUsers holds a map with all active users
|
||||||
var activeUsers ActiveUsersMap
|
var activeUsers *ActiveUsers
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
activeUsers = make(ActiveUsersMap)
|
activeUsers = &ActiveUsers{
|
||||||
|
users: make(map[int64]*ActiveUser),
|
||||||
|
mutex: &sync.Mutex{},
|
||||||
|
}
|
||||||
|
|
||||||
promauto.NewGaugeFunc(prometheus.GaugeOpts{
|
promauto.NewGaugeFunc(prometheus.GaugeOpts{
|
||||||
Name: "vikunja_active_users",
|
Name: "vikunja_active_users",
|
||||||
Help: "The currently active users on this node",
|
Help: "The currently active users on this node",
|
||||||
}, func() float64 {
|
}, func() float64 {
|
||||||
|
|
||||||
allActiveUsers, err := GetActiveUsers()
|
allActiveUsers, err := getActiveUsers()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err.Error())
|
log.Error(err.Error())
|
||||||
}
|
}
|
||||||
|
if allActiveUsers == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
activeUsersCount := 0
|
activeUsersCount := 0
|
||||||
for _, u := range allActiveUsers {
|
for _, u := range allActiveUsers {
|
||||||
if time.Since(u.LastSeen) < SecondsUntilInactive*time.Second {
|
if time.Since(u.LastSeen) < SecondsUntilInactive*time.Second {
|
||||||
|
@ -68,15 +80,17 @@ func init() {
|
||||||
|
|
||||||
// SetUserActive sets a user as active and pushes it to redis
|
// SetUserActive sets a user as active and pushes it to redis
|
||||||
func SetUserActive(a web.Auth) (err error) {
|
func SetUserActive(a web.Auth) (err error) {
|
||||||
activeUsers[a.GetID()] = &ActiveUser{
|
activeUsers.mutex.Lock()
|
||||||
|
activeUsers.users[a.GetID()] = &ActiveUser{
|
||||||
UserID: a.GetID(),
|
UserID: a.GetID(),
|
||||||
LastSeen: time.Now(),
|
LastSeen: time.Now(),
|
||||||
}
|
}
|
||||||
|
activeUsers.mutex.Unlock()
|
||||||
return PushActiveUsers()
|
return PushActiveUsers()
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetActiveUsers returns the active users from redis
|
// getActiveUsers returns the active users from redis
|
||||||
func GetActiveUsers() (users ActiveUsersMap, err error) {
|
func getActiveUsers() (users activeUsersMap, err error) {
|
||||||
|
|
||||||
activeUsersR, err := r.Get(ActiveUsersKey).Bytes()
|
activeUsersR, err := r.Get(ActiveUsersKey).Bytes()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -102,7 +116,9 @@ func GetActiveUsers() (users ActiveUsersMap, err error) {
|
||||||
func PushActiveUsers() (err error) {
|
func PushActiveUsers() (err error) {
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
e := gob.NewEncoder(&b)
|
e := gob.NewEncoder(&b)
|
||||||
if err := e.Encode(activeUsers); err != nil {
|
activeUsers.mutex.Lock()
|
||||||
|
defer activeUsers.mutex.Unlock()
|
||||||
|
if err := e.Encode(activeUsers.users); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -55,7 +55,7 @@ func init() {
|
||||||
// The general idea here is to take the title and slice it into pieces, until we found a unique piece.
|
// The general idea here is to take the title and slice it into pieces, until we found a unique piece.
|
||||||
|
|
||||||
var exists = true
|
var exists = true
|
||||||
titleSlug := strings.Replace(strings.ToUpper(l.Title), " ", "", -1)
|
titleSlug := []rune(strings.Replace(strings.ToUpper(l.Title), " ", "", -1))
|
||||||
|
|
||||||
// We can save at most 10 characters in the db, so we need to ensure it has at most 10 characters
|
// We can save at most 10 characters in the db, so we need to ensure it has at most 10 characters
|
||||||
if len(titleSlug) > 10 {
|
if len(titleSlug) > 10 {
|
||||||
|
@ -72,7 +72,7 @@ func init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Take a random part of the title slug, starting at the beginning
|
// Take a random part of the title slug, starting at the beginning
|
||||||
l.Identifier = titleSlug[i:]
|
l.Identifier = string(titleSlug[i:])
|
||||||
exists, err = sess.
|
exists, err = sess.
|
||||||
Where("identifier = ?", l.Identifier).
|
Where("identifier = ?", l.Identifier).
|
||||||
And("id != ?", l.ID).
|
And("id != ?", l.ID).
|
||||||
|
|
|
@ -404,7 +404,7 @@ func GenerateListIdentifier(l *List, sess *xorm.Engine) (err error) {
|
||||||
// The general idea here is to take the title and slice it into pieces, until we found a unique piece.
|
// The general idea here is to take the title and slice it into pieces, until we found a unique piece.
|
||||||
|
|
||||||
var exists = true
|
var exists = true
|
||||||
titleSlug := strings.Replace(strings.ToUpper(l.Title), " ", "", -1)
|
titleSlug := []rune(strings.Replace(strings.ToUpper(l.Title), " ", "", -1))
|
||||||
|
|
||||||
// We can save at most 10 characters in the db, so we need to ensure it has at most 10 characters
|
// We can save at most 10 characters in the db, so we need to ensure it has at most 10 characters
|
||||||
if len(titleSlug) > 10 {
|
if len(titleSlug) > 10 {
|
||||||
|
@ -421,7 +421,7 @@ func GenerateListIdentifier(l *List, sess *xorm.Engine) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Take a random part of the title slug, starting at the beginning
|
// Take a random part of the title slug, starting at the beginning
|
||||||
l.Identifier = titleSlug[i:]
|
l.Identifier = string(titleSlug[i:])
|
||||||
exists, err = sess.
|
exists, err = sess.
|
||||||
Where("identifier = ?", l.Identifier).
|
Where("identifier = ?", l.Identifier).
|
||||||
And("id != ?", l.ID).
|
And("id != ?", l.ID).
|
||||||
|
|
|
@ -79,6 +79,16 @@ func TestList_CreateOrUpdate(t *testing.T) {
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.True(t, IsErrListIdentifierIsNotUnique(err))
|
assert.True(t, IsErrListIdentifierIsNotUnique(err))
|
||||||
})
|
})
|
||||||
|
t.Run("non ascii characters", func(t *testing.T) {
|
||||||
|
db.LoadAndAssertFixtures(t)
|
||||||
|
list := List{
|
||||||
|
Title: "приффки фсем",
|
||||||
|
Description: "Lorem Ipsum",
|
||||||
|
NamespaceID: 1,
|
||||||
|
}
|
||||||
|
err := list.Create(usr)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("update", func(t *testing.T) {
|
t.Run("update", func(t *testing.T) {
|
||||||
|
|
|
@ -368,7 +368,15 @@ func convertTodoistToVikunja(sync *sync) (fullVikunjaHierachie []*models.Namespa
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
date, err := time.Parse("2006-01-02", r.Due.Date)
|
var err error
|
||||||
|
var date time.Time
|
||||||
|
date, err = time.Parse("2006-01-02T15:04:05Z", r.Due.Date)
|
||||||
|
if err != nil {
|
||||||
|
date, err = time.Parse("2006-01-02T15:04:05", r.Due.Date)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
date, err = time.Parse("2006-01-02", r.Due.Date)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -274,7 +274,7 @@ func TestConvertTodoistToVikunja(t *testing.T) {
|
||||||
ID: 103001,
|
ID: 103001,
|
||||||
ItemID: 400000000,
|
ItemID: 400000000,
|
||||||
Due: &dueDate{
|
Due: &dueDate{
|
||||||
Date: "2020-06-16",
|
Date: "2020-06-16T07:00:00",
|
||||||
IsRecurring: false,
|
IsRecurring: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -282,7 +282,7 @@ func TestConvertTodoistToVikunja(t *testing.T) {
|
||||||
ID: 103002,
|
ID: 103002,
|
||||||
ItemID: 400000002,
|
ItemID: 400000002,
|
||||||
Due: &dueDate{
|
Due: &dueDate{
|
||||||
Date: "2020-07-15",
|
Date: "2020-07-15T07:00:00Z",
|
||||||
IsRecurring: true,
|
IsRecurring: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -290,7 +290,7 @@ func TestConvertTodoistToVikunja(t *testing.T) {
|
||||||
ID: 103003,
|
ID: 103003,
|
||||||
ItemID: 400000003,
|
ItemID: 400000003,
|
||||||
Due: &dueDate{
|
Due: &dueDate{
|
||||||
Date: "2020-06-15",
|
Date: "2020-06-15T07:00:00",
|
||||||
IsRecurring: false,
|
IsRecurring: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -298,7 +298,7 @@ func TestConvertTodoistToVikunja(t *testing.T) {
|
||||||
ID: 103004,
|
ID: 103004,
|
||||||
ItemID: 400000005,
|
ItemID: 400000005,
|
||||||
Due: &dueDate{
|
Due: &dueDate{
|
||||||
Date: "2020-06-15",
|
Date: "2020-06-15T07:00:00",
|
||||||
IsRecurring: false,
|
IsRecurring: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -306,7 +306,7 @@ func TestConvertTodoistToVikunja(t *testing.T) {
|
||||||
ID: 103006,
|
ID: 103006,
|
||||||
ItemID: 400000009,
|
ItemID: 400000009,
|
||||||
Due: &dueDate{
|
Due: &dueDate{
|
||||||
Date: "2020-06-15",
|
Date: "2020-06-15T07:00:00",
|
||||||
IsRecurring: false,
|
IsRecurring: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -350,7 +350,7 @@ func TestConvertTodoistToVikunja(t *testing.T) {
|
||||||
Created: time1,
|
Created: time1,
|
||||||
Reminders: []time.Time{
|
Reminders: []time.Time{
|
||||||
time.Date(2020, time.June, 15, 0, 0, 0, 0, time.UTC).In(config.GetTimeZone()),
|
time.Date(2020, time.June, 15, 0, 0, 0, 0, time.UTC).In(config.GetTimeZone()),
|
||||||
time.Date(2020, time.June, 16, 0, 0, 0, 0, time.UTC).In(config.GetTimeZone()),
|
time.Date(2020, time.June, 16, 7, 0, 0, 0, time.UTC).In(config.GetTimeZone()),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -364,7 +364,7 @@ func TestConvertTodoistToVikunja(t *testing.T) {
|
||||||
Done: false,
|
Done: false,
|
||||||
Created: time1,
|
Created: time1,
|
||||||
Reminders: []time.Time{
|
Reminders: []time.Time{
|
||||||
time.Date(2020, time.July, 15, 0, 0, 0, 0, time.UTC).In(config.GetTimeZone()),
|
time.Date(2020, time.July, 15, 7, 0, 0, 0, time.UTC).In(config.GetTimeZone()),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -376,7 +376,7 @@ func TestConvertTodoistToVikunja(t *testing.T) {
|
||||||
DoneAt: time3,
|
DoneAt: time3,
|
||||||
Labels: vikunjaLabels,
|
Labels: vikunjaLabels,
|
||||||
Reminders: []time.Time{
|
Reminders: []time.Time{
|
||||||
time.Date(2020, time.June, 15, 0, 0, 0, 0, time.UTC).In(config.GetTimeZone()),
|
time.Date(2020, time.June, 15, 7, 0, 0, 0, time.UTC).In(config.GetTimeZone()),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -392,7 +392,7 @@ func TestConvertTodoistToVikunja(t *testing.T) {
|
||||||
Created: time1,
|
Created: time1,
|
||||||
DoneAt: time3,
|
DoneAt: time3,
|
||||||
Reminders: []time.Time{
|
Reminders: []time.Time{
|
||||||
time.Date(2020, time.June, 15, 0, 0, 0, 0, time.UTC).In(config.GetTimeZone()),
|
time.Date(2020, time.June, 15, 7, 0, 0, 0, time.UTC).In(config.GetTimeZone()),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -470,7 +470,7 @@ func TestConvertTodoistToVikunja(t *testing.T) {
|
||||||
Done: false,
|
Done: false,
|
||||||
Created: time1,
|
Created: time1,
|
||||||
Reminders: []time.Time{
|
Reminders: []time.Time{
|
||||||
time.Date(2020, time.June, 15, 0, 0, 0, 0, time.UTC).In(config.GetTimeZone()),
|
time.Date(2020, time.June, 15, 7, 0, 0, 0, time.UTC).In(config.GetTimeZone()),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue
Block a user