Compare commits

..

17 Commits

107 changed files with 1100 additions and 4525 deletions

View File

@ -141,7 +141,7 @@ steps:
commands:
- export "GOROOT=$(go env GOROOT)"
- apk --no-cache add build-base git
- wget -O - -q https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.52.1
- wget -O - -q https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.51.0
- ./mage-static check:all
when:
event: [ push, tag, pull_request ]
@ -506,7 +506,7 @@ steps:
# Build os packages and push it to our bucket
- name: build-os-packages-unstable
image: goreleaser/nfpm:v2.27.1
image: goreleaser/nfpm:v2.25.0
pull: always
commands:
- apk add git go
@ -522,7 +522,7 @@ steps:
depends_on: [ after-build-compress ]
- name: build-os-packages-version
image: goreleaser/nfpm:v2.27.1
image: goreleaser/nfpm:v2.25.0
pull: always
commands:
- apk add git go
@ -731,6 +731,6 @@ steps:
- failure
---
kind: signature
hmac: 166caa5ba66cd55bc0f1c5cb42be0a0a647fbadf66716778cf795fc084fc80fd
hmac: 69b4c4718d673e0a42c797b4882a73968c42318b8a249a8f9fd58d1c6aa9a636
...

3
.github/FUNDING.yml vendored
View File

@ -1,3 +1,2 @@
github: kolaente
open_collective: vikunja
custom: ["https://vikunja.cloud", "https://www.buymeacoffee.com/kolaente"]
custom: https://www.buymeacoffee.com/kolaente

View File

@ -79,7 +79,6 @@ issues:
- path: pkg/routes/api/v1/docs.go
linters:
- goheader
- misspell
- text: "Missed string"
linters:
- goheader

View File

@ -7,190 +7,6 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
All releases can be found on https://code.vikunja.io/api/releases.
## [0.20.4] - 2023-03-12
### Bug Fixes
* *(docker)* Allow non-unique group id
### Documentation
* Add link to tutorial for installing Vikunja on Synology ([4de0efe](4de0efec1dd7da95dbf936728d7e23791396a63a))
## [0.20.3] - 2023-03-10
### Bug Fixes
* *(build)* Downgrade xgo to 1.19.2 so that builds work again
* *(caldav)* Add Z suffix to dates make it clear dates are in UTC
* *(caldav)* Use const for repeat modes
* *(caldav)* Make sure only labels where the user has permission to use them are used
* *(ci)* Pipeline dependency
* *(ci)* Pin nfpm container version and binary location
* *(ci)* Set release path to /source
* *(ci)* Tagging logic for release docker images
* *(ci)* Save generated .tags file to correctly tag docker releases
* *(ci)* Sign drone config
* *(docd)* Update Subdirectory Documentation (#1363)
* *(docker)* Cross compilation with buildx
* *(docker)* Re-add expose
* *(docker)* Passing environment variables into the container
* *(docker)* Make sure the vikunja user always exists and only modify the uid instead of recreating the user
* *(docs)* Add docs about cli user delete
* *(docs)* Old helm charts url (#1344)
* *(docs)* Fix a few minor typos (#59)
* *(docs)* Fix traefik v2 example (#65)
* *(docs)* Clarify support for caldav reccurrence
* *(drone)* Add type, fix pull, remove group (#1355)
* *(dump)* Make sure null dates are properly set when restoring from a dump
* *(export)* Ignore file size for export files
* *(list)* Return lists for a namespace id even if that namespace is deleted
* *(list)* When list background is removed, delete file from file system and DB (#1372)
* *(mailer)* Forcessl config (#60)
* *(migration)* Use Todoist v9 api to migrate tasks from them
* *(migration)* Import TickTick data by column name instead of index (#1356)
* *(migration)* Use the proper authorization method for Todoist's api, fix issues with importing deleted items
* *(migration)* Remove unused todoist parameters
* *(migration)* Todoist pagination now avoids too many loops
* *(migration)* Don't try to add nonexistent tasks as related
* *(migration)* Make sure trello checklists are properly imported
* *(reminders)* Overdue tasks join condition
* *(reminders)* Make sure an overdue reminder is sent when there is only one overdue task
* *(reminders)* Prevent duplicate reminders when updating task details
* *(restore)* Check if we're really dealing with a string
* *(task)* Make sure the task's last updated timestamp is always updated when releated entities changed
* *(task)* Correctly load tasks by id and uuid in caldav
* *(tasks)* Don't include undone overdue tasks from archived lists or namespaces in notification mails
* *(tasks)* Don't reset the kanban bucket when updating a task and not providing one
* *(tasks)* Don't set a repeating task done when moving it do the done bucket
* *(tasks)* Recalculate position of all tasks in a list or bucket when it would hit 0
* *(tasks)* Make sure tasks are sorted by position before recalculating them
* *(user)* Make reset the user's name to empty actually work
* Swagger docs ([96b5e93](96b5e933796275e87f3007e31db0623688dbdb3a))
* Restore notifications table from dump when it already had the correct format ([8c67be5](8c67be558f697ab52740c51ab453092c0f8f7c14))
* Make sure labels are always exported as caldav (#1412) ([1afc72e](1afc72e1906c02b093bb6d9748235b93ab0eb181))
* Lint ([491a142](491a1423788b76f236d070071cb46f5b2f5d3fd0))
* Lint ([20a5994](20a5994b1717e7751750f14a9a164825a8e6ade6))
* Lint ([077baba](077baba2eaff2f10b97384f07375ece7f51ec0fa))
* Lint ([9f14466](9f14466dfa8660362a4e51b3c8c6810bf8d66a22))
### Dependencies
* *(deps)* Update module github.com/yuin/goldmark to v1.5.3 (#1317)
* *(deps)* Update module golang.org/x/crypto to v0.2.0 (#1315)
* *(deps)* Update module github.com/spf13/afero to v1.9.3 (#1320)
* *(deps)* Update module golang.org/x/crypto to v0.3.0 (#1321)
* *(deps)* Update github.com/arran4/golang-ical digest to a677353 (#1323)
* *(deps)* Update module github.com/wneessen/go-mail to v0.3.5 (#1325)
* *(deps)* Update github.com/arran4/golang-ical digest to 1093469 (#1326)
* *(deps)* Update module github.com/golang-jwt/jwt/v4 to v4.4.3 (#1328)
* *(deps)* Update module github.com/go-sql-driver/mysql to v1.7.0 (#1332)
* *(deps)* Update module golang.org/x/sys to v0.3.0 (#1333)
* *(deps)* Update module golang.org/x/term to v0.3.0 (#1336)
* *(deps)* Update module golang.org/x/image to v0.2.0 (#1335)
* *(deps)* Update module golang.org/x/oauth2 to v0.2.0 (#1316)
* *(deps)* Update module golang.org/x/oauth2 to v0.3.0 (#1337)
* *(deps)* Update module github.com/getsentry/sentry-go to v0.16.0 (#1338)
* *(deps)* Update module golang.org/x/crypto to v0.4.0 (#1339)
* *(deps)* Update module github.com/pquerna/otp to v1.4.0 (#1341)
* *(deps)* Update module github.com/swaggo/swag to v1.8.9 (#1327)
* *(deps)* Update module github.com/wneessen/go-mail to v0.3.6 (#1342)
* *(deps)* Update module github.com/labstack/echo/v4 to v4.10.0 (#1343)
* *(deps)* Update module github.com/wneessen/go-mail to v0.3.7 (#1348)
* *(deps)* Update module github.com/coreos/go-oidc/v3 to v3.5.0 (#1349)
* *(deps)* Update module golang.org/x/sys to v0.4.0 (#1351)
* *(deps)* Update module golang.org/x/image to v0.3.0 (#1350)
* *(deps)* Update module golang.org/x/term to v0.4.0 (#1352)
* *(deps)* Update module golang.org/x/crypto to v0.5.0 (#1353)
* *(deps)* Update goreleaser/nfpm docker tag to v2.23.0 (#1347)
* *(deps)* Update module github.com/wneessen/go-mail to v0.3.8 (#1357)
* *(deps)* Update module src.techknowlogick.com/xgo to v1.6.0+1.19.5 (#1358)
* *(deps)* Update klakegg/hugo docker tag to v0.107.0 (#1272)
* *(deps)* Update module github.com/getsentry/sentry-go to v0.17.0 (#1361)
* *(deps)* Update module src.techknowlogick.com/xgo to v1.7.0+1.19.5 (#1364)
* *(deps)* Update module github.com/spf13/viper to v1.15.0 (#1365)
* *(deps)* Update module github.com/labstack/echo-jwt/v4 to v4.0.1 (#1369)
* *(deps)* Update module golang.org/x/oauth2 to v0.4.0 (#1354)
* *(deps)* Update github.com/gocarina/gocsv digest to 763e25b (#1370)
* *(deps)* Update goreleaser/nfpm docker tag to v2.24.0 (#1367)
* *(deps)* Update module github.com/swaggo/swag to v1.8.10 (#1371)
* *(deps)* Update module github.com/go-redis/redis/v8 to v9 (#1377)
* *(deps)* Update module github.com/labstack/echo-jwt/v4 to v4.1.0
* *(deps)* Update module github.com/ulule/limiter/v3 to v3.11.0 (#1378)
* *(deps)* Update module github.com/redis/go-redis/v9 to v9.0.2
* *(deps)* Update goreleaser/nfpm docker tag to v2.25.0 (#1382)
* *(deps)* Upgrade golangci-lint to 1.51.0
* *(deps)* Update module github.com/yuin/goldmark to v1.5.4
* *(deps)* Update module go to 1.20
* *(deps)* Update xgo to 1.20
* *(deps)* Update module golang.org/x/sys to v0.5.0
* *(deps)* Update module github.com/getsentry/sentry-go to v0.18.0 (#1386)
* *(deps)* Update module golang.org/x/term to v0.5.0
* *(deps)* Update module golang.org/x/crypto to v0.6.0
* *(deps)* Update module golang.org/x/oauth2 to v0.5.0
* *(deps)* Update module golang.org/x/image to v0.4.0
* *(deps)* Update goreleaser/nfpm docker tag to v2.26.0 (#1394)
* *(deps)* Update github.com/arran4/golang-ical digest to 07c6aad
* *(deps)* Update module github.com/threedotslabs/watermill to v1.2.0 (#1384)
* *(deps)* Update module golang.org/x/image to v0.5.0 (#1396)
* *(deps)* Update golang.org/x/net to 0.7.0
* *(deps)* Update module github.com/golang-jwt/jwt/v4 to v4.5.0 (#1399)
* *(deps)* Update github.com/gocarina/gocsv digest to bcce7dc
* *(deps)* Update golangci-lint to 1.51.2
* *(deps)* Update module github.com/labstack/echo/v4 to v4.10.1
* *(deps)* Update github.com/gocarina/gocsv digest to bee85ea
* *(deps)* Update module github.com/labstack/echo/v4 to v4.10.2
* *(deps)* Update module github.com/spf13/afero to v1.9.4
* *(deps)* Update github.com/gocarina/gocsv digest to dc4ee9d
* *(deps)* Update module github.com/stretchr/testify to v1.8.2
* *(deps)* Update github.com/gocarina/gocsv digest to 70c27cb
* *(deps)* Update module golang.org/x/sys to v0.6.0
* *(deps)* Update module golang.org/x/term to v0.6.0
* *(deps)* Update module golang.org/x/crypto to v0.7.0
* *(deps)* Update module golang.org/x/oauth2 to v0.6.0
* *(deps)* Update module golang.org/x/image to v0.6.0
* *(deps)* Update github.com/kolaente/caldav-go digest to 2a4eb8b
* *(deps)* Remove fsnotify replacement
* *(deps)* Update github.com/vectordotdev/go-datemath digest to f3954d0
* *(deps)* Update src.techknowlogick.com/xgo digest to 44f7e66
* *(deps)* Update module github.com/getsentry/sentry-go to v0.19.0
* *(deps)* Update module github.com/spf13/afero to v1.9.5
* *(deps)* Update module github.com/ulule/limiter/v3 to v3.11.1
* *(deps)* Update src.techknowlogick.com/xgo digest to b607086
* *(deps)* Update module github.com/gabriel-vasile/mimetype to v1.4.2
### Features
* *(background)* Add Last-Modified header (#1376)
* *(caldav)* Add support for repeating tasks
* *(caldav)* Export Labels to Caldav (#1409)
* *(caldav)* Import caldav categories as Labels (#1413)
* *(migrators)* Remove wunderlist (#1346)
* *(release)* Use compressed binaries for package releases
* Use docker buildx to build multiarch images ([a6e214b](a6e214b654f28836cc8b93683dbfd5999282d11c))
* Provide logout url for openid providers (#1340) ([a79b1de](a79b1de2d0247a424f49cecaa267d30e8fa70a83))
* Refactored Dockerfile (#1375) ([522bf7d](522bf7d2fc3cc1704f58299b6435baccc7add533))
* Disable events log by default ([da9d25c](da9d25cf727c56acd7394b4b74e17a2959ee5242))
- **BREAKING**: events log level is now off unless explicitly enabled
### Miscellaneous Tasks
* *(docs)* Adjust docs about frontend docker container
* *(docs)* Remove sponsors
* *(task)* Add test to check if a task's reminders are duplicated
* Remove custom gitea bug template in favor of githubs ([4fa45bf](4fa45bf9dcbaa8a41a53fc2305c4c2c1aa15691c))
* 0.20.2 release preperations ([d19fc80](d19fc80b8be08673136d84e10187cadb293822bf))
* Update funding links ([aa25ccd](aa25ccdc917684583a9bff4b7cb272004386f0fa))
### Other
* *(other)* Added Google & Google Workspace to OpenId examples (#1319)
## [0.20.2] - 2023-01-24
### Bug Fixes

View File

@ -3,7 +3,7 @@
# │─││ │││ │ │
# ┘─┘┘─┘┘┘─┘┘─┘
FROM --platform=$BUILDPLATFORM techknowlogick/xgo:go-1.20.x AS builder
FROM --platform=$BUILDPLATFORM techknowlogick/xgo:go-1.19.2 AS builder
RUN go install github.com/magefile/mage@latest && \
mv /go/bin/mage /usr/local/go/bin
@ -33,9 +33,7 @@ ENV VIKUNJA_SERVICE_ROOTPATH=/app/vikunja/
ENV PUID 1000
ENV PGID 1000
RUN apk --update --no-cache add tzdata tini shadow && \
addgroup vikunja && \
adduser -s /bin/sh -D -G vikunja vikunja -h /app/vikunja -H
RUN apk --update --no-cache add tzdata tini
COPY docker/entrypoint.sh /entrypoint.sh
RUN chmod 0755 /entrypoint.sh && mkdir files

View File

@ -2,7 +2,7 @@
[![Build Status](https://drone.kolaente.de/api/badges/vikunja/api/status.svg)](https://drone.kolaente.de/vikunja/api)
[![License: AGPL v3](https://img.shields.io/badge/License-AGPL%20v3-blue.svg)](LICENSE)
[![Download](https://img.shields.io/badge/download-v0.20.4-brightgreen.svg)](https://dl.vikunja.io)
[![Download](https://img.shields.io/badge/download-v0.20.2-brightgreen.svg)](https://dl.vikunja.io)
[![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)
[![Go Report Card](https://goreportcard.com/badge/kolaente.dev/vikunja/api)](https://goreportcard.com/report/kolaente.dev/vikunja/api)
@ -56,6 +56,10 @@ See [the roadmap](https://my.vikunja.cloud/share/QFyzYEmEYfSyQfTOmIRSwLUpkFjboaB
Fork -> Push -> Pull-Request. Also see the [dev docs](https://vikunja.io/docs/development/) for more info.
## Sponsors
[![Relm](https://vikunja.io/images/sponsors/relm.png)](https://relm.us)
## License
This project is licensed under the AGPLv3 License. See the [LICENSE](LICENSE) file for the full license text.

View File

@ -30,7 +30,7 @@ service:
enablecaldav: true
# Set the motd message, available from the /info endpoint
motd: ""
# Enable sharing of project via a link
# Enable sharing of lists via a link
enablelinksharing: true
# Whether to let new users registering themselves or not
enableregistration: true
@ -165,13 +165,9 @@ log:
# Echo has its own logging which usually is unnecessary, which is why it is disabled by default. Possible values are stdout, stderr, file or off to disable standard logging.
echo: "off"
# Whether or not to log events. Useful for debugging. Possible values are stdout, stderr, file or off to disable events logging.
events: "off"
events: "stdout"
# The log level for event log messages. Possible values (case-insensitive) are ERROR, INFO, DEBUG.
eventslevel: "info"
# Whether or not to log mail log messages. This will not log mail contents. Possible values are stdout, stderr, file or off to disable mail-related logging.
mail: "off"
# The log level for mail log messages. Possible values (case-insensitive) are ERROR, WARNING, INFO, DEBUG.
maillevel: "info"
ratelimit:
# whether or not to enable the rate limit
@ -242,14 +238,14 @@ avatar:
gravatarexpiration: 3600
backgrounds:
# Whether to enable backgrounds for projects at all.
# Whether to enable backgrounds for lists at all.
enabled: true
providers:
upload:
# Whether to enable uploaded project backgrounds
# Whethere to enable uploaded list backgrounds
enabled: true
unsplash:
# Whether to enable setting backgrounds from unsplash as project backgrounds
# Whether to enable setting backgrounds from unsplash as list backgrounds
enabled: false
# You need to create an application for your installation at https://unsplash.com/oauth/applications/new
# and set the access token below.
@ -333,8 +329,8 @@ defaultsettings:
overdue_tasks_reminders_enabled: true
# When to send the overdue task reminder email.
overdue_tasks_reminders_time: 9:00
# The id of the default project. Make sure users actually have access to this project when setting this value.
default_project_id: 0
# The id of the default list. Make sure users actually have access to this list when setting this value.
default_list_id: 0
# Start of the week for the user. `0` is sunday, `1` is monday and so on.
week_start: 0
# The language of the user interface. Must be an ISO 639-1 language code. Will default to the browser language the user uses when signing up.

View File

@ -4,10 +4,9 @@ set -e
if [ -n "$PUID" ] && [ "$PUID" -ne 0 ] && \
[ -n "$PGID" ] && [ "$PGID" -ne 0 ] ; then
echo "info: creating the new user vikunja with $PUID:$PGID"
groupmod -g "$PGID" -o vikunja
usermod -u "$PUID" -o vikunja
chown -R vikunja:vikunja ./files/
chown vikunja:vikunja .
addgroup -g "$PGID" vikunja
adduser -s /bin/sh -D -G vikunja -u "$PUID" vikunja -h /app/vikunja -H
chown -R vikunja:vikunja ./
exec su vikunja -c /app/vikunja/vikunja "$@"
else
echo "info: creation of non-root user is skipped"

View File

@ -28,7 +28,7 @@ markup:
menu:
page:
- name: Home
url: https://vikunja.io/
url: https://vikunja.io/en/
weight: 10
- name: Features
url: https://vikunja.io/features
@ -36,9 +36,6 @@ menu:
- name: Download
url: https://vikunja.io/download
weight: 30
- name: Blog
url: https://vikunja.io/blog/
weight: 35
- name: Docs
url: https://vikunja.io/docs
weight: 40
@ -48,9 +45,6 @@ menu:
- name: Community
url: https://community.vikunja.io/
weight: 60
- name: Stickers
url: https://vikunja.cloud/stickers?utm_source=io&utm_medium=io&utm_campaign=menu
weight: 65
- name: Get it Hosted
url: https://vikunja.cloud/?utm_source=io&utm_medium=io&utm_campaign=menu
weight: 70

View File

@ -49,7 +49,7 @@ func (err ErrUserDoesNotExist) Error() string {
// This needs to be unique, so you should check whether the error code exists or not.
// The general convention for error codes is as follows:
// * Every "group" errors lives in a thousend something. For example all user issues are 1000-something, all
// project errors are 3000-something and so on.
// list errors are 3000-something and so on.
// * New error codes should be the current max error code + 1. Don't take free numbers to prevent old errors
// which are depricated and removed from being "new ones". For example, if there are error codes 1001, 1002, 1004,
// a new error should be 1005 and not 1003.

View File

@ -26,8 +26,8 @@ It returns the `limit` (max-length) and `offset` parameters needed for SQL-Queri
You can feed this function directly into xorm's `Limit`-Function like so:
{{< highlight golang >}}
projects := []*Project{}
err := x.Limit(getLimitFromPageIndex(pageIndex, itemsPerPage)).Find(&projects)
lists := []List{}
err := x.Limit(getLimitFromPageIndex(pageIndex, itemsPerPage)).Find(&lists)
{{< /highlight >}}
// TODO: Add a full example from start to finish, like a tutorial on how to create a new endpoint?

View File

@ -20,8 +20,8 @@ There are two ways of migrating data from another service:
1. Through the auth-based flow where the user gives you access to their data at the third-party service through an
oauth flow. You can then call the service's api on behalf of your user to get all the data.
The Todoist, Trello and Microsoft To-Do Migrators use this pattern.
2. A file migration where the user uploads a file obtained from some third-party service. In your migrator, you need
to parse the file and create the projects, tasks etc.
2. A file migration where the user uploads a file obtained from some third-party service. In your migrator, you need
to parse the file and create the lists, tasks etc.
The Vikunja File Import uses this pattern.
To differentiate the two, there are two different interfaces you must implement.
@ -61,7 +61,7 @@ type FileMigrator interface {
// Name holds the name of the migration.
// This is used to show the name to users and to keep track of users who already migrated.
Name() string
// Migrate is the interface used to migrate a user's tasks, projects and other things from a file to vikunja.
// Migrate is the interface used to migrate a user's tasks, list and other things from a file to vikunja.
// The user object is the user who's tasks will be migrated.
Migrate(user *user.User, file io.ReaderAt, size int64) error
}
@ -103,9 +103,9 @@ You should also document the routes with [swagger annotations]({{< ref "swagger-
## Insertion helper method
There is a method available in the `migration` package which takes a fully nested Vikunja structure and creates it with all relations.
This means you start by adding a namespace, then add projects inside that namespace, then tasks in the lists and so on.
This means you start by adding a namespace, then add lists inside of that namespace, then tasks in the lists and so on.
The root structure must be present as `[]*models.NamespaceWithProjectsAndTasks`. It allows to represent all of Vikunja's
The root structure must be present as `[]*models.NamespaceWithListsAndTasks`. It allows to represent all of Vikunja's
hierachie as a single data structure.
Then call the method like so:

View File

@ -108,7 +108,7 @@ Contains all possible avatar providers a user can choose to set their avatar.
#### background
All project background providers are in sub-packages of this package.
All list background providers are in sub-packages of this package.
#### dump

View File

@ -19,51 +19,29 @@ The api documentation is generated using [swaggo](https://github.com/swaggo/swag
You should always comment every field which will be exposed as a json in the api.
These comments will show up in the documentation, it'll make it easier for developers using the api.
As an example, this is the definition of a project with all comments:
As an example, this is the definition of a list with all comments:
{{< highlight golang >}}
type Project struct {
// The unique, numeric id of this project.
ID int64 `xorm:"bigint autoincr not null unique pk" json:"id" param:"project"`
// The title of the project. You'll see this in the namespace overview.
Title string `xorm:"varchar(250) not null" json:"title" valid:"required,runelength(1|250)" minLength:"1" maxLength:"250"`
// The description of the project.
Description string `xorm:"longtext null" json:"description"`
// The unique project short identifier. Used to build task identifiers.
Identifier string `xorm:"varchar(10) null" json:"identifier" valid:"runelength(0|10)" minLength:"0" maxLength:"10"`
// The hex color of this project
HexColor string `xorm:"varchar(6) null" json:"hex_color" valid:"runelength(0|6)" maxLength:"6"`
// List represents a list of tasks
type List struct {
// The unique, numeric id of this list.
ID int64 `xorm:"bigint autoincr not null unique pk" json:"id" param:"list"`
// The title of the list. You'll see this in the namespace overview.
Title string `xorm:"varchar(250)" json:"title" valid:"required,runelength(3|250)" minLength:"3" maxLength:"250"`
// The description of the list.
Description string `xorm:"varchar(1000)" json:"description" valid:"runelength(0|1000)" maxLength:"1000"`
OwnerID int64 `xorm:"bigint INDEX" json:"-"`
NamespaceID int64 `xorm:"bigint INDEX" json:"-" param:"namespace"`
OwnerID int64 `xorm:"bigint INDEX not null" json:"-"`
NamespaceID int64 `xorm:"bigint INDEX not null" json:"namespace_id" param:"namespace"`
// The user who created this list.
Owner User `xorm:"-" json:"owner" valid:"-"`
// An array of tasks which belong to the list.
Tasks []*ListTask `xorm:"-" json:"tasks"`
// The user who created this project.
Owner *user.User `xorm:"-" json:"owner" valid:"-"`
// Whether or not a project is archived.
IsArchived bool `xorm:"not null default false" json:"is_archived" query:"is_archived"`
// The id of the file this project has set as background
BackgroundFileID int64 `xorm:"null" json:"-"`
// Holds extra information about the background set since some background providers require attribution or similar. If not null, the background can be accessed at /projects/{projectID}/background
BackgroundInformation interface{} `xorm:"-" json:"background_information"`
// Contains a very small version of the project background to use as a blurry preview until the actual background is loaded. Check out https://blurha.sh/ to learn how it works.
BackgroundBlurHash string `xorm:"varchar(50) null" json:"background_blur_hash"`
// True if a project is a favorite. Favorite projects show up in a separate namespace. This value depends on the user making the call to the api.
IsFavorite bool `xorm:"-" json:"is_favorite"`
// The subscription status for the user reading this project. You can only read this property, use the subscription endpoints to modify it.
// Will only returned when retreiving one project.
Subscription *Subscription `xorm:"-" json:"subscription,omitempty"`
// The position this project has when querying all projects. See the tasks.position property on how to use this.
Position float64 `xorm:"double null" json:"position"`
// A timestamp when this project was created. You cannot change this value.
Created time.Time `xorm:"created not null" json:"created"`
// A timestamp when this project was last updated. You cannot change this value.
Updated time.Time `xorm:"updated not null" json:"updated"`
// A unix timestamp when this list was created. You cannot change this value.
Created int64 `xorm:"created" json:"created"`
// A unix timestamp when this list was last updated. You cannot change this value.
Updated int64 `xorm:"updated" json:"updated"`
web.CRUDable `xorm:"-" json:"-"`
web.Rights `xorm:"-" json:"-"`

View File

@ -208,7 +208,7 @@ Environment path: `VIKUNJA_SERVICE_MOTD`
### enablelinksharing
Enable sharing of project via a link
Enable sharing of lists via a link
Default: `true`
@ -853,7 +853,7 @@ Environment path: `VIKUNJA_LOG_ECHO`
Whether or not to log events. Useful for debugging. Possible values are stdout, stderr, file or off to disable events logging.
Default: `off`
Default: `stdout`
Full path: `log.events`
@ -871,28 +871,6 @@ Full path: `log.eventslevel`
Environment path: `VIKUNJA_LOG_EVENTSLEVEL`
### mail
Whether or not to log mail log messages. This will not log mail contents. Possible values are stdout, stderr, file or off to disable mail-related logging.
Default: `off`
Full path: `log.mail`
Environment path: `VIKUNJA_LOG_MAIL`
### maillevel
The log level for mail log messages. Possible values (case-insensitive) are ERROR, WARNING, INFO, DEBUG.
Default: `info`
Full path: `log.maillevel`
Environment path: `VIKUNJA_LOG_MAILLEVEL`
---
## ratelimit
@ -1043,7 +1021,7 @@ Environment path: `VIKUNJA_AVATAR_GRAVATAREXPIRATION`
### enabled
Whether to enable backgrounds for projects at all.
Whether to enable backgrounds for lists at all.
Default: `true`
@ -1270,15 +1248,15 @@ Full path: `defaultsettings.overdue_tasks_reminders_time`
Environment path: `VIKUNJA_DEFAULTSETTINGS_OVERDUE_TASKS_REMINDERS_TIME`
### default_project_id
### default_list_id
The id of the default project. Make sure users actually have access to this project when setting this value.
The id of the default list. Make sure users actually have access to this list when setting this value.
Default: `0`
Full path: `defaultsettings.default_project_id`
Full path: `defaultsettings.default_list_id`
Environment path: `VIKUNJA_DEFAULTSETTINGS_DEFAULT_PROJECT_ID`
Environment path: `VIKUNJA_DEFAULTSETTINGS_DEFAULT_LIST_ID`
### week_start

View File

@ -76,7 +76,7 @@ This defines four services, each with their own container:
* An api service which runs the vikunja api. Most of the core logic lives here.
* The frontend which will make vikunja actually usable for most people.
* A database container which will store all projects, tasks, etc. We're using mariadb here, but you're free to use mysql or postgres if you want.
* A database container which will store all lists, tasks, etc. We're using mariadb here, but you're free to use mysql or postgres if you want.
* A proxy service which makes the frontend and api available on the same port, redirecting all requests to `/api` to the api container.
If you already have a proxy on your host, you may want to check out the [reverse proxy examples]() to use that.
By default, it uses port 80 on the host.
@ -201,6 +201,7 @@ You should see something like this:
"max_file_size": "20MB",
"registration_enabled": true,
"available_migrators": [
"wunderlist",
"todoist"
],
"task_attachments_enabled": true

View File

@ -56,4 +56,3 @@ A third-party Helm Chart is available from the k8s-at-home project [here](https:
* [Install Vikunja in Docker for self-hosted Task Tracking](https://smarthomepursuits.com/install-vikunja-in-docker-for-self-hosted-task-tracking/)
* [Self-Hosted To-Do List with Vikunja in Docker](https://www.youtube.com/watch?v=DqyqDWpEvKI) (Youtube)
* [Vikunja self-hosted (step by step)](https://nguyenminhhung.com/vikunja-self-hosted-step-by-step/)
* [How to Install Vikunja on Your Synology NAS](https://mariushosting.com/how-to-install-vikunja-on-your-synology-nas/)

View File

@ -17,27 +17,15 @@ However, you can still run it in a subdirectory but need to build the frontend y
First, make sure you're able to build the frontend from source.
Check [the guide about building from source]({{< ref "build-from-source.md">}}#frontend) about that.
### Dynamicly set with build command
Run the build with the `VIKUNJA_FRONTEND_BASE` variable specified.
Then, run
```
VIKUNJA_FRONTEND_BASE=/SUBPATH/ pnpm run build
pnpm vite build --base=/SUBPATH
pnpm workbox copyLibraries dist/
```
Where `SUBPATH` is the subdirectory you want to run Vikunja on.
### Set via .env.local
* Copy `.env.local.example` to `.env.local`
* Uncomment `VIKUNJA_FRONTEND_BASE` and set `/subpath/` to the desired path.
After saving, build Vikunja as normal.
```
pnpm run build
```
Once you have the build files you can deploy them as usual.
Note that when deploying in docker you'll need to put the files in a web container yourself, you
can't use the `Dockerfile` in the repo without modifications.

View File

@ -12,7 +12,7 @@ menu:
Vikunja itself is always fully capable of handling utf-8 characters.
However, your database might be not.
Vikunja itself will work just fine until you want to use non-latin characters in your tasks/projects/etc.
Vikunja itself will work just fine until you want to use non-latin characters in your tasks/lists/etc.
On this page, you will find information about how to fully ensure non-latin characters like aüäß or emojis work
with your installation.

View File

@ -10,9 +10,9 @@ menu:
# Caldav
> **Warning:** The caldav integration is in an early alpha stage and has bugs.
> **Warning:** The caldav integration is in an early alpha stage and has bugs.
> It works well with some clients while having issues with others.
> If you encounter issues, please [report them](https://code.vikunja.io/api/issues/new?body=[caldav])
> If you encounter issues, please [report them](https://code.vikunja.io/api/issues/new?body=[caldav])
Vikunja supports managing tasks via the [caldav VTODO](https://tools.ietf.org/html/rfc5545#section-3.6.2) extension.
@ -24,10 +24,10 @@ All urls are located under the `/dav` subspace.
Urls are:
* `/principals/<username>/`: Returns urls for project discovery. *Use this url to initially make connections to new clients.*
* `/projects/`: Used to manage projects
* `/projects/<List ID>/`: Used to manage a single project
* `/projects/<List ID>/<Task UID>`: Used to manage a task on a project
* `/principals/<username>/`: Returns urls for list discovery. *Use this url to initially make connections to new clients.*
* `/lists/`: Used to manage lists
* `/lists/<List ID>/`: Used to manage a single list
* `/lists/<List ID>/<Task UID>`: Used to manage a task on a list
## Supported properties
@ -37,33 +37,32 @@ Vikunja currently supports the following properties:
* `SUMMARY`
* `DESCRIPTION`
* `PRIORITY`
* `CATEGORIES`
* `COMPLETED`
* `CREATED` (only Vikunja -> Client)
* `DUE`
* `DURATION`
* `DTSTAMP`
* `DTSTART`
* `LAST-MODIFIED` (only Vikunja -> Client)
* `RRULE` (Recurrence) (only Vikunja -> Client)
* `VALARM` (Reminders)
* `DURATION`
* `ORGANIZER`
* `RELATED-TO`
* `CREATED`
* `DTSTAMP`
* `LAST-MODIFIED`
Vikunja **currently does not** support these properties:
* `ATTACH`
* `CATEGORIES`
* `CLASS`
* `COMMENT`
* `CONTACT`
* `GEO`
* `LOCATION`
* `ORGANIZER` (disabled)
* `PERCENT-COMPLETE`
* `RECURRENCE-ID`
* `RELATED-TO`
* `RESOURCES`
* `SEQUENCE`
* `STATUS`
* `CONTACT`
* `RECURRENCE-ID`
* `URL`
* Recurrence
* `SEQUENCE`
## Tested Clients

View File

@ -24,26 +24,24 @@ This document describes the different errors Vikunja can return.
| ErrorCode | HTTP Status Code | Description |
|-----------|------------------|-------------|
| 1001 | 400 | A user with this username already exists. |
| 1002 | 400 | A user with this email address already exists. |
| 1004 | 400 | No username and password specified. |
| 1005 | 404 | The user does not exist. |
| 1006 | 400 | Could not get the user id. |
| 1008 | 412 | No password reset token provided. |
| 1009 | 412 | Invalid password reset token. |
| 1010 | 412 | Invalid email confirm token. |
| 1011 | 412 | Wrong username or password. |
| 1012 | 412 | Email address of the user not confirmed. |
| 1013 | 412 | New password is empty. |
| 1014 | 412 | Old password is empty. |
| 1015 | 412 | Totp is already enabled for this user. |
| 1016 | 412 | Totp is not enabled for this user. |
| 1017 | 412 | The provided Totp passcode is invalid. |
| 1018 | 412 | The provided user avatar provider type setting is invalid. |
| 1019 | 412 | No openid email address was provided. |
| 1020 | 412 | This user account is disabled. |
| 1021 | 412 | This account is managed by a third-party authentication provider. |
| 1021 | 412 | The username must not contain spaces. |
| 1001 | 400 | A user with this username already exists. |
| 1002 | 400 | A user with this email address already exists. |
| 1004 | 400 | No username and password specified. |
| 1005 | 404 | The user does not exist. |
| 1006 | 400 | Could not get the user id. |
| 1008 | 412 | No password reset token provided. |
| 1009 | 412 | Invalid password reset token. |
| 1010 | 412 | Invalid email confirm token. |
| 1011 | 412 | Wrong username or password. |
| 1012 | 412 | Email address of the user not confirmed. |
| 1013 | 412 | New password is empty. |
| 1014 | 412 | Old password is empty. |
| 1015 | 412 | Totp is already enabled for this user. |
| 1016 | 412 | Totp is not enabled for this user. |
| 1017 | 412 | The provided Totp passcode is invalid. |
| 1018 | 412 | The provided user avatar provider type setting is invalid. |
| 1019 | 412 | No openid email address was provided. |
| 1020 | 412 | This user account is disabled. |
## Validation
@ -52,26 +50,26 @@ This document describes the different errors Vikunja can return.
| 2001 | 400 | ID cannot be empty or 0. |
| 2002 | 400 | Some of the request data was invalid. The response contains an aditional array with all invalid fields. |
## Project
## List
| ErrorCode | HTTP Status Code | Description |
|-----------|------------------|-------------------------------------------------------------------------------------------------------------------------------|
| 3001 | 404 | The project does not exist. |
| 3004 | 403 | The user needs to have read permissions on that project to perform that action. |
| 3005 | 400 | The project title cannot be empty. |
| 3006 | 404 | The project share does not exist. |
| 3007 | 400 | A project with this identifier already exists. |
| 3008 | 412 | The project is archived and can therefore only be accessed read only. This is also true for all tasks associated with this project. |
| 3009 | 412 | The project cannot belong to a dynamically generated namespace like "Favorites". |
| 3010 | 412 | The project must belong to a namespace. |
| 3001 | 404 | The list does not exist. |
| 3004 | 403 | The user needs to have read permissions on that list to perform that action. |
| 3005 | 400 | The list title cannot be empty. |
| 3006 | 404 | The list share does not exist. |
| 3007 | 400 | A list with this identifier already exists. |
| 3008 | 412 | The list is archived and can therefore only be accessed read only. This is also true for all tasks associated with this list. |
| 3009 | 412 | The list cannot belong to a dynamically generated namespace like "Favorites". |
| 3010 | 412 | The list must belong to a namespace. |
## Task
| ErrorCode | HTTP Status Code | Description |
|-----------|------------------|-------------|
| 4001 | 400 | The project task text cannot be empty. |
| 4002 | 404 | The project task does not exist. |
| 4003 | 403 | All bulk editing tasks must belong to the same project. |
| 4001 | 400 | The list task text cannot be empty. |
| 4002 | 404 | The list task does not exist. |
| 4003 | 403 | All bulk editing tasks must belong to the same list. |
| 4004 | 403 | Need at least one task when bulk editing tasks. |
| 4005 | 403 | The user does not have the right to see the task. |
| 4006 | 403 | The user tried to set a parent task as the task itself. |
@ -90,7 +88,6 @@ This document describes the different errors Vikunja can return.
| 4019 | 400 | Invalid task filter value. |
| 4020 | 400 | The provided attachment does not belong to that task. |
| 4021 | 400 | This user is already assigned to that task. |
| 4022 | 400 | The task has a relative reminder which does not specify relative to what. |
## Namespace
@ -110,17 +107,17 @@ This document describes the different errors Vikunja can return.
|-----------|------------------|-------------|
| 6001 | 400 | The team name cannot be emtpy. |
| 6002 | 404 | The team does not exist. |
| 6004 | 409 | The team already has access to that namespace or project. |
| 6004 | 409 | The team already has access to that namespace or list. |
| 6005 | 409 | The user is already a member of that team. |
| 6006 | 400 | Cannot delete the last team member. |
| 6007 | 403 | The team does not have access to the project to perform that action. |
| 6007 | 403 | The team does not have access to the list to perform that action. |
## User Project Access
## User List Access
| ErrorCode | HTTP Status Code | Description |
|-----------|------------------|-------------|
| 7002 | 409 | The user already has access to that project. |
| 7003 | 403 | The user does not have access to that project. |
| 7002 | 409 | The user already has access to that list. |
| 7003 | 403 | The user does not have access to that list. |
## Label
@ -141,10 +138,10 @@ This document describes the different errors Vikunja can return.
| ErrorCode | HTTP Status Code | Description |
|-----------|------------------|-------------|
| 10001 | 404 | The bucket does not exist. |
| 10002 | 400 | The bucket does not belong to that project. |
| 10003 | 412 | You cannot remove the last bucket on a project. |
| 10002 | 400 | The bucket does not belong to that list. |
| 10003 | 412 | You cannot remove the last bucket on a list. |
| 10004 | 412 | You cannot add the task to this bucket as it already exceeded the limit of tasks it can hold. |
| 10005 | 412 | There can be only one done bucket per project. |
| 10005 | 412 | There can be only one done bucket per list. |
## Saved Filters

View File

@ -8,20 +8,20 @@ menu:
parent: "usage"
---
# Project and namespace rights for teams and users
# List and namespace rights for teams and users
Whenever you share a project or namespace with a user or team, you can specify a `rights` parameter.
Whenever you share a list or namespace with a user or team, you can specify a `rights` parameter.
This parameter controls the rights that team or user is going to have (or has, if you request the current sharing status).
Rights are being specified using integers.
The following values are possible:
| Right (int) | Meaning |
|-------------|---------------------------------------------------------------------------------------------------------------|
| 0 (Default) | Read only. Anything which is shared with this right cannot be edited. |
| 1 | Read and write. Namespaces or projects shared with this right can be read and written to by the team or user. |
| 2 | Admin. Can do anything like read and write, but can additionally manage sharing options. |
| Right (int) | Meaning |
|-------------|---------|
| 0 (Default) | Read only. Anything which is shared with this right cannot be edited. |
| 1 | Read and write. Namespaces or lists shared with this right can be read and written to by the team or user. |
| 2 | Admin. Can do anything like read and write, but can additionally manage sharing options. |
## Team admins

77
go.mod
View File

@ -19,9 +19,9 @@ module code.vikunja.io/api
require (
code.vikunja.io/web v0.0.0-20210706160506-d85def955bd3
gitea.com/xorm/xorm-redis-cache v0.2.0
github.com/ThreeDotsLabs/watermill v1.2.0
github.com/ThreeDotsLabs/watermill v1.1.1
github.com/adlio/trello v1.10.0
github.com/arran4/golang-ical v0.0.0-20230318005454-19abf92700cc
github.com/arran4/golang-ical v0.0.0-20221122102835-109346913e54
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef
github.com/bbrks/go-blurhash v1.1.1
github.com/c2h5oh/datasize v0.0.0-20220606134207-859f65c6625b
@ -30,19 +30,19 @@ require (
github.com/d4l3k/messagediff v1.2.1
github.com/disintegration/imaging v1.6.2
github.com/dustinkirkland/golang-petname v0.0.0-20191129215211-8e5a1ed0cff0
github.com/gabriel-vasile/mimetype v1.4.2
github.com/getsentry/sentry-go v0.20.0
github.com/gabriel-vasile/mimetype v1.4.1
github.com/getsentry/sentry-go v0.17.0
github.com/go-sql-driver/mysql v1.7.0
github.com/go-testfixtures/testfixtures/v3 v3.8.1
github.com/gocarina/gocsv v0.0.0-20230325173030-9a18a846a479
github.com/golang-jwt/jwt/v4 v4.5.0
github.com/gocarina/gocsv v0.0.0-20230123225133-763e25b40669
github.com/golang-jwt/jwt/v4 v4.4.3
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
github.com/google/uuid v1.3.0
github.com/iancoleman/strcase v0.2.0
github.com/imdario/mergo v0.3.15
github.com/imdario/mergo v0.3.13
github.com/jinzhu/copier v0.3.5
github.com/labstack/echo-jwt/v4 v4.1.0
github.com/labstack/echo/v4 v4.10.2
github.com/labstack/echo/v4 v4.10.0
github.com/labstack/gommon v0.4.0
github.com/lib/pq v1.10.7
github.com/magefile/mage v1.14.0
@ -54,25 +54,25 @@ require (
github.com/redis/go-redis/v9 v9.0.2
github.com/robfig/cron/v3 v3.0.1
github.com/samedi/caldav-go v3.0.0+incompatible
github.com/spf13/afero v1.9.5
github.com/spf13/afero v1.9.3
github.com/spf13/cobra v1.6.1
github.com/spf13/viper v1.15.0
github.com/stretchr/testify v1.8.2
github.com/swaggo/swag v1.8.12
github.com/stretchr/testify v1.8.1
github.com/swaggo/swag v1.8.10
github.com/tkuchiki/go-timezone v0.2.2
github.com/ulule/limiter/v3 v3.11.1
github.com/vectordotdev/go-datemath v0.1.1-0.20220323213446-f3954d0b18ae
github.com/wneessen/go-mail v0.3.9
github.com/yuin/goldmark v1.5.4
golang.org/x/crypto v0.7.0
golang.org/x/image v0.6.0
golang.org/x/oauth2 v0.6.0
github.com/ulule/limiter/v3 v3.11.0
github.com/vectordotdev/go-datemath v0.1.1-0.20211214182920-0a4ac8742b93
github.com/wneessen/go-mail v0.3.8
github.com/yuin/goldmark v1.5.3
golang.org/x/crypto v0.5.0
golang.org/x/image v0.3.0
golang.org/x/oauth2 v0.4.0
golang.org/x/sync v0.1.0
golang.org/x/sys v0.6.0
golang.org/x/term v0.6.0
golang.org/x/sys v0.4.0
golang.org/x/term v0.4.0
gopkg.in/d4l3k/messagediff.v1 v1.2.1
gopkg.in/yaml.v3 v3.0.1
src.techknowlogick.com/xgo v1.7.1-0.20230307171022-b60708668fc7
src.techknowlogick.com/xgo v1.7.1-0.20230117190652-94aee174ab86
src.techknowlogick.com/xormigrate v1.5.0
xorm.io/builder v0.3.12
xorm.io/xorm v1.3.2
@ -85,7 +85,7 @@ require (
github.com/beevik/etree v1.1.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
github.com/cenkalti/backoff/v3 v3.2.2 // indirect
github.com/cenkalti/backoff/v3 v3.0.0 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
@ -93,7 +93,7 @@ require (
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/garyburd/redigo v1.6.0 // indirect
github.com/ghodss/yaml v1.0.0 // indirect
github.com/go-chi/chi/v5 v5.0.8 // indirect
github.com/go-chi/chi v4.0.2+incompatible // indirect
github.com/go-jose/go-jose/v3 v3.0.0 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.19.6 // indirect
@ -103,20 +103,20 @@ require (
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/errwrap v1.0.0 // indirect
github.com/hashicorp/go-multierror v1.1.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.0.1 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/laurent22/ical-go v0.1.1-0.20181107184520-7e5d6ade8eef // indirect
github.com/lithammer/shortuuid/v3 v3.0.7 // indirect
github.com/lithammer/shortuuid/v3 v3.0.4 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect
github.com/mattn/go-runewidth v0.0.9 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
@ -127,8 +127,8 @@ require (
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.3.0 // indirect
github.com/prometheus/common v0.39.0 // indirect
github.com/prometheus/procfs v0.9.0 // indirect
github.com/prometheus/common v0.37.0 // indirect
github.com/prometheus/procfs v0.8.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
@ -138,17 +138,20 @@ require (
github.com/urfave/cli/v2 v2.3.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
golang.org/x/mod v0.9.0 // indirect
golang.org/x/net v0.8.0 // indirect
golang.org/x/text v0.8.0 // indirect
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
golang.org/x/net v0.5.0 // indirect
golang.org/x/text v0.6.0 // indirect
golang.org/x/time v0.3.0 // indirect
golang.org/x/tools v0.7.0 // indirect
golang.org/x/tools v0.1.12 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.29.1 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)
replace github.com/samedi/caldav-go => github.com/kolaente/caldav-go v3.0.1-0.20190610114120-2a4eb8b5dcc9+incompatible // Branch: feature/dynamic-supported-components, PR: https://github.com/samedi/caldav-go/pull/6 and https://github.com/samedi/caldav-go/pull/7
replace (
github.com/samedi/caldav-go => github.com/kolaente/caldav-go v3.0.1-0.20190524174923-9e5cd1688227+incompatible // Branch: feature/dynamic-supported-components, PR: https://github.com/samedi/caldav-go/pull/6 and https://github.com/samedi/caldav-go/pull/7
gopkg.in/fsnotify.v1 => github.com/kolaente/fsnotify v1.4.10-0.20200411160148-1bc3c8ff4048 // See https://github.com/fsnotify/fsnotify/issues/328 and https://github.com/golang/go/issues/26904
)
go 1.20
go 1.19

193
go.sum
View File

@ -60,8 +60,8 @@ github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/ThreeDotsLabs/watermill v1.2.0 h1:TU3TML1dnQ/ifK09F2+4JQk2EKhmhXe7Qv7eb5ZpTS8=
github.com/ThreeDotsLabs/watermill v1.2.0/go.mod h1:IuVxGk/kgCN0cex2S94BLglUiB0PwOm8hbUhm6g2Nx4=
github.com/ThreeDotsLabs/watermill v1.1.1 h1:+9NXqWQvplzxBru2CIInvVOZeKUnM+Nysg42fInl5sY=
github.com/ThreeDotsLabs/watermill v1.1.1/go.mod h1:Qd1xNFxolCAHCzcMrm6RnjW0manbvN+DJVWc1MWRFlI=
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
github.com/adlio/trello v1.10.0 h1:ia/rzoBwJJKr4IqnMlrU6n09CVqeyaahSkEVcV5/gPc=
github.com/adlio/trello v1.10.0/go.mod h1:I4Lti4jf2KxjTNgTqs5W3lLuE78QZZdYbbPnQQGwjOo=
@ -70,16 +70,15 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/arran4/golang-ical v0.0.0-20230213232137-07c6aad5e4f0 h1:VVPogIxPiZ6WK5G4Pve5VSQ4HEFiJ8GChpqRjo1gN2c=
github.com/arran4/golang-ical v0.0.0-20230213232137-07c6aad5e4f0/go.mod h1:BSTTrYHuM12oAL8jDdcmPdw02SBThKYWNFHQlvEG6b0=
github.com/arran4/golang-ical v0.0.0-20230318005454-19abf92700cc h1:up1aDcTCZ3KrL2ukKxNqjMRx/CCaXyn9Wl6N7ea3EWc=
github.com/arran4/golang-ical v0.0.0-20230318005454-19abf92700cc/go.mod h1:BSTTrYHuM12oAL8jDdcmPdw02SBThKYWNFHQlvEG6b0=
github.com/arran4/golang-ical v0.0.0-20221122102835-109346913e54 h1:HfAA5Vxbo64UTckj+EW/hfBjvvcUcbcwWCASvypy8JU=
github.com/arran4/golang-ical v0.0.0-20221122102835-109346913e54/go.mod h1:BSTTrYHuM12oAL8jDdcmPdw02SBThKYWNFHQlvEG6b0=
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef h1:46PFijGLmAjMPwCCCo7Jf0W6f9slllCkkv7vyc1yOSg=
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
@ -103,10 +102,11 @@ github.com/c2h5oh/datasize v0.0.0-20220606134207-859f65c6625b h1:6+ZFm0flnudZzdS
github.com/c2h5oh/datasize v0.0.0-20220606134207-859f65c6625b/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M=
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M=
github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs=
github.com/cenkalti/backoff/v3 v3.0.0 h1:ske+9nBpD9qZsTBoF41nW5L+AIuFBKMeze18XQ3eG1c=
github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
@ -173,20 +173,14 @@ github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/gabriel-vasile/mimetype v1.4.1 h1:TRWk7se+TOjCYgRth7+1/OYLNiRNIotknkFtf/dnN7Q=
github.com/gabriel-vasile/mimetype v1.4.1/go.mod h1:05Vi0w3Y9c/lNvJOdmIwvrrAhX3rYhfQQCaf9VJcv7M=
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
github.com/garyburd/redigo v1.6.0 h1:0VruCpn7yAIIu7pWVClQC8wxCJEcG3nyzpMSHKi1PQc=
github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
github.com/getsentry/sentry-go v0.18.0 h1:MtBW5H9QgdcJabtZcuJG80BMOwaBpkRDZkxRkNC1sN0=
github.com/getsentry/sentry-go v0.18.0/go.mod h1:Kgon4Mby+FJ7ZWHFUAZgVaIa8sxHtnRJRLTXZr51aKQ=
github.com/getsentry/sentry-go v0.19.0 h1:BcCH3CN5tXt5aML+gwmbFwVptLLQA+eT866fCO9wVOM=
github.com/getsentry/sentry-go v0.19.0/go.mod h1:y3+lGEFEFexZtpbG1GUE2WD/f9zGyKYwpEqryTOC/nE=
github.com/getsentry/sentry-go v0.20.0 h1:bwXW98iMRIWxn+4FgPW7vMrjmbym6HblXALmhjHmQaQ=
github.com/getsentry/sentry-go v0.20.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY=
github.com/getsentry/sentry-go v0.17.0 h1:UustVWnOoDFHBS7IJUB2QK/nB5pap748ZEp0swnQJak=
github.com/getsentry/sentry-go v0.17.0/go.mod h1:B82dxtBvxG0KaPD8/hfSV+VcHD+Lg/xUS4JuQn1P4cM=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-chi/chi/v5 v5.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0=
github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/chi v4.0.2+incompatible h1:maB6vn6FqCxrpz4FqWdh4+lwpyZIQS7YEAUcHlgXVRs=
github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
@ -196,9 +190,12 @@ github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxF
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
@ -219,10 +216,8 @@ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/me
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/go-testfixtures/testfixtures/v3 v3.8.1 h1:uonwvepqRvSgddcrReZQhojTlWlmOlHkYAb9ZaOMWgU=
github.com/go-testfixtures/testfixtures/v3 v3.8.1/go.mod h1:Kdu7YeMC0KRXVHdaQ91Vmx3pcjoTF63h4f1qTJDdXLA=
github.com/gocarina/gocsv v0.0.0-20230226133904-70c27cb2918a h1:/5o1ejt5M0fNAN2lU1NBLtPzUSZru689EWJq01ptr+E=
github.com/gocarina/gocsv v0.0.0-20230226133904-70c27cb2918a/go.mod h1:5YoVOkjYAQumqlV356Hj3xeYh4BdZuLE0/nRkf2NKkI=
github.com/gocarina/gocsv v0.0.0-20230325173030-9a18a846a479 h1:KaCpc4e48emF9hYmMB9INyfpGJHAZxEAS9EqWFkpTig=
github.com/gocarina/gocsv v0.0.0-20230325173030-9a18a846a479/go.mod h1:5YoVOkjYAQumqlV356Hj3xeYh4BdZuLE0/nRkf2NKkI=
github.com/gocarina/gocsv v0.0.0-20230123225133-763e25b40669 h1:MvZzCA/mduVWoBSVKJeMdv+AqXQmZZ8i6p8889ejt/Y=
github.com/gocarina/gocsv v0.0.0-20230123225133-763e25b40669/go.mod h1:5YoVOkjYAQumqlV356Hj3xeYh4BdZuLE0/nRkf2NKkI=
github.com/goccy/go-json v0.8.1/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk=
github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
@ -234,8 +229,8 @@ github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v4 v4.4.3 h1:Hxl6lhQFj4AnOX6MLrsCb/+7tCj7DxP7VA+2rDIq5AU=
github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
@ -305,8 +300,8 @@ github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLe
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
@ -322,15 +317,14 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgf
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI=
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
@ -354,10 +348,6 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
github.com/imdario/mergo v0.3.14 h1:fOqeC1+nCuuk6PKQdg9YmosXX7Y7mHX6R/0ZldI9iHo=
github.com/imdario/mergo v0.3.14/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM=
github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
@ -430,29 +420,33 @@ github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kolaente/caldav-go v3.0.1-0.20190524174923-9e5cd1688227+incompatible h1:PkEEpmbrFXlMul8cOplR8nkcIM/NDbx+H6fq2+vaKAA=
github.com/kolaente/caldav-go v3.0.1-0.20190524174923-9e5cd1688227+incompatible/go.mod h1:y1UhTNI4g0hVymJrI6yJ5/ohy09hNBeU8iJEZjgdDOw=
github.com/kolaente/caldav-go v3.0.1-0.20190610114120-2a4eb8b5dcc9+incompatible h1:q7DbyV+sFjEoTuuUdRDNl2nlyfztkZgxVVCV7JhzIkY=
github.com/kolaente/caldav-go v3.0.1-0.20190610114120-2a4eb8b5dcc9+incompatible/go.mod h1:y1UhTNI4g0hVymJrI6yJ5/ohy09hNBeU8iJEZjgdDOw=
github.com/kolaente/fsnotify v1.4.10-0.20200411160148-1bc3c8ff4048/go.mod h1:dv6KyzAg9UuJWiE1pwkvvB2i0TvcQM6QhdsXLZ7K5KI=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
@ -461,8 +455,8 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/labstack/echo-jwt/v4 v4.1.0 h1:eYGBxauPkyzBM78KJbR5OSz5uhKMDkhJZhTTIuoH6Pg=
github.com/labstack/echo-jwt/v4 v4.1.0/go.mod h1:DHSSaL6cTgczdPXjf8qrTHRbrau2flcddV7CPMs2U/Y=
github.com/labstack/echo/v4 v4.1.16/go.mod h1:awO+5TzAjvL8XpibdsfXxPgHr+orhtXZJZIQCVjogKI=
github.com/labstack/echo/v4 v4.10.2 h1:n1jAhnq/elIFTHr1EYpiYtyKgx4RW9ccVgkqByZaN2M=
github.com/labstack/echo/v4 v4.10.2/go.mod h1:OEyqf2//K1DFdE57vw2DRgWY0M7s65IVQO2FzvI4J5k=
github.com/labstack/echo/v4 v4.10.0 h1:5CiyngihEO4HXsz3vVsJn7f8xAlWwRr3aY6Ih280ZKA=
github.com/labstack/echo/v4 v4.10.0/go.mod h1:S/T/5fy/GigaXnHTkh0ZGe4LpkkQysvRjFMSUTkDRNQ=
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8=
github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
@ -478,8 +472,8 @@ github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw=
github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
github.com/lithammer/shortuuid/v3 v3.0.7 h1:trX0KTHy4Pbwo/6ia8fscyHoGA+mf1jWbPJVuvyJQQ8=
github.com/lithammer/shortuuid/v3 v3.0.7/go.mod h1:vMk8ke37EmiewwolSO1NLW8vP4ZaKlRuDIi8tWWmAts=
github.com/lithammer/shortuuid/v3 v3.0.4 h1:uj4xhotfY92Y1Oa6n6HUiFn87CdoEHYUlTy0+IgbLrs=
github.com/lithammer/shortuuid/v3 v3.0.4/go.mod h1:RviRjexKqIzx/7r1peoAITm6m7gnif/h+0zmolKJjzw=
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo=
github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
@ -508,9 +502,8 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-oci8 v0.0.0-20191108001511-cbd8d5bc1da0/go.mod h1:/M9VLO+lUPmxvoOK2PfWRZ8mTtB4q1Hy9lEGijv9Nr8=
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
@ -520,9 +513,8 @@ github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71
github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
@ -541,6 +533,7 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k=
@ -548,6 +541,7 @@ github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzE
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
@ -603,6 +597,9 @@ github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw=
github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
@ -610,20 +607,29 @@ github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4=
github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
github.com/prometheus/common v0.39.0 h1:oOyhkDq05hPZKItWVBkJ6g6AtGxi+fy7F4JvUV8uhsI=
github.com/prometheus/common v0.39.0/go.mod h1:6XBZ7lYdLCbkAVhwRsWTZn+IN5AB9F/NXd5w0BbEX0Y=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE=
github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI=
github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo=
github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/redis/go-redis/v9 v9.0.1 h1:L1B0L2Y7dQMnKxwfzSwemceGlQwVUsqJ1kjkdaoNhts=
github.com/redis/go-redis/v9 v9.0.1/go.mod h1:/xDTe9EF1LM61hek62Poq2nzQSGj0xSrEtEHbBQevps=
github.com/redis/go-redis/v9 v9.0.2 h1:BA426Zqe/7r56kCcvxYLWe1mkaz71LKF77GwgFzSxfE=
github.com/redis/go-redis/v9 v9.0.2/go.mod h1:/xDTe9EF1LM61hek62Poq2nzQSGj0xSrEtEHbBQevps=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
@ -651,14 +657,13 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeV
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
github.com/spf13/afero v1.9.4 h1:Sd43wM1IWz/s1aVXdOBkjJvuP8UdyqioeE4AmM0QsBs=
github.com/spf13/afero v1.9.4/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM=
github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk=
github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
@ -687,17 +692,12 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8=
github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
github.com/swaggo/swag v1.8.10 h1:eExW4bFa52WOjqRzRD58bgWsWfdFJso50lpbeTcmTfo=
github.com/swaggo/swag v1.8.10/go.mod h1:ezQVUUhly8dludpVk+/PuwJWvLLanB13ygV5Pr9enSk=
github.com/swaggo/swag v1.8.11 h1:Fp1dNNtDvbCf+8kvehZbHQnlF6AxHGjmw6H/xAMrZfY=
github.com/swaggo/swag v1.8.11/go.mod h1:2GXgpNI9iy5OdsYWu8zXfRAGnOAPxYxTWTyM0XOTYZQ=
github.com/swaggo/swag v1.8.12 h1:pctzkNPu0AlQP2royqX3apjKCQonAnf7KGoxeO4y64w=
github.com/swaggo/swag v1.8.12/go.mod h1:lNfm6Gg+oAq3zRJQNEMBE66LIJKM44mxFqhEEgy2its=
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
github.com/tkuchiki/go-timezone v0.2.2 h1:MdHR65KwgVTwWFQrota4SKzc4L5EfuH5SdZZGtk/P2Q=
@ -705,8 +705,6 @@ github.com/tkuchiki/go-timezone v0.2.2/go.mod h1:oFweWxYl35C/s7HMVZXiA19Jr9Y0qJH
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ulule/limiter/v3 v3.11.0 h1:9hXMyS0K8Z+EYfrtwPMwmWYflPimswsC/EOMsO2sHx4=
github.com/ulule/limiter/v3 v3.11.0/go.mod h1:OiKIiMs9dXLMk5TwtIBZlswhPigov9fGmwO4xYbmFkY=
github.com/ulule/limiter/v3 v3.11.1 h1:wm6YaA2JwIXc0S+z8TK8/neWMOTf4m20I5jL1dwLRcw=
github.com/ulule/limiter/v3 v3.11.1/go.mod h1:4nk/9RHEJthkjD+mmkqYxaPfD4pkB91PTH7k8ozB80g=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
@ -721,20 +719,16 @@ github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQ
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/vectordotdev/go-datemath v0.1.1-0.20211214182920-0a4ac8742b93 h1:bT0ZMfsMi2Xh8dopgxhFT+OJH88QITHpdppdkG1rXJQ=
github.com/vectordotdev/go-datemath v0.1.1-0.20211214182920-0a4ac8742b93/go.mod h1:PnwzbSst7KD3vpBzzlntZU5gjVa455Uqa5QPiKSYJzQ=
github.com/vectordotdev/go-datemath v0.1.1-0.20220323213446-f3954d0b18ae h1:oyiy3uBj1F4O3AaFh7hUGBrJjAssJhKyAbwxtkslxqo=
github.com/vectordotdev/go-datemath v0.1.1-0.20220323213446-f3954d0b18ae/go.mod h1:PnwzbSst7KD3vpBzzlntZU5gjVa455Uqa5QPiKSYJzQ=
github.com/wneessen/go-mail v0.3.8 h1:ja5D/o/RVwrtRIYFlrO7GmtcjDNeMakGQuwQRZYv0JM=
github.com/wneessen/go-mail v0.3.8/go.mod h1:m25lkU2GYQnlVr6tdwK533/UXxo57V0kLOjaFYmub0E=
github.com/wneessen/go-mail v0.3.9 h1:Q4DbCk3htT5DtDWKeMgNXCiHc4bBY/vv/XQPT6XDXzc=
github.com/wneessen/go-mail v0.3.9/go.mod h1:zxOlafWCP/r6FEhAaRgH4IC1vg2YXxO0Nar9u0IScZ8=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark v1.5.4 h1:2uY/xC0roWy8IBEGLgB1ywIoEJFGmRrX21YQcvGZzjU=
github.com/yuin/goldmark v1.5.4/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark v1.5.3 h1:3HUJmBFbQW9fhQOzMgseU134xfi6hU+mjWywx5Ty+/M=
github.com/yuin/goldmark v1.5.3/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
@ -779,9 +773,8 @@ golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -795,8 +788,8 @@ golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMk
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.6.0 h1:bR8b5okrPI3g/gyZakLZHeWxAR8Dn5CyxXv1hLH5g/4=
golang.org/x/image v0.6.0/go.mod h1:MXLdDR43H7cDJq5GEGXEVeeNhPgi+YYEQ2pC1byI1x0=
golang.org/x/image v0.3.0 h1:HTDXbdK9bjfSWkPzDJIw89W8CAtfFGduujWs33NLLsg=
golang.org/x/image v0.3.0/go.mod h1:fXd9211C/0VTlYuAcOhW8dY/RtEJqODXOWBDpmYBf+A=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@ -818,11 +811,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs=
golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -866,14 +856,15 @@ golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw=
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -883,9 +874,11 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/oauth2 v0.3.0/go.mod h1:rQrIauxkUhJ6CuwEXwymO2/eh4xz2ZWF1nBkcxS+tGk=
golang.org/x/oauth2 v0.6.0 h1:Lh8GPgSKBfWSwFvtuWOfeI3aAAnbXTSutYxJiOJFgIw=
golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw=
golang.org/x/oauth2 v0.4.0 h1:NF0gk8LVPg1Ml7SSbGyySuoxdsXitj7TvgvuRxIMc/M=
golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -928,6 +921,7 @@ golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -941,6 +935,8 @@ golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200808120158-1030fc2bf1d9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -951,31 +947,33 @@ golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210902050250-f475640dd07b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.4.0 h1:O7UWfv5+A2qiuulQk30kVinPoMtoIPeVaKLEgLpVkvg=
golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -985,9 +983,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k=
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@ -1053,11 +1050,8 @@ golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4f
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4=
golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -1168,18 +1162,16 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.29.1 h1:7QBf+IK2gx70Ap/hDsOmam3GE0v9HicjfEdAxE62UoM=
google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/d4l3k/messagediff.v1 v1.2.1 h1:70AthpjunwzUiarMHyED52mj9UwtAnE89l1Gmrt3EU0=
gopkg.in/d4l3k/messagediff.v1 v1.2.1/go.mod h1:EUzikiKadqXWcD1AzJLagx0j/BeeWGtn++04Xniyg44=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
@ -1193,6 +1185,7 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
@ -1326,10 +1319,6 @@ sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
src.techknowlogick.com/xgo v1.7.1-0.20230117190652-94aee174ab86 h1:VybPMHRdCLbdCttI8fMXOaGpoJGSG9+W/5cfRgr1Xjc=
src.techknowlogick.com/xgo v1.7.1-0.20230117190652-94aee174ab86/go.mod h1:31CE1YKtDOrKTk9PSnjTpe6YbO6W/0LTYZ1VskL09oU=
src.techknowlogick.com/xgo v1.7.1-0.20230214195350-44f7e66f9b20 h1:Wye8Ljlv2AZvYPW1twGbW9sQWGtjurbQECnnkNx6gd0=
src.techknowlogick.com/xgo v1.7.1-0.20230214195350-44f7e66f9b20/go.mod h1:31CE1YKtDOrKTk9PSnjTpe6YbO6W/0LTYZ1VskL09oU=
src.techknowlogick.com/xgo v1.7.1-0.20230307171022-b60708668fc7 h1:nPPnMdR4wih62PSsnHK/SlYM1lOZk/St0k7DkJadMV4=
src.techknowlogick.com/xgo v1.7.1-0.20230307171022-b60708668fc7/go.mod h1:31CE1YKtDOrKTk9PSnjTpe6YbO6W/0LTYZ1VskL09oU=
src.techknowlogick.com/xormigrate v1.5.0 h1:6mWTh8d0sWjMTLUgJqiLe0e0Teu+1j+RgI7ErAeOEV0=
src.techknowlogick.com/xormigrate v1.5.0/go.mod h1:QOCnBeWralVncPn9eZlM4w/rglFK8o1vYpemzPenkBM=
xorm.io/builder v0.3.7/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=

View File

@ -405,7 +405,7 @@ func checkGolangCiLintInstalled() {
mg.Deps(initVars)
if err := exec.Command("golangci-lint").Run(); err != nil && strings.Contains(err.Error(), "executable file not found") {
fmt.Println("Please manually install golangci-lint by running")
fmt.Println("curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.52.1")
fmt.Println("curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.51.0")
os.Exit(1)
}
}

View File

@ -31,6 +31,19 @@ import (
// DateFormat is the caldav date format
const DateFormat = `20060102T150405`
// Event holds a single caldav event
type Event struct {
Summary string
Description string
UID string
Alarms []Alarm
Color string
Timestamp time.Time
Start time.Time
End time.Time
}
// Todo holds a single VTODO
type Todo struct {
// Required
@ -45,14 +58,13 @@ type Todo struct {
Priority int64 // 0-9, 1 is highest
RelatedToUID string
Color string
Categories []string
Start time.Time
End time.Time
DueDate time.Time
Duration time.Duration
RepeatAfter int64
RepeatMode models.TaskRepeatMode
Alarms []Alarm
Start time.Time
End time.Time
DueDate time.Time
Duration time.Duration
RepeatAfter int64
RepeatMode models.TaskRepeatMode
Created time.Time
Updated time.Time // last-mod
@ -61,8 +73,6 @@ type Todo struct {
// Alarm holds infos about an alarm from a caldav event
type Alarm struct {
Time time.Time
Duration time.Duration
RelativeTo models.ReminderRelation
Description string
}
@ -90,6 +100,58 @@ X-OUTLOOK-COLOR:` + color + `
X-FUNAMBOL-COLOR:` + color
}
// ParseEvents parses an array of caldav events and gives them back as string
func ParseEvents(config *Config, events []*Event) (caldavevents string) {
caldavevents += `BEGIN:VCALENDAR
VERSION:2.0
METHOD:PUBLISH
X-PUBLISHED-TTL:PT4H
X-WR-CALNAME:` + config.Name + `
PRODID:-//` + config.ProdID + `//EN` + getCaldavColor(config.Color)
for _, e := range events {
if e.UID == "" {
e.UID = makeCalDavTimeFromTimeStamp(e.Timestamp) + utils.Sha256(e.Summary)
}
formattedDescription := ""
if e.Description != "" {
re := regexp.MustCompile(`\r?\n`)
formattedDescription = re.ReplaceAllString(e.Description, "\\n")
}
caldavevents += `
BEGIN:VEVENT
UID:` + e.UID + `
SUMMARY:` + e.Summary + getCaldavColor(e.Color) + `
DESCRIPTION:` + formattedDescription + `
DTSTAMP:` + makeCalDavTimeFromTimeStamp(e.Timestamp) + `
DTSTART:` + makeCalDavTimeFromTimeStamp(e.Start) + `
DTEND:` + makeCalDavTimeFromTimeStamp(e.End)
for _, a := range e.Alarms {
if a.Description == "" {
a.Description = e.Summary
}
caldavevents += `
BEGIN:VALARM
TRIGGER:` + calcAlarmDateFromReminder(e.Start, a.Time) + `
ACTION:DISPLAY
DESCRIPTION:` + a.Description + `
END:VALARM`
}
caldavevents += `
END:VEVENT`
}
caldavevents += `
END:VCALENDAR` // Need a line break
return
}
func formatDuration(duration time.Duration) string {
seconds := duration.Seconds() - duration.Minutes()*60
minutes := duration.Minutes() - duration.Hours()*60
@ -177,14 +239,9 @@ RRULE:FREQ=SECONDLY;INTERVAL=` + strconv.FormatInt(t.RepeatAfter, 10)
}
}
if len(t.Categories) > 0 {
caldavtodos += `
CATEGORIES:` + strings.Join(t.Categories, ",")
}
caldavtodos += `
LAST-MODIFIED:` + makeCalDavTimeFromTimeStamp(t.Updated)
caldavtodos += ParseAlarms(t.Alarms, t.Summary)
caldavtodos += `
END:VTODO`
}
@ -195,42 +252,19 @@ END:VCALENDAR` // Need a line break
return
}
func ParseAlarms(alarms []Alarm, taskDescription string) (caldavalarms string) {
for _, a := range alarms {
if a.Description == "" {
a.Description = taskDescription
}
caldavalarms += `
BEGIN:VALARM`
switch a.RelativeTo {
case models.ReminderRelationStartDate:
caldavalarms += `
TRIGGER;RELATED=START:` + makeCalDavDuration(a.Duration)
case models.ReminderRelationEndDate, models.ReminderRelationDueDate:
caldavalarms += `
TRIGGER;RELATED=END:` + makeCalDavDuration(a.Duration)
default:
caldavalarms += `
TRIGGER;VALUE=DATE-TIME:` + makeCalDavTimeFromTimeStamp(a.Time)
}
caldavalarms += `
ACTION:DISPLAY
DESCRIPTION:` + a.Description + `
END:VALARM`
}
return caldavalarms
}
func makeCalDavTimeFromTimeStamp(ts time.Time) (caldavtime string) {
return ts.In(time.UTC).Format(DateFormat) + "Z"
}
func makeCalDavDuration(duration time.Duration) (caldavtime string) {
if duration < 0 {
duration = duration.Abs()
caldavtime = "-"
func calcAlarmDateFromReminder(eventStart, reminder time.Time) (alarmTime string) {
diff := reminder.Sub(eventStart)
diffStr := strings.ToUpper(diff.String())
if diff < 0 {
alarmTime += `-`
// We append the - at the beginning of the caldav flag, that would get in the way if the minutes
// themselves are also containing it
diffStr = diffStr[1:]
}
caldavtime += "PT" + strings.ToUpper(duration.Truncate(time.Millisecond).String())
alarmTime += `PT` + diffStr
return
}

View File

@ -26,6 +26,275 @@ import (
"github.com/stretchr/testify/assert"
)
func TestParseEvents(t *testing.T) {
type args struct {
config *Config
events []*Event
}
tests := []struct {
name string
args args
wantCaldavevents string
}{
{
name: "Test caldavparsing without reminders",
args: args{
config: &Config{
Name: "test",
ProdID: "RandomProdID which is not random",
Color: "ffffff",
},
events: []*Event{
{
Summary: "Event #1",
Description: "Lorem Ipsum",
UID: "randommduid",
Timestamp: time.Unix(1543626724, 0).In(config.GetTimeZone()),
Start: time.Unix(1543626724, 0).In(config.GetTimeZone()),
End: time.Unix(1543627824, 0).In(config.GetTimeZone()),
Color: "affffe",
},
{
Summary: "Event #2",
UID: "randommduidd",
Timestamp: time.Unix(1543726724, 0).In(config.GetTimeZone()),
Start: time.Unix(1543726724, 0).In(config.GetTimeZone()),
End: time.Unix(1543738724, 0).In(config.GetTimeZone()),
},
{
Summary: "Event #3 with empty uid",
UID: "20181202T0600242aaef4a81d770c1e775e26bc5abebc87f1d3d7bffaa83",
Timestamp: time.Unix(1543726824, 0).In(config.GetTimeZone()),
Start: time.Unix(1543726824, 0).In(config.GetTimeZone()),
End: time.Unix(1543727000, 0).In(config.GetTimeZone()),
},
},
},
wantCaldavevents: `BEGIN:VCALENDAR
VERSION:2.0
METHOD:PUBLISH
X-PUBLISHED-TTL:PT4H
X-WR-CALNAME:test
PRODID:-//RandomProdID which is not random//EN
X-APPLE-CALENDAR-COLOR:#ffffffFF
X-OUTLOOK-COLOR:#ffffffFF
X-FUNAMBOL-COLOR:#ffffffFF
BEGIN:VEVENT
UID:randommduid
SUMMARY:Event #1
X-APPLE-CALENDAR-COLOR:#affffeFF
X-OUTLOOK-COLOR:#affffeFF
X-FUNAMBOL-COLOR:#affffeFF
DESCRIPTION:Lorem Ipsum
DTSTAMP:20181201T011204Z
DTSTART:20181201T011204Z
DTEND:20181201T013024Z
END:VEVENT
BEGIN:VEVENT
UID:randommduidd
SUMMARY:Event #2
DESCRIPTION:
DTSTAMP:20181202T045844Z
DTSTART:20181202T045844Z
DTEND:20181202T081844Z
END:VEVENT
BEGIN:VEVENT
UID:20181202T0600242aaef4a81d770c1e775e26bc5abebc87f1d3d7bffaa83
SUMMARY:Event #3 with empty uid
DESCRIPTION:
DTSTAMP:20181202T050024Z
DTSTART:20181202T050024Z
DTEND:20181202T050320Z
END:VEVENT
END:VCALENDAR`,
},
{
name: "Test caldavparsing with reminders",
args: args{
config: &Config{
Name: "test2",
ProdID: "RandomProdID which is not random",
},
events: []*Event{
{
Summary: "Event #1",
Description: "Lorem Ipsum",
UID: "randommduid",
Timestamp: time.Unix(1543626724, 0).In(config.GetTimeZone()),
Start: time.Unix(1543626724, 0).In(config.GetTimeZone()),
End: time.Unix(1543627824, 0).In(config.GetTimeZone()),
Alarms: []Alarm{
{Time: time.Unix(1543626524, 0).In(config.GetTimeZone())},
{Time: time.Unix(1543626224, 0).In(config.GetTimeZone())},
{Time: time.Unix(1543626024, 0)},
},
},
{
Summary: "Event #2",
UID: "randommduidd",
Timestamp: time.Unix(1543726724, 0).In(config.GetTimeZone()),
Start: time.Unix(1543726724, 0).In(config.GetTimeZone()),
End: time.Unix(1543738724, 0).In(config.GetTimeZone()),
Alarms: []Alarm{
{Time: time.Unix(1543626524, 0).In(config.GetTimeZone())},
{Time: time.Unix(1543626224, 0).In(config.GetTimeZone())},
{Time: time.Unix(1543626024, 0).In(config.GetTimeZone())},
},
},
{
Summary: "Event #3 with empty uid",
Timestamp: time.Unix(1543726824, 0).In(config.GetTimeZone()),
Start: time.Unix(1543726824, 0).In(config.GetTimeZone()),
End: time.Unix(1543727000, 0).In(config.GetTimeZone()),
Alarms: []Alarm{
{Time: time.Unix(1543626524, 0).In(config.GetTimeZone())},
{Time: time.Unix(1543626224, 0).In(config.GetTimeZone())},
{Time: time.Unix(1543626024, 0).In(config.GetTimeZone())},
{Time: time.Unix(1543826824, 0).In(config.GetTimeZone())},
},
},
{
Summary: "Event #4 without any",
Timestamp: time.Unix(1543726824, 0),
Start: time.Unix(1543726824, 0),
End: time.Unix(1543727000, 0),
},
},
},
wantCaldavevents: `BEGIN:VCALENDAR
VERSION:2.0
METHOD:PUBLISH
X-PUBLISHED-TTL:PT4H
X-WR-CALNAME:test2
PRODID:-//RandomProdID which is not random//EN
BEGIN:VEVENT
UID:randommduid
SUMMARY:Event #1
DESCRIPTION:Lorem Ipsum
DTSTAMP:20181201T011204Z
DTSTART:20181201T011204Z
DTEND:20181201T013024Z
BEGIN:VALARM
TRIGGER:-PT3M20S
ACTION:DISPLAY
DESCRIPTION:Event #1
END:VALARM
BEGIN:VALARM
TRIGGER:-PT8M20S
ACTION:DISPLAY
DESCRIPTION:Event #1
END:VALARM
BEGIN:VALARM
TRIGGER:-PT11M40S
ACTION:DISPLAY
DESCRIPTION:Event #1
END:VALARM
END:VEVENT
BEGIN:VEVENT
UID:randommduidd
SUMMARY:Event #2
DESCRIPTION:
DTSTAMP:20181202T045844Z
DTSTART:20181202T045844Z
DTEND:20181202T081844Z
BEGIN:VALARM
TRIGGER:-PT27H50M0S
ACTION:DISPLAY
DESCRIPTION:Event #2
END:VALARM
BEGIN:VALARM
TRIGGER:-PT27H55M0S
ACTION:DISPLAY
DESCRIPTION:Event #2
END:VALARM
BEGIN:VALARM
TRIGGER:-PT27H58M20S
ACTION:DISPLAY
DESCRIPTION:Event #2
END:VALARM
END:VEVENT
BEGIN:VEVENT
UID:20181202T050024Z2aaef4a81d770c1e775e26bc5abebc87f1d3d7bffaa83
SUMMARY:Event #3 with empty uid
DESCRIPTION:
DTSTAMP:20181202T050024Z
DTSTART:20181202T050024Z
DTEND:20181202T050320Z
BEGIN:VALARM
TRIGGER:-PT27H51M40S
ACTION:DISPLAY
DESCRIPTION:Event #3 with empty uid
END:VALARM
BEGIN:VALARM
TRIGGER:-PT27H56M40S
ACTION:DISPLAY
DESCRIPTION:Event #3 with empty uid
END:VALARM
BEGIN:VALARM
TRIGGER:-PT28H0M0S
ACTION:DISPLAY
DESCRIPTION:Event #3 with empty uid
END:VALARM
BEGIN:VALARM
TRIGGER:PT27H46M40S
ACTION:DISPLAY
DESCRIPTION:Event #3 with empty uid
END:VALARM
END:VEVENT
BEGIN:VEVENT
UID:20181202T050024Zae7548ce9556df85038abe90dc674d4741a61ce74d1cf
SUMMARY:Event #4 without any
DESCRIPTION:
DTSTAMP:20181202T050024Z
DTSTART:20181202T050024Z
DTEND:20181202T050320Z
END:VEVENT
END:VCALENDAR`,
},
{
name: "Test caldavparsing with multiline description",
args: args{
config: &Config{
Name: "test",
ProdID: "RandomProdID which is not random",
},
events: []*Event{
{
Summary: "Event #1",
Description: `Lorem Ipsum
Dolor sit amet`,
UID: "randommduid",
Timestamp: time.Unix(1543626724, 0).In(config.GetTimeZone()),
Start: time.Unix(1543626724, 0).In(config.GetTimeZone()),
End: time.Unix(1543627824, 0).In(config.GetTimeZone()),
},
},
},
wantCaldavevents: `BEGIN:VCALENDAR
VERSION:2.0
METHOD:PUBLISH
X-PUBLISHED-TTL:PT4H
X-WR-CALNAME:test
PRODID:-//RandomProdID which is not random//EN
BEGIN:VEVENT
UID:randommduid
SUMMARY:Event #1
DESCRIPTION:Lorem Ipsum\nDolor sit amet
DTSTAMP:20181201T011204Z
DTSTART:20181201T011204Z
DTEND:20181201T013024Z
END:VEVENT
END:VCALENDAR`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotCaldavevents := ParseEvents(tt.args.config, tt.args.events)
assert.Equal(t, gotCaldavevents, tt.wantCaldavevents)
})
}
}
func TestParseTodos(t *testing.T) {
type args struct {
config *Config
@ -212,127 +481,13 @@ DUE:20181201T011204Z
RRULE:FREQ=SECONDLY;INTERVAL=435
LAST-MODIFIED:00010101T000000Z
END:VTODO
END:VCALENDAR`,
},
{
name: "with categories",
args: args{
config: &Config{
Name: "test",
ProdID: "RandomProdID which is not random",
Color: "ffffff",
},
todos: []*Todo{
{
Summary: "Todo #1",
UID: "randommduid",
Timestamp: time.Unix(1543626724, 0).In(config.GetTimeZone()),
Color: "affffe",
Categories: []string{"label1", "label2"},
},
},
},
wantCaldavtasks: `BEGIN:VCALENDAR
VERSION:2.0
METHOD:PUBLISH
X-PUBLISHED-TTL:PT4H
X-WR-CALNAME:test
PRODID:-//RandomProdID which is not random//EN
X-APPLE-CALENDAR-COLOR:#ffffffFF
X-OUTLOOK-COLOR:#ffffffFF
X-FUNAMBOL-COLOR:#ffffffFF
BEGIN:VTODO
UID:randommduid
DTSTAMP:20181201T011204Z
SUMMARY:Todo #1
X-APPLE-CALENDAR-COLOR:#affffeFF
X-OUTLOOK-COLOR:#affffeFF
X-FUNAMBOL-COLOR:#affffeFF
CATEGORIES:label1,label2
LAST-MODIFIED:00010101T000000Z
END:VTODO
END:VCALENDAR`,
},
{
name: "with alarm",
args: args{
config: &Config{
Name: "test",
ProdID: "RandomProdID which is not random",
},
todos: []*Todo{
{
Summary: "Todo #1",
UID: "randommduid",
Timestamp: time.Unix(1543626724, 0).In(config.GetTimeZone()),
Alarms: []Alarm{
{
Time: time.Unix(1543626724, 0).In(config.GetTimeZone()),
},
{
Time: time.Unix(1543626724, 0).In(config.GetTimeZone()),
Description: "alarm description",
},
{
Duration: -2 * time.Hour,
RelativeTo: "due_date",
},
{
Duration: 1 * time.Hour,
RelativeTo: "start_date",
},
{
Duration: time.Duration(0),
RelativeTo: "end_date",
},
},
},
},
},
wantCaldavtasks: `BEGIN:VCALENDAR
VERSION:2.0
METHOD:PUBLISH
X-PUBLISHED-TTL:PT4H
X-WR-CALNAME:test
PRODID:-//RandomProdID which is not random//EN
BEGIN:VTODO
UID:randommduid
DTSTAMP:20181201T011204Z
SUMMARY:Todo #1
LAST-MODIFIED:00010101T000000Z
BEGIN:VALARM
TRIGGER;VALUE=DATE-TIME:20181201T011204Z
ACTION:DISPLAY
DESCRIPTION:Todo #1
END:VALARM
BEGIN:VALARM
TRIGGER;VALUE=DATE-TIME:20181201T011204Z
ACTION:DISPLAY
DESCRIPTION:alarm description
END:VALARM
BEGIN:VALARM
TRIGGER;RELATED=END:-PT2H0M0S
ACTION:DISPLAY
DESCRIPTION:Todo #1
END:VALARM
BEGIN:VALARM
TRIGGER;RELATED=START:PT1H0M0S
ACTION:DISPLAY
DESCRIPTION:Todo #1
END:VALARM
BEGIN:VALARM
TRIGGER;RELATED=END:PT0S
ACTION:DISPLAY
DESCRIPTION:Todo #1
END:VALARM
END:VTODO
END:VCALENDAR`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotCaldavtasks := ParseTodos(tt.args.config, tt.args.todos)
assert.Equal(t, tt.wantCaldavtasks, gotCaldavtasks)
assert.Equal(t, gotCaldavtasks, tt.wantCaldavtasks)
})
}
}

View File

@ -17,15 +17,12 @@
package caldav
import (
"errors"
"strconv"
"strings"
"time"
"code.vikunja.io/api/pkg/config"
"code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/utils"
ics "github.com/arran4/golang-ical"
)
@ -37,18 +34,6 @@ func GetCaldavTodosForTasks(project *models.ProjectWithTasksAndBuckets, projectT
for _, t := range projectTasks {
duration := t.EndDate.Sub(t.StartDate)
var categories []string
for _, label := range t.Labels {
categories = append(categories, label.Title)
}
var alarms []Alarm
for _, reminder := range t.Reminders {
alarms = append(alarms, Alarm{
Time: reminder.Reminder,
Duration: time.Duration(reminder.RelativePeriod) * time.Second,
RelativeTo: reminder.RelativeTo,
})
}
caldavtodos = append(caldavtodos, &Todo{
Timestamp: t.Updated,
@ -57,7 +42,6 @@ func GetCaldavTodosForTasks(project *models.ProjectWithTasksAndBuckets, projectT
Description: t.Description,
Completed: t.DoneAt,
// Organizer: &t.CreatedBy, // Disabled until we figure out how this works
Categories: categories,
Priority: t.Priority,
Start: t.StartDate,
End: t.EndDate,
@ -67,7 +51,6 @@ func GetCaldavTodosForTasks(project *models.ProjectWithTasksAndBuckets, projectT
Duration: duration,
RepeatAfter: t.RepeatAfter,
RepeatMode: t.RepeatMode,
Alarms: alarms,
})
}
@ -84,20 +67,17 @@ func ParseTaskFromVTODO(content string) (vTask *models.Task, err error) {
if err != nil {
return nil, err
}
vTodo, ok := parsed.Components[0].(*ics.VTodo)
if !ok {
return nil, errors.New("VTODO element not found")
}
// We put the vTodo details in a map to be able to handle them more easily
task := make(map[string]ics.IANAProperty)
for _, c := range vTodo.UnknownPropertiesIANAProperties() {
task[c.IANAToken] = c
// We put the task details in a map to be able to handle them more easily
task := make(map[string]string)
for _, c := range parsed.Components[0].UnknownPropertiesIANAProperties() {
task[c.IANAToken] = c.Value
}
// Parse the priority
var priority int64
if _, ok := task["PRIORITY"]; ok {
priorityParsed, err := strconv.ParseInt(task["PRIORITY"].Value, 10, 64)
priorityParsed, err := strconv.ParseInt(task["PRIORITY"], 10, 64)
if err != nil {
return nil, err
}
@ -106,35 +86,23 @@ func ParseTaskFromVTODO(content string) (vTask *models.Task, err error) {
}
// Parse the enddate
duration, _ := time.ParseDuration(task["DURATION"].Value)
duration, _ := time.ParseDuration(task["DURATION"])
description := strings.ReplaceAll(task["DESCRIPTION"].Value, "\\,", ",")
description := strings.ReplaceAll(task["DESCRIPTION"], "\\,", ",")
description = strings.ReplaceAll(description, "\\n", "\n")
var labels []*models.Label
if val, ok := task["CATEGORIES"]; ok {
categories := strings.Split(val.Value, ",")
labels = make([]*models.Label, 0, len(categories))
for _, category := range categories {
labels = append(labels, &models.Label{
Title: category,
})
}
}
vTask = &models.Task{
UID: task["UID"].Value,
Title: task["SUMMARY"].Value,
UID: task["UID"],
Title: task["SUMMARY"],
Description: description,
Priority: priority,
Labels: labels,
DueDate: caldavTimeToTimestamp(task["DUE"]),
Updated: caldavTimeToTimestamp(task["DTSTAMP"]),
StartDate: caldavTimeToTimestamp(task["DTSTART"]),
DoneAt: caldavTimeToTimestamp(task["COMPLETED"]),
}
if task["STATUS"].Value == "COMPLETED" {
if task["STATUS"] == "COMPLETED" {
vTask.Done = true
}
@ -142,66 +110,11 @@ func ParseTaskFromVTODO(content string) (vTask *models.Task, err error) {
vTask.EndDate = vTask.StartDate.Add(duration)
}
for _, vAlarm := range vTodo.SubComponents() {
if vAlarm, ok := vAlarm.(*ics.VAlarm); ok {
vTask = parseVAlarm(vAlarm, vTask)
}
}
return
}
func parseVAlarm(vAlarm *ics.VAlarm, vTask *models.Task) *models.Task {
for _, property := range vAlarm.UnknownPropertiesIANAProperties() {
if property.IANAToken != "TRIGGER" {
continue
}
if contains(property.ICalParameters["VALUE"], "DATE-TIME") {
// Example: TRIGGER;VALUE=DATE-TIME:20181201T011210Z
vTask.Reminders = append(vTask.Reminders, &models.TaskReminder{
Reminder: caldavTimeToTimestamp(property),
})
continue
}
duration := utils.ParseISO8601Duration(property.Value)
if contains(property.ICalParameters["RELATED"], "END") {
// Example: TRIGGER;RELATED=END:-P2D
if vTask.EndDate.IsZero() {
vTask.Reminders = append(vTask.Reminders, &models.TaskReminder{
RelativePeriod: int64(duration.Seconds()),
RelativeTo: models.ReminderRelationDueDate})
} else {
vTask.Reminders = append(vTask.Reminders, &models.TaskReminder{
RelativePeriod: int64(duration.Seconds()),
RelativeTo: models.ReminderRelationEndDate})
}
continue
}
// Example: TRIGGER;RELATED=START:-P2D
// Example: TRIGGER:-PT60M
vTask.Reminders = append(vTask.Reminders, &models.TaskReminder{
RelativePeriod: int64(duration.Seconds()),
RelativeTo: models.ReminderRelationStartDate})
}
return vTask
}
func contains(array []string, str string) bool {
for _, value := range array {
if value == str {
return true
}
}
return false
}
// https://tools.ietf.org/html/rfc5545#section-3.3.5
func caldavTimeToTimestamp(ianaProperty ics.IANAProperty) time.Time {
tstring := ianaProperty.Value
func caldavTimeToTimestamp(tstring string) time.Time {
if tstring == "" {
return time.Time{}
}
@ -216,24 +129,7 @@ func caldavTimeToTimestamp(ianaProperty ics.IANAProperty) time.Time {
format = `20060102`
}
var t time.Time
var err error
tzParameter := ianaProperty.ICalParameters["TZID"]
if len(tzParameter) > 0 {
loc, err := time.LoadLocation(tzParameter[0])
if err != nil {
log.Warningf("Error while parsing caldav timezone %s: %s", tzParameter[0], err)
} else {
t, err = time.ParseInLocation(format, tstring, loc)
if err != nil {
log.Warningf("Error while parsing caldav time %s to TimeStamp: %s at location %s", tstring, loc, err)
} else {
t = t.In(config.GetTimeZone())
return t
}
}
}
t, err = time.Parse(format, tstring)
t, err := time.Parse(format, tstring)
if err != nil {
log.Warningf("Error while parsing caldav time %s to TimeStamp: %s", tstring, err)
return time.Time{}

View File

@ -85,210 +85,6 @@ END:VCALENDAR`,
Updated: time.Unix(1543626724, 0).In(config.GetTimeZone()),
},
},
{
name: "With categories",
args: args{content: `BEGIN:VCALENDAR
VERSION:2.0
METHOD:PUBLISH
X-PUBLISHED-TTL:PT4H
X-WR-CALNAME:test
PRODID:-//RandomProdID which is not random//EN
BEGIN:VTODO
UID:randomuid
DTSTAMP:20181201T011204
SUMMARY:Todo #1
DESCRIPTION:Lorem Ipsum
CATEGORIES:cat1,cat2
LAST-MODIFIED:00010101T000000
END:VTODO
END:VCALENDAR`,
},
wantVTask: &models.Task{
Title: "Todo #1",
UID: "randomuid",
Description: "Lorem Ipsum",
Labels: []*models.Label{
{
Title: "cat1",
},
{
Title: "cat2",
},
},
Updated: time.Unix(1543626724, 0).In(config.GetTimeZone()),
},
},
{
name: "With alarm (time trigger)",
args: args{content: `BEGIN:VCALENDAR
VERSION:2.0
METHOD:PUBLISH
X-PUBLISHED-TTL:PT4H
X-WR-CALNAME:test
PRODID:-//RandomProdID which is not random//EN
BEGIN:VTODO
UID:randomuid
DTSTAMP:20181201T011204
SUMMARY:Todo #1
DESCRIPTION:Lorem Ipsum
BEGIN:VALARM
TRIGGER;VALUE=DATE-TIME:20181201T011210Z
ACTION:DISPLAY
END:VALARM
END:VTODO
END:VCALENDAR`,
},
wantVTask: &models.Task{
Title: "Todo #1",
UID: "randomuid",
Description: "Lorem Ipsum",
Reminders: []*models.TaskReminder{
{
Reminder: time.Date(2018, 12, 1, 1, 12, 10, 0, config.GetTimeZone()),
},
},
Updated: time.Unix(1543626724, 0).In(config.GetTimeZone()),
},
},
{
name: "With alarm (relative trigger)",
args: args{content: `BEGIN:VCALENDAR
VERSION:2.0
METHOD:PUBLISH
X-PUBLISHED-TTL:PT4H
X-WR-CALNAME:test
PRODID:-//RandomProdID which is not random//EN
BEGIN:VTODO
UID:randomuid
DTSTAMP:20181201T011204
SUMMARY:Todo #1
DESCRIPTION:Lorem Ipsum
DTSTART:20230228T170000Z
DUE:20230304T150000Z
BEGIN:VALARM
TRIGGER:PT0S
ACTION:DISPLAY
END:VALARM
BEGIN:VALARM
TRIGGER;VALUE=DURATION:-PT60M
ACTION:DISPLAY
END:VALARM
BEGIN:VALARM
TRIGGER:-PT61M
ACTION:DISPLAY
END:VALARM
BEGIN:VALARM
TRIGGER;RELATED=START:-P1D
ACTION:DISPLAY
END:VALARM
BEGIN:VALARM
TRIGGER;RELATED=END:-PT30M
ACTION:DISPLAY
END:VALARM
END:VTODO
END:VCALENDAR`,
},
wantVTask: &models.Task{
Title: "Todo #1",
UID: "randomuid",
Description: "Lorem Ipsum",
StartDate: time.Date(2023, 2, 28, 17, 0, 0, 0, config.GetTimeZone()),
DueDate: time.Date(2023, 3, 4, 15, 0, 0, 0, config.GetTimeZone()),
Reminders: []*models.TaskReminder{
{
RelativeTo: models.ReminderRelationStartDate,
RelativePeriod: 0,
},
{
RelativeTo: models.ReminderRelationStartDate,
RelativePeriod: -3600,
},
{
RelativeTo: models.ReminderRelationStartDate,
RelativePeriod: -3660,
},
{
RelativeTo: models.ReminderRelationStartDate,
RelativePeriod: -86400,
},
{
RelativeTo: models.ReminderRelationDueDate,
RelativePeriod: -1800,
},
},
Updated: time.Unix(1543626724, 0).In(config.GetTimeZone()),
},
},
{
name: "example task from tasks.org app",
args: args{content: `BEGIN:VCALENDAR
VERSION:2.0
PRODID:+//IDN tasks.org//android-130102//EN
BEGIN:VTODO
DTSTAMP:20230402T074158Z
UID:4290517349243274514
CREATED:20230402T060451Z
LAST-MODIFIED:20230402T074154Z
SUMMARY:Test with tasks.org
PRIORITY:9
CATEGORIES:Vikunja
X-APPLE-SORT-ORDER:697384109
DUE;TZID=Europe/Berlin:20230402T170001
DTSTART;TZID=Europe/Berlin:20230401T090000
BEGIN:VALARM
TRIGGER;RELATED=END:PT0S
ACTION:DISPLAY
DESCRIPTION:Default Tasks.org description
END:VALARM
BEGIN:VALARM
TRIGGER;VALUE=DATE-TIME:20230402T100000Z
ACTION:DISPLAY
DESCRIPTION:Default Tasks.org description
END:VALARM
END:VTODO
BEGIN:VTIMEZONE
TZID:Europe/Berlin
LAST-MODIFIED:20220816T024022Z
BEGIN:DAYLIGHT
TZNAME:CEST
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
DTSTART:19810329T020000
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
END:DAYLIGHT
BEGIN:STANDARD
TZNAME:CET
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
DTSTART:19961027T030000
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
END:STANDARD
END:VTIMEZONE
END:VCALENDAR`,
},
wantVTask: &models.Task{
Updated: time.Date(2023, 4, 2, 7, 41, 58, 0, config.GetTimeZone()),
UID: "4290517349243274514",
Title: "Test with tasks.org",
Priority: 1,
Labels: []*models.Label{
{
Title: "Vikunja",
},
},
DueDate: time.Date(2023, 4, 2, 15, 0, 1, 0, config.GetTimeZone()),
StartDate: time.Date(2023, 4, 1, 7, 0, 0, 0, config.GetTimeZone()),
Reminders: []*models.TaskReminder{
{
RelativeTo: models.ReminderRelationDueDate,
RelativePeriod: 0,
},
{
Reminder: time.Date(2023, 4, 2, 10, 0, 0, 0, config.GetTimeZone()),
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@ -298,108 +94,7 @@ END:VCALENDAR`,
return
}
if diff, equal := messagediff.PrettyDiff(got, tt.wantVTask); !equal {
t.Errorf("ParseTaskFromVTODO()\n gotVTask = %v\n want %v\n diff = %s", got, tt.wantVTask, diff)
}
})
}
}
func TestGetCaldavTodosForTasks(t *testing.T) {
type args struct {
list *models.ProjectWithTasksAndBuckets
tasks []*models.TaskWithComments
}
tests := []struct {
name string
args args
wantCaldav string
}{
{
name: "Format single Task as Caldav",
args: args{
list: &models.ProjectWithTasksAndBuckets{
Project: models.Project{
Title: "List title",
},
},
tasks: []*models.TaskWithComments{
{
Task: models.Task{
Title: "Task 1",
UID: "randomuid",
Description: "Description",
Priority: 3,
Created: time.Unix(1543626721, 0).In(config.GetTimeZone()),
DueDate: time.Unix(1543626722, 0).In(config.GetTimeZone()),
StartDate: time.Unix(1543626723, 0).In(config.GetTimeZone()),
EndDate: time.Unix(1543626724, 0).In(config.GetTimeZone()),
Updated: time.Unix(1543626725, 0).In(config.GetTimeZone()),
DoneAt: time.Unix(1543626726, 0).In(config.GetTimeZone()),
RepeatAfter: 86400,
Labels: []*models.Label{
{
ID: 1,
Title: "label1",
},
{
ID: 2,
Title: "label2",
},
},
Reminders: []*models.TaskReminder{
{
Reminder: time.Unix(1543626730, 0).In(config.GetTimeZone()),
},
{
Reminder: time.Unix(1543626731, 0).In(config.GetTimeZone()),
RelativePeriod: -3600,
RelativeTo: models.ReminderRelationDueDate,
},
},
},
},
},
},
wantCaldav: `BEGIN:VCALENDAR
VERSION:2.0
METHOD:PUBLISH
X-PUBLISHED-TTL:PT4H
X-WR-CALNAME:List title
PRODID:-//Vikunja Todo App//EN
BEGIN:VTODO
UID:randomuid
DTSTAMP:20181201T011205Z
SUMMARY:Task 1
DTSTART:20181201T011203Z
DTEND:20181201T011204Z
DESCRIPTION:Description
COMPLETED:20181201T011206Z
STATUS:COMPLETED
DUE:20181201T011202Z
CREATED:20181201T011201Z
PRIORITY:3
RRULE:FREQ=SECONDLY;INTERVAL=86400
CATEGORIES:label1,label2
LAST-MODIFIED:20181201T011205Z
BEGIN:VALARM
TRIGGER;VALUE=DATE-TIME:20181201T011210Z
ACTION:DISPLAY
DESCRIPTION:Task 1
END:VALARM
BEGIN:VALARM
TRIGGER;RELATED=END:-PT1H0M0S
ACTION:DISPLAY
DESCRIPTION:Task 1
END:VALARM
END:VTODO
END:VCALENDAR`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := GetCaldavTodosForTasks(tt.args.list, tt.args.tasks)
if diff, equal := messagediff.PrettyDiff(got, tt.wantCaldav); !equal {
t.Errorf("GetCaldavTodosForTasks() gotVTask = %v, want %v, diff = %s", got, tt.wantCaldav, diff)
t.Errorf("ParseTaskFromVTODO() gotVTask = %v, want %v, diff = %s", got, tt.wantVTask, diff)
}
})
}

View File

@ -118,8 +118,6 @@ const (
LogPath Key = `log.path`
LogEvents Key = `log.events`
LogEventsLevel Key = `log.eventslevel`
LogMail Key = `log.mail`
LogMailLevel Key = `log.maillevel`
RateLimitEnabled Key = `ratelimit.enabled`
RateLimitKind Key = `ratelimit.kind`
@ -351,10 +349,8 @@ func InitDefaultConfig() {
LogHTTP.setDefault("stdout")
LogEcho.setDefault("off")
LogPath.setDefault(ServiceRootpath.GetString() + "/logs")
LogEvents.setDefault("off")
LogEvents.setDefault("stdout")
LogEventsLevel.setDefault("INFO")
LogMail.setDefault("off")
LogMailLevel.setDefault("INFO")
// Rate Limit
RateLimitEnabled.setDefault(false)
RateLimitKind.setDefault("user")

View File

@ -218,9 +218,3 @@
created_by_id: -2
created: 2020-04-18 21:13:52
updated: 2020-04-18 21:13:52
- id: 36
title: testbucket36
project_id: 26
created_by_id: 15
created: 2020-04-18 21:13:52
updated: 2020-04-18 21:13:52

View File

@ -14,7 +14,3 @@
task_id: 36
label_id: 4
created: 2018-12-01 15:13:12
- id: 5
task_id: 39
label_id: 4
created: 2018-12-01 15:13:12

View File

@ -88,9 +88,3 @@
owner_id: 12
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
- id: 18
title: testnamespace18
description: Lorem Ipsum
owner_id: 15
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12

View File

@ -253,13 +253,3 @@
position: 8
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
-
id: 26
title: List 26 for Caldav tests
description: Lorem Ipsum
identifier: test26
owner_id: 15
namespace_id: 18
position: 1
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12

View File

@ -6,13 +6,7 @@
task_id: 27
reminder: 2018-12-01 01:13:44
created: 2018-12-01 01:12:04
relative_to: 'start_date'
relative_period: -3600
- id: 3
task_id: 2
reminder: 2018-12-01 01:13:44
created: 2018-12-01 01:12:04
- id: 4
task_id: 39
reminder: 2023-03-04 15:00:00
created: 2018-12-01 01:12:04

View File

@ -244,7 +244,7 @@
created: 2018-12-01 01:12:04
updated: 2018-12-01 01:12:04
- id: 27
title: 'task #27 with reminders and start_date'
title: 'task #27 with reminders'
done: false
created_by_id: 1
project_id: 1
@ -252,7 +252,6 @@
bucket_id: 1
created: 2018-12-01 01:12:04
updated: 2018-12-01 01:12:04
start_date: 2018-11-30 22:25:24
- id: 28
title: 'task #28 with repeat after'
done: false
@ -356,17 +355,3 @@
created: 2018-12-01 01:12:04
updated: 2018-12-01 01:12:04
due_date: 2018-10-30 22:25:24
- id: 39
uid: 'uid-caldav-test'
title: 'Title Caldav Test'
description: 'Description Caldav Test'
priority: 3
done: false
created_by_id: 15
project_id: 26
index: 39
due_date: 2023-03-01 15:00:00
created: 2018-12-01 01:12:04
updated: 2018-12-01 01:12:04
bucket_id: 1
position: 39

View File

@ -109,10 +109,3 @@
subject: '12345'
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
- id: 15
username: 'user15'
password: '$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.' # 1234
email: 'user15@example.com'
issuer: local
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12

View File

@ -46,9 +46,3 @@
right: 2
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
- id: 9
user_id: 15
project_id: 26
right: 0
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12

View File

@ -1,77 +0,0 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 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/>.
package integrations
import (
"net/http"
"testing"
"code.vikunja.io/api/pkg/routes/caldav"
"github.com/stretchr/testify/assert"
)
const vtodo = `BEGIN:VCALENDAR
VERSION:2.0
METHOD:PUBLISH
X-PUBLISHED-TTL:PT4H
X-WR-CALNAME:List 26 for Caldav tests
PRODID:-//Vikunja Todo App//EN
BEGIN:VTODO
UID:uid
DTSTAMP:20230301T073337Z
SUMMARY:Caldav Task 1
CATEGORIES:tag1,tag2,tag3
CREATED:20230301T073337Z
LAST-MODIFIED:20230301T073337Z
BEGIN:VALARM
TRIGGER;VALUE=DATE-TIME:20230304T150000Z
ACTION:DISPLAY
END:VALARM
END:VTODO
END:VCALENDAR`
func TestCaldav(t *testing.T) {
t.Run("Delivers VTODO for project", func(t *testing.T) {
rec, err := newCaldavTestRequestWithUser(t, http.MethodGet, caldav.ProjectHandler, &testuser15, ``, nil, map[string]string{"project": "26"})
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), "BEGIN:VCALENDAR")
assert.Contains(t, rec.Body.String(), "PRODID:-//Vikunja Todo App//EN")
assert.Contains(t, rec.Body.String(), "X-WR-CALNAME:List 26 for Caldav tests")
assert.Contains(t, rec.Body.String(), "BEGIN:VTODO")
assert.Contains(t, rec.Body.String(), "END:VTODO")
assert.Contains(t, rec.Body.String(), "END:VCALENDAR")
})
t.Run("Import VTODO", func(t *testing.T) {
rec, err := newCaldavTestRequestWithUser(t, http.MethodPut, caldav.TaskHandler, &testuser15, vtodo, nil, map[string]string{"project": "26", "task": "uid"})
assert.NoError(t, err)
assert.Equal(t, rec.Result().StatusCode, 201)
})
t.Run("Export VTODO", func(t *testing.T) {
rec, err := newCaldavTestRequestWithUser(t, http.MethodGet, caldav.TaskHandler, &testuser15, ``, nil, map[string]string{"project": "26", "task": "uid-caldav-test"})
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), "BEGIN:VCALENDAR")
assert.Contains(t, rec.Body.String(), "SUMMARY:Title Caldav Test")
assert.Contains(t, rec.Body.String(), "DESCRIPTION:Description Caldav Test")
assert.Contains(t, rec.Body.String(), "DUE:20230301T150000Z")
assert.Contains(t, rec.Body.String(), "PRIORITY:3")
assert.Contains(t, rec.Body.String(), "CATEGORIES:Label #4")
assert.Contains(t, rec.Body.String(), "BEGIN:VALARM")
assert.Contains(t, rec.Body.String(), "TRIGGER;VALUE=DATE-TIME:20230304T150000Z")
assert.Contains(t, rec.Body.String(), "ACTION:DISPLAY")
assert.Contains(t, rec.Body.String(), "END:VALARM")
})
}

View File

@ -33,7 +33,6 @@ import (
"code.vikunja.io/api/pkg/modules/auth"
"code.vikunja.io/api/pkg/modules/keyvalue"
"code.vikunja.io/api/pkg/routes"
"code.vikunja.io/api/pkg/routes/caldav"
"code.vikunja.io/api/pkg/user"
"code.vikunja.io/web"
"code.vikunja.io/web/handler"
@ -50,12 +49,6 @@ var (
Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.",
Email: "user1@example.com",
}
testuser15 = user.User{
ID: 15,
Username: "user15",
Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.",
Email: "user15@example.com",
}
)
func setupTestEnv() (e *echo.Echo, err error) {
@ -152,19 +145,6 @@ func newTestRequestWithLinkShare(t *testing.T, method string, handler echo.Handl
return
}
func newCaldavTestRequestWithUser(t *testing.T, method string, handler echo.HandlerFunc, user *user.User, payload string, queryParams url.Values, urlParams map[string]string) (rec *httptest.ResponseRecorder, err error) {
rec, c := testRequestSetup(t, method, payload, queryParams, urlParams)
c.Request().Header.Set(echo.HeaderContentType, echo.MIMETextPlain)
result, _ := caldav.BasicAuth(user.Username, "1234", c)
if !result {
t.Error("BasicAuth for caldav failed")
t.FailNow()
}
err = handler(c)
return
}
func assertHandlerErrorCode(t *testing.T, err error, expectedErrorCode int) {
if err == nil {
t.Error("Error is nil")

View File

@ -42,14 +42,14 @@ func TestTaskCollection(t *testing.T) {
assert.NoError(t, err)
// Not using assert.Equal to avoid having the tests break every time we add new fixtures
assert.Contains(t, rec.Body.String(), `task #1`)
assert.Contains(t, rec.Body.String(), `task #2 `)
assert.Contains(t, rec.Body.String(), `task #3 `)
assert.Contains(t, rec.Body.String(), `task #4 `)
assert.Contains(t, rec.Body.String(), `task #5 `)
assert.Contains(t, rec.Body.String(), `task #6 `)
assert.Contains(t, rec.Body.String(), `task #7 `)
assert.Contains(t, rec.Body.String(), `task #8 `)
assert.Contains(t, rec.Body.String(), `task #9 `)
assert.Contains(t, rec.Body.String(), `task #2`)
assert.Contains(t, rec.Body.String(), `task #3`)
assert.Contains(t, rec.Body.String(), `task #4`)
assert.Contains(t, rec.Body.String(), `task #5`)
assert.Contains(t, rec.Body.String(), `task #6`)
assert.Contains(t, rec.Body.String(), `task #7`)
assert.Contains(t, rec.Body.String(), `task #8`)
assert.Contains(t, rec.Body.String(), `task #9`)
assert.Contains(t, rec.Body.String(), `task #10`)
assert.Contains(t, rec.Body.String(), `task #11`)
assert.Contains(t, rec.Body.String(), `task #12`)
@ -75,14 +75,14 @@ func TestTaskCollection(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"s": []string{"task #6"}}, urlParams)
assert.NoError(t, err)
assert.NotContains(t, rec.Body.String(), `task #1`)
assert.NotContains(t, rec.Body.String(), `task #2 `)
assert.NotContains(t, rec.Body.String(), `task #3 `)
assert.NotContains(t, rec.Body.String(), `task #4 `)
assert.NotContains(t, rec.Body.String(), `task #5 `)
assert.Contains(t, rec.Body.String(), `task #6 `)
assert.NotContains(t, rec.Body.String(), `task #7 `)
assert.NotContains(t, rec.Body.String(), `task #8 `)
assert.NotContains(t, rec.Body.String(), `task #9 `)
assert.NotContains(t, rec.Body.String(), `task #2`)
assert.NotContains(t, rec.Body.String(), `task #3`)
assert.NotContains(t, rec.Body.String(), `task #4`)
assert.NotContains(t, rec.Body.String(), `task #5`)
assert.Contains(t, rec.Body.String(), `task #6`)
assert.NotContains(t, rec.Body.String(), `task #7`)
assert.NotContains(t, rec.Body.String(), `task #8`)
assert.NotContains(t, rec.Body.String(), `task #9`)
assert.NotContains(t, rec.Body.String(), `task #10`)
assert.NotContains(t, rec.Body.String(), `task #11`)
assert.NotContains(t, rec.Body.String(), `task #12`)
@ -93,14 +93,14 @@ func TestTaskCollection(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"s": []string{"tASk #6"}}, urlParams)
assert.NoError(t, err)
assert.NotContains(t, rec.Body.String(), `task #1`)
assert.NotContains(t, rec.Body.String(), `task #2 `)
assert.NotContains(t, rec.Body.String(), `task #3 `)
assert.NotContains(t, rec.Body.String(), `task #4 `)
assert.NotContains(t, rec.Body.String(), `task #5 `)
assert.Contains(t, rec.Body.String(), `task #6 `)
assert.NotContains(t, rec.Body.String(), `task #7 `)
assert.NotContains(t, rec.Body.String(), `task #8 `)
assert.NotContains(t, rec.Body.String(), `task #9 `)
assert.NotContains(t, rec.Body.String(), `task #2`)
assert.NotContains(t, rec.Body.String(), `task #3`)
assert.NotContains(t, rec.Body.String(), `task #4`)
assert.NotContains(t, rec.Body.String(), `task #5`)
assert.Contains(t, rec.Body.String(), `task #6`)
assert.NotContains(t, rec.Body.String(), `task #7`)
assert.NotContains(t, rec.Body.String(), `task #8`)
assert.NotContains(t, rec.Body.String(), `task #9`)
assert.NotContains(t, rec.Body.String(), `task #10`)
assert.NotContains(t, rec.Body.String(), `task #11`)
assert.NotContains(t, rec.Body.String(), `task #12`)
@ -113,49 +113,49 @@ func TestTaskCollection(t *testing.T) {
t.Run("by priority", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}}, urlParams)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`)
assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`)
})
t.Run("by priority desc", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"desc"}}, urlParams)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[{"id":3,"title":"task #3 high prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":100,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-3","index":3,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":4,"title":"task #4 low prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":1`)
assert.Contains(t, rec.Body.String(), `[{"id":3,"title":"task #3 high prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":100,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-3","index":3,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":4,"title":"task #4 low prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":1`)
})
t.Run("by priority asc", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"asc"}}, urlParams)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`)
assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`)
})
// should equal duedate asc
t.Run("by due_date", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}}, urlParams)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminder_dates":null,"reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`)
assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminder_dates":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`)
})
t.Run("by duedate desc", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"desc"}}, urlParams)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminder_dates":null,"reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":6,"title":"task #6 lower due date`)
assert.Contains(t, rec.Body.String(), `[{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminder_dates":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":6,"title":"task #6 lower due date`)
})
// Due date without unix suffix
t.Run("by duedate asc without suffix", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"asc"}}, urlParams)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminder_dates":null,"reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`)
assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminder_dates":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`)
})
t.Run("by due_date without suffix", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}}, urlParams)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminder_dates":null,"reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`)
assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminder_dates":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`)
})
t.Run("by duedate desc without suffix", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"desc"}}, urlParams)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminder_dates":null,"reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":6,"title":"task #6 lower due date`)
assert.Contains(t, rec.Body.String(), `[{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminder_dates":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":6,"title":"task #6 lower due date`)
})
t.Run("by duedate asc", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"asc"}}, urlParams)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminder_dates":null,"reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`)
assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminder_dates":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`)
})
t.Run("invalid sort parameter", func(t *testing.T) {
_, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"loremipsum"}}, urlParams)
@ -171,10 +171,10 @@ func TestTaskCollection(t *testing.T) {
// Invalid parameter should not sort at all
rec, err := testHandler.testReadAllWithUser(url.Values{"sort": []string{"loremipsum"}}, urlParams)
assert.NoError(t, err)
assert.NotContains(t, rec.Body.String(), `[{"id":3,"title":"task #3 high prio","description":"","done":false,"due_date":0,"reminder_dates":null,"reminders":null,"repeat_after":0,"repeat_mode":0,"priority":100,"start_date":0,"end_date":0,"assignees":null,"labels":null,"hex_color":"","created":1543626724,"updated":1543626724,"created_by":{"id":0,"name":"","username":"","email":"","created":0,"updated":0}},{"id":4,"title":"task #4 low prio","description":"","done":false,"due_date":0,"reminder_dates":null,"repeat_after":0,"repeat_mode":0,"priority":1`)
assert.NotContains(t, rec.Body.String(), `{"id":4,"title":"task #4 low prio","description":"","done":false,"due_date":0,"reminder_dates":null,"reminders":null,"repeat_after":0,"repeat_mode":0,"priority":1,"start_date":0,"end_date":0,"assignees":null,"labels":null,"hex_color":"","created":1543626724,"updated":1543626724,"created_by":{"id":0,"name":"","username":"","email":"","created":0,"updated":0}},{"id":3,"title":"task #3 high prio","description":"","done":false,"due_date":0,"reminder_dates":null,"repeat_after":0,"repeat_mode":0,"priority":100,"start_date":0,"end_date":0,"assignees":null,"labels":null,"created":1543626724,"updated":1543626724,"created_by":{"id":0,"name":"","username":"","email":"","created":0,"updated":0}}]`)
assert.NotContains(t, rec.Body.String(), `[{"id":5,"title":"task #5 higher due date","description":"","done":false,"due_date":1543636724,"reminder_dates":null,"reminders":null,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":0,"end_date":0,"assignees":null,"labels":null,"hex_color":"","created":1543626724,"updated":1543626724,"created_by":{"id":0,"name":"","username":"","email":"","created":0,"updated":0}},{"id":6,"title":"task #6 lower due date"`)
assert.NotContains(t, rec.Body.String(), `{"id":6,"title":"task #6 lower due date","description":"","done":false,"due_date":1543616724,"reminder_dates":null,"reminders":null,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":0,"end_date":0,"assignees":null,"labels":null,"hex_color":"","created":1543626724,"updated":1543626724,"created_by":{"id":0,"name":"","username":"","email":"","created":0,"updated":0}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"due_date":1543636724,"reminder_dates":null,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":0,"end_date":0,"assignees":null,"labels":null,"created":1543626724,"updated":1543626724,"created_by":{"id":0,"name":"","username":"","email":"","created":0,"updated":0}}]`)
assert.NotContains(t, rec.Body.String(), `[{"id":3,"title":"task #3 high prio","description":"","done":false,"due_date":0,"reminder_dates":null,"repeat_after":0,"repeat_mode":0,"priority":100,"start_date":0,"end_date":0,"assignees":null,"labels":null,"hex_color":"","created":1543626724,"updated":1543626724,"created_by":{"id":0,"name":"","username":"","email":"","created":0,"updated":0}},{"id":4,"title":"task #4 low prio","description":"","done":false,"due_date":0,"reminder_dates":null,"repeat_after":0,"repeat_mode":0,"priority":1`)
assert.NotContains(t, rec.Body.String(), `{"id":4,"title":"task #4 low prio","description":"","done":false,"due_date":0,"reminder_dates":null,"repeat_after":0,"repeat_mode":0,"priority":1,"start_date":0,"end_date":0,"assignees":null,"labels":null,"hex_color":"","created":1543626724,"updated":1543626724,"created_by":{"id":0,"name":"","username":"","email":"","created":0,"updated":0}},{"id":3,"title":"task #3 high prio","description":"","done":false,"due_date":0,"reminder_dates":null,"repeat_after":0,"repeat_mode":0,"priority":100,"start_date":0,"end_date":0,"assignees":null,"labels":null,"created":1543626724,"updated":1543626724,"created_by":{"id":0,"name":"","username":"","email":"","created":0,"updated":0}}]`)
assert.NotContains(t, rec.Body.String(), `[{"id":5,"title":"task #5 higher due date","description":"","done":false,"due_date":1543636724,"reminder_dates":null,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":0,"end_date":0,"assignees":null,"labels":null,"hex_color":"","created":1543626724,"updated":1543626724,"created_by":{"id":0,"name":"","username":"","email":"","created":0,"updated":0}},{"id":6,"title":"task #6 lower due date"`)
assert.NotContains(t, rec.Body.String(), `{"id":6,"title":"task #6 lower due date","description":"","done":false,"due_date":1543616724,"reminder_dates":null,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":0,"end_date":0,"assignees":null,"labels":null,"hex_color":"","created":1543626724,"updated":1543626724,"created_by":{"id":0,"name":"","username":"","email":"","created":0,"updated":0}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"due_date":1543636724,"reminder_dates":null,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":0,"end_date":0,"assignees":null,"labels":null,"created":1543626724,"updated":1543626724,"created_by":{"id":0,"name":"","username":"","email":"","created":0,"updated":0}}]`)
})
})
t.Run("Filter", func(t *testing.T) {
@ -190,14 +190,14 @@ func TestTaskCollection(t *testing.T) {
)
assert.NoError(t, err)
assert.NotContains(t, rec.Body.String(), `task #1`)
assert.NotContains(t, rec.Body.String(), `task #2 `)
assert.NotContains(t, rec.Body.String(), `task #3 `)
assert.NotContains(t, rec.Body.String(), `task #4 `)
assert.Contains(t, rec.Body.String(), `task #5 `)
assert.Contains(t, rec.Body.String(), `task #6 `)
assert.Contains(t, rec.Body.String(), `task #7 `)
assert.Contains(t, rec.Body.String(), `task #8 `)
assert.Contains(t, rec.Body.String(), `task #9 `)
assert.NotContains(t, rec.Body.String(), `task #2`)
assert.NotContains(t, rec.Body.String(), `task #3`)
assert.NotContains(t, rec.Body.String(), `task #4`)
assert.Contains(t, rec.Body.String(), `task #5`)
assert.Contains(t, rec.Body.String(), `task #6`)
assert.Contains(t, rec.Body.String(), `task #7`)
assert.Contains(t, rec.Body.String(), `task #8`)
assert.Contains(t, rec.Body.String(), `task #9`)
assert.NotContains(t, rec.Body.String(), `task #10`)
assert.NotContains(t, rec.Body.String(), `task #11`)
assert.NotContains(t, rec.Body.String(), `task #12`)
@ -215,14 +215,14 @@ func TestTaskCollection(t *testing.T) {
)
assert.NoError(t, err)
assert.NotContains(t, rec.Body.String(), `task #1`)
assert.NotContains(t, rec.Body.String(), `task #2 `)
assert.NotContains(t, rec.Body.String(), `task #3 `)
assert.NotContains(t, rec.Body.String(), `task #4 `)
assert.NotContains(t, rec.Body.String(), `task #5 `)
assert.NotContains(t, rec.Body.String(), `task #6 `)
assert.Contains(t, rec.Body.String(), `task #7 `)
assert.NotContains(t, rec.Body.String(), `task #8 `)
assert.Contains(t, rec.Body.String(), `task #9 `)
assert.NotContains(t, rec.Body.String(), `task #2`)
assert.NotContains(t, rec.Body.String(), `task #3`)
assert.NotContains(t, rec.Body.String(), `task #4`)
assert.NotContains(t, rec.Body.String(), `task #5`)
assert.NotContains(t, rec.Body.String(), `task #6`)
assert.Contains(t, rec.Body.String(), `task #7`)
assert.NotContains(t, rec.Body.String(), `task #8`)
assert.Contains(t, rec.Body.String(), `task #9`)
assert.NotContains(t, rec.Body.String(), `task #10`)
assert.NotContains(t, rec.Body.String(), `task #11`)
assert.NotContains(t, rec.Body.String(), `task #12`)
@ -255,14 +255,14 @@ func TestTaskCollection(t *testing.T) {
)
assert.NoError(t, err)
assert.NotContains(t, rec.Body.String(), `task #1`)
assert.NotContains(t, rec.Body.String(), `task #2 `)
assert.NotContains(t, rec.Body.String(), `task #3 `)
assert.NotContains(t, rec.Body.String(), `task #4 `)
assert.Contains(t, rec.Body.String(), `task #5 `)
assert.Contains(t, rec.Body.String(), `task #6 `)
assert.Contains(t, rec.Body.String(), `task #7 `)
assert.NotContains(t, rec.Body.String(), `task #8 `)
assert.Contains(t, rec.Body.String(), `task #9 `)
assert.NotContains(t, rec.Body.String(), `task #2`)
assert.NotContains(t, rec.Body.String(), `task #3`)
assert.NotContains(t, rec.Body.String(), `task #4`)
assert.Contains(t, rec.Body.String(), `task #5`)
assert.Contains(t, rec.Body.String(), `task #6`)
assert.Contains(t, rec.Body.String(), `task #7`)
assert.NotContains(t, rec.Body.String(), `task #8`)
assert.Contains(t, rec.Body.String(), `task #9`)
assert.NotContains(t, rec.Body.String(), `task #10`)
assert.NotContains(t, rec.Body.String(), `task #11`)
assert.NotContains(t, rec.Body.String(), `task #12`)
@ -291,14 +291,14 @@ func TestTaskCollection(t *testing.T) {
)
assert.NoError(t, err)
assert.NotContains(t, rec.Body.String(), `task #1`)
assert.NotContains(t, rec.Body.String(), `task #2 `)
assert.NotContains(t, rec.Body.String(), `task #3 `)
assert.NotContains(t, rec.Body.String(), `task #4 `)
assert.Contains(t, rec.Body.String(), `task #5 `)
assert.Contains(t, rec.Body.String(), `task #6 `)
assert.Contains(t, rec.Body.String(), `task #7 `)
assert.Contains(t, rec.Body.String(), `task #8 `)
assert.Contains(t, rec.Body.String(), `task #9 `)
assert.NotContains(t, rec.Body.String(), `task #2`)
assert.NotContains(t, rec.Body.String(), `task #3`)
assert.NotContains(t, rec.Body.String(), `task #4`)
assert.Contains(t, rec.Body.String(), `task #5`)
assert.Contains(t, rec.Body.String(), `task #6`)
assert.Contains(t, rec.Body.String(), `task #7`)
assert.Contains(t, rec.Body.String(), `task #8`)
assert.Contains(t, rec.Body.String(), `task #9`)
assert.NotContains(t, rec.Body.String(), `task #10`)
assert.NotContains(t, rec.Body.String(), `task #11`)
assert.NotContains(t, rec.Body.String(), `task #12`)
@ -314,14 +314,14 @@ func TestTaskCollection(t *testing.T) {
assert.NoError(t, err)
// Not using assert.Equal to avoid having the tests break every time we add new fixtures
assert.Contains(t, rec.Body.String(), `task #1`)
assert.Contains(t, rec.Body.String(), `task #2 `)
assert.Contains(t, rec.Body.String(), `task #3 `)
assert.Contains(t, rec.Body.String(), `task #4 `)
assert.Contains(t, rec.Body.String(), `task #5 `)
assert.Contains(t, rec.Body.String(), `task #6 `)
assert.Contains(t, rec.Body.String(), `task #7 `)
assert.Contains(t, rec.Body.String(), `task #8 `)
assert.Contains(t, rec.Body.String(), `task #9 `)
assert.Contains(t, rec.Body.String(), `task #2`)
assert.Contains(t, rec.Body.String(), `task #3`)
assert.Contains(t, rec.Body.String(), `task #4`)
assert.Contains(t, rec.Body.String(), `task #5`)
assert.Contains(t, rec.Body.String(), `task #6`)
assert.Contains(t, rec.Body.String(), `task #7`)
assert.Contains(t, rec.Body.String(), `task #8`)
assert.Contains(t, rec.Body.String(), `task #9`)
assert.Contains(t, rec.Body.String(), `task #10`)
assert.Contains(t, rec.Body.String(), `task #11`)
assert.Contains(t, rec.Body.String(), `task #12`)
@ -347,14 +347,14 @@ func TestTaskCollection(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"s": []string{"task #6"}}, nil)
assert.NoError(t, err)
assert.NotContains(t, rec.Body.String(), `task #1`)
assert.NotContains(t, rec.Body.String(), `task #2 `)
assert.NotContains(t, rec.Body.String(), `task #3 `)
assert.NotContains(t, rec.Body.String(), `task #4 `)
assert.NotContains(t, rec.Body.String(), `task #5 `)
assert.Contains(t, rec.Body.String(), `task #6 `)
assert.NotContains(t, rec.Body.String(), `task #7 `)
assert.NotContains(t, rec.Body.String(), `task #8 `)
assert.NotContains(t, rec.Body.String(), `task #9 `)
assert.NotContains(t, rec.Body.String(), `task #2`)
assert.NotContains(t, rec.Body.String(), `task #3`)
assert.NotContains(t, rec.Body.String(), `task #4`)
assert.NotContains(t, rec.Body.String(), `task #5`)
assert.Contains(t, rec.Body.String(), `task #6`)
assert.NotContains(t, rec.Body.String(), `task #7`)
assert.NotContains(t, rec.Body.String(), `task #8`)
assert.NotContains(t, rec.Body.String(), `task #9`)
assert.NotContains(t, rec.Body.String(), `task #10`)
assert.NotContains(t, rec.Body.String(), `task #11`)
assert.NotContains(t, rec.Body.String(), `task #12`)
@ -366,42 +366,42 @@ func TestTaskCollection(t *testing.T) {
t.Run("by priority", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}}, nil)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`)
assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`)
})
t.Run("by priority desc", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"desc"}}, nil)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[{"id":3,"title":"task #3 high prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":100,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-3","index":3,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":4,"title":"task #4 low prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":1`)
assert.Contains(t, rec.Body.String(), `[{"id":3,"title":"task #3 high prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":100,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-3","index":3,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":4,"title":"task #4 low prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":1`)
})
t.Run("by priority asc", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"asc"}}, nil)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`)
assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`)
})
// should equal duedate asc
t.Run("by due_date", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}}, nil)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminder_dates":null,"reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminder_dates":null,"reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`)
assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminder_dates":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminder_dates":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`)
})
t.Run("by duedate desc", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"desc"}}, nil)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminder_dates":null,"reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":6,"title":"task #6 lower due date`)
assert.Contains(t, rec.Body.String(), `[{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminder_dates":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":6,"title":"task #6 lower due date`)
})
t.Run("by duedate asc", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"asc"}}, nil)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminder_dates":null,"reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminder_dates":null,"reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`)
assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminder_dates":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminder_dates":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`)
})
t.Run("invalid parameter", func(t *testing.T) {
// Invalid parameter should not sort at all
rec, err := testHandler.testReadAllWithUser(url.Values{"sort": []string{"loremipsum"}}, nil)
assert.NoError(t, err)
assert.NotContains(t, rec.Body.String(), `[{"id":3,"title":"task #3 high prio","description":"","done":false,"due_date":0,"reminder_dates":null,"reminders":null,"repeat_after":0,"repeat_mode":0,"priority":100,"start_date":0,"end_date":0,"assignees":null,"labels":null,"hex_color":"","created":1543626724,"updated":1543626724,"created_by":{"id":0,"name":"","username":"","email":"","created":0,"updated":0}},{"id":4,"title":"task #4 low prio","description":"","done":false,"due_date":0,"reminder_dates":null,"repeat_after":0,"repeat_mode":0,"priority":1`)
assert.NotContains(t, rec.Body.String(), `{"id":4,"title":"task #4 low prio","description":"","done":false,"due_date":0,"reminder_dates":null,"reminders":null,"repeat_after":0,"repeat_mode":0,"priority":1,"start_date":0,"end_date":0,"assignees":null,"labels":null,"hex_color":"","created":1543626724,"updated":1543626724,"created_by":{"id":0,"name":"","username":"","email":"","created":0,"updated":0}},{"id":3,"title":"task #3 high prio","description":"","done":false,"due_date":0,"reminder_dates":null,"repeat_after":0,"repeat_mode":0,"priority":100,"start_date":0,"end_date":0,"assignees":null,"labels":null,"created":1543626724,"updated":1543626724,"created_by":{"id":0,"name":"","username":"","email":"","created":0,"updated":0}}]`)
assert.NotContains(t, rec.Body.String(), `[{"id":5,"title":"task #5 higher due date","description":"","done":false,"due_date":1543636724,"reminder_dates":null,"reminders":null,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":0,"end_date":0,"assignees":null,"labels":null,"hex_color":"","created":1543626724,"updated":1543626724,"created_by":{"id":0,"name":"","username":"","email":"","created":0,"updated":0}},{"id":6,"title":"task #6 lower due date"`)
assert.NotContains(t, rec.Body.String(), `{"id":6,"title":"task #6 lower due date","description":"","done":false,"due_date":1543616724,"reminder_dates":null,"reminders":null,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":0,"end_date":0,"assignees":null,"labels":null,"hex_color":"","created":1543626724,"updated":1543626724,"created_by":{"id":0,"name":"","username":"","email":"","created":0,"updated":0}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"due_date":1543636724,"reminder_dates":null,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":0,"end_date":0,"assignees":null,"labels":null,"created":1543626724,"updated":1543626724,"created_by":{"id":0,"name":"","username":"","email":"","created":0,"updated":0}}]`)
assert.NotContains(t, rec.Body.String(), `[{"id":3,"title":"task #3 high prio","description":"","done":false,"due_date":0,"reminder_dates":null,"repeat_after":0,"repeat_mode":0,"priority":100,"start_date":0,"end_date":0,"assignees":null,"labels":null,"hex_color":"","created":1543626724,"updated":1543626724,"created_by":{"id":0,"name":"","username":"","email":"","created":0,"updated":0}},{"id":4,"title":"task #4 low prio","description":"","done":false,"due_date":0,"reminder_dates":null,"repeat_after":0,"repeat_mode":0,"priority":1`)
assert.NotContains(t, rec.Body.String(), `{"id":4,"title":"task #4 low prio","description":"","done":false,"due_date":0,"reminder_dates":null,"repeat_after":0,"repeat_mode":0,"priority":1,"start_date":0,"end_date":0,"assignees":null,"labels":null,"hex_color":"","created":1543626724,"updated":1543626724,"created_by":{"id":0,"name":"","username":"","email":"","created":0,"updated":0}},{"id":3,"title":"task #3 high prio","description":"","done":false,"due_date":0,"reminder_dates":null,"repeat_after":0,"repeat_mode":0,"priority":100,"start_date":0,"end_date":0,"assignees":null,"labels":null,"created":1543626724,"updated":1543626724,"created_by":{"id":0,"name":"","username":"","email":"","created":0,"updated":0}}]`)
assert.NotContains(t, rec.Body.String(), `[{"id":5,"title":"task #5 higher due date","description":"","done":false,"due_date":1543636724,"reminder_dates":null,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":0,"end_date":0,"assignees":null,"labels":null,"hex_color":"","created":1543626724,"updated":1543626724,"created_by":{"id":0,"name":"","username":"","email":"","created":0,"updated":0}},{"id":6,"title":"task #6 lower due date"`)
assert.NotContains(t, rec.Body.String(), `{"id":6,"title":"task #6 lower due date","description":"","done":false,"due_date":1543616724,"reminder_dates":null,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":0,"end_date":0,"assignees":null,"labels":null,"hex_color":"","created":1543626724,"updated":1543626724,"created_by":{"id":0,"name":"","username":"","email":"","created":0,"updated":0}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"due_date":1543636724,"reminder_dates":null,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":0,"end_date":0,"assignees":null,"labels":null,"created":1543626724,"updated":1543626724,"created_by":{"id":0,"name":"","username":"","email":"","created":0,"updated":0}}]`)
})
})
t.Run("Filter", func(t *testing.T) {
@ -417,14 +417,14 @@ func TestTaskCollection(t *testing.T) {
)
assert.NoError(t, err)
assert.NotContains(t, rec.Body.String(), `task #1`)
assert.NotContains(t, rec.Body.String(), `task #2 `)
assert.NotContains(t, rec.Body.String(), `task #3 `)
assert.NotContains(t, rec.Body.String(), `task #4 `)
assert.Contains(t, rec.Body.String(), `task #5 `)
assert.Contains(t, rec.Body.String(), `task #6 `)
assert.Contains(t, rec.Body.String(), `task #7 `)
assert.Contains(t, rec.Body.String(), `task #8 `)
assert.Contains(t, rec.Body.String(), `task #9 `)
assert.NotContains(t, rec.Body.String(), `task #2`)
assert.NotContains(t, rec.Body.String(), `task #3`)
assert.NotContains(t, rec.Body.String(), `task #4`)
assert.Contains(t, rec.Body.String(), `task #5`)
assert.Contains(t, rec.Body.String(), `task #6`)
assert.Contains(t, rec.Body.String(), `task #7`)
assert.Contains(t, rec.Body.String(), `task #8`)
assert.Contains(t, rec.Body.String(), `task #9`)
assert.NotContains(t, rec.Body.String(), `task #10`)
assert.NotContains(t, rec.Body.String(), `task #11`)
assert.NotContains(t, rec.Body.String(), `task #12`)
@ -442,14 +442,14 @@ func TestTaskCollection(t *testing.T) {
)
assert.NoError(t, err)
assert.NotContains(t, rec.Body.String(), `task #1`)
assert.NotContains(t, rec.Body.String(), `task #2 `)
assert.NotContains(t, rec.Body.String(), `task #3 `)
assert.NotContains(t, rec.Body.String(), `task #4 `)
assert.NotContains(t, rec.Body.String(), `task #5 `)
assert.NotContains(t, rec.Body.String(), `task #6 `)
assert.Contains(t, rec.Body.String(), `task #7 `)
assert.NotContains(t, rec.Body.String(), `task #8 `)
assert.Contains(t, rec.Body.String(), `task #9 `)
assert.NotContains(t, rec.Body.String(), `task #2`)
assert.NotContains(t, rec.Body.String(), `task #3`)
assert.NotContains(t, rec.Body.String(), `task #4`)
assert.NotContains(t, rec.Body.String(), `task #5`)
assert.NotContains(t, rec.Body.String(), `task #6`)
assert.Contains(t, rec.Body.String(), `task #7`)
assert.NotContains(t, rec.Body.String(), `task #8`)
assert.Contains(t, rec.Body.String(), `task #9`)
assert.NotContains(t, rec.Body.String(), `task #10`)
assert.NotContains(t, rec.Body.String(), `task #11`)
assert.NotContains(t, rec.Body.String(), `task #12`)

View File

@ -95,47 +95,24 @@ func TestTask(t *testing.T) {
assert.Contains(t, rec.Body.String(), `"due_date":"0001-01-01T00:00:00Z"`)
assert.NotContains(t, rec.Body.String(), `"due_date":"2020-02-10T10:00:00Z"`)
})
// Deprecated: Remove if ReminderDates is removed
t.Run("ReminderDates", func(t *testing.T) {
t.Run("Reminders", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "1"}, `{"reminder_dates": ["2020-02-10T10:00:00Z","2020-02-11T10:00:00Z"]}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"reminder_dates":["2020-02-10T10:00:00Z","2020-02-11T10:00:00Z"]`)
assert.NotContains(t, rec.Body.String(), `"reminder_dates": null`)
})
// Deprecated: Remove if ReminderDates is removed
t.Run("ReminderDates unset to empty array", func(t *testing.T) {
t.Run("Reminders unset to empty array", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "27"}, `{"reminder_dates": []}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"reminder_dates":null`)
assert.NotContains(t, rec.Body.String(), `"reminder_dates":[1543626724,1543626824]`)
})
// Deprecated: Remove if ReminderDates is removed
t.Run("ReminderDates unset to null", func(t *testing.T) {
t.Run("Reminders unset to null", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "27"}, `{"reminder_dates": null}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"reminder_dates":null`)
assert.NotContains(t, rec.Body.String(), `"reminder_dates":[1543626724,1543626824]`)
})
t.Run("Reminders", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "1"}, `{"reminders": [{"reminder": "2020-02-10T10:00:00Z"},{"reminder": "2020-02-11T10:00:00Z"}]}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"reminders":[`)
assert.Contains(t, rec.Body.String(), `{"reminder":"2020-02-10T10:00:00Z"`)
assert.Contains(t, rec.Body.String(), `{"reminder":"2020-02-11T10:00:00Z"`)
assert.NotContains(t, rec.Body.String(), `"reminders":null`)
})
t.Run("Reminders unset to empty array", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "27"}, `{"reminders": []}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"reminders":null`)
assert.NotContains(t, rec.Body.String(), `{"Reminder":"2020-02-10T10:00:00Z"`)
})
t.Run("Reminders unset to null", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "27"}, `{"reminders": null}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"reminder_dates":null`)
assert.NotContains(t, rec.Body.String(), `{"Reminder":"2020-02-10T10:00:00Z"`)
})
t.Run("Repeat after", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "1"}, `{"repeat_after":3600}`)
assert.NoError(t, err)

View File

@ -1,87 +0,0 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 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/>.
package log
import (
"strings"
"time"
"code.vikunja.io/api/pkg/config"
"github.com/op/go-logging"
"xorm.io/xorm/log"
)
type MailLogger struct {
logger *logging.Logger
level log.LogLevel
}
const mailFormat = `%{color}%{time:` + time.RFC3339Nano + `}: %{level}` + "\t" + `▶ [MAIL] %{id:03x}%{color:reset} %{message}`
const mailLogModule = `vikunja_mail`
func NewMailLogger() *MailLogger {
lvl := strings.ToUpper(config.LogMailLevel.GetString())
level, err := logging.LogLevel(lvl)
if err != nil {
Criticalf("Error setting database log level: %s", err.Error())
}
mailLogger := &MailLogger{
logger: logging.MustGetLogger(mailLogModule),
}
logBackend := logging.NewLogBackend(GetLogWriter("mail"), "", 0)
backend := logging.NewBackendFormatter(logBackend, logging.MustStringFormatter(mailFormat+"\n"))
backendLeveled := logging.AddModuleLevel(backend)
backendLeveled.SetLevel(level, mailLogModule)
mailLogger.logger.SetBackend(backendLeveled)
switch level {
case logging.CRITICAL:
case logging.ERROR:
mailLogger.level = log.LOG_ERR
case logging.WARNING:
mailLogger.level = log.LOG_WARNING
case logging.NOTICE:
case logging.INFO:
mailLogger.level = log.LOG_INFO
case logging.DEBUG:
mailLogger.level = log.LOG_DEBUG
default:
mailLogger.level = log.LOG_OFF
}
return mailLogger
}
func (m *MailLogger) Errorf(format string, v ...interface{}) {
m.logger.Errorf(format, v...)
}
func (m *MailLogger) Warnf(format string, v ...interface{}) {
m.logger.Warningf(format, v...)
}
func (m *MailLogger) Infof(format string, v ...interface{}) {
m.logger.Infof(format, v...)
}
func (m *MailLogger) Debugf(format string, v ...interface{}) {
m.logger.Debugf(format, v...)
}

View File

@ -23,6 +23,6 @@ import (
// NoopBackend doesn't log anything. Used in cases where we want to disable logging completely.
type NoopBackend struct{}
func (n *NoopBackend) Log(_ logging.Level, _ int, _ *logging.Record) error {
func (n *NoopBackend) Log(level logging.Level, i int, record *logging.Record) error {
return nil
}

View File

@ -91,6 +91,6 @@ func (w *WatermillLogger) Trace(msg string, fields watermill.LogFields) {
w.logger.Debugf("%s, %s", msg, concatFields(fields))
}
func (w *WatermillLogger) With(_ watermill.LogFields) watermill.LoggerAdapter {
func (w *WatermillLogger) With(fields watermill.LogFields) watermill.LoggerAdapter {
return w
}

View File

@ -56,8 +56,6 @@ func getClient() (*mail.Client, error) {
ServerName: config.MailerHost.GetString(),
}),
mail.WithTimeout((config.MailerQueueTimeout.GetDuration() + 3) * time.Second), // 3s more for us to close before mail server timeout
mail.WithLogger(log.NewMailLogger()),
mail.WithDebugLog(),
}
if config.MailerForceSSL.GetBool() {

View File

@ -1,44 +0,0 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 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/>.
package migration
import (
"src.techknowlogick.com/xormigrate"
"xorm.io/xorm"
)
type taskReminders20230307171848 struct {
RelativePeriod int64 `xorm:"bigint null" json:"relative_period"`
RelativeTo string `xorm:"varchar(50) null" json:"relative_to,omitempty"`
}
func (taskReminders20230307171848) TableName() string {
return "task_reminders"
}
func init() {
migrations = append(migrations, &xormigrate.Migration{
ID: "20230307171848",
Description: "Add relative period to task reminders",
Migrate: func(tx *xorm.Engine) error {
return tx.Sync2(taskReminders20230307171848{})
},
Rollback: func(tx *xorm.Engine) error {
return nil
},
})
}

View File

@ -109,7 +109,10 @@ func Rollback(migrationID string) {
// MigrateTo executes all migrations up to a certain point
func MigrateTo(migrationID string, x *xorm.Engine) error {
m := initMigration(x)
return m.MigrateTo(migrationID)
if err := m.MigrateTo(migrationID); err != nil {
return err
}
return nil
}
// Deletes a column from a table. All arguments are strings, to let them be standalone and not depending on any struct.

View File

@ -875,33 +875,6 @@ func (err ErrUserAlreadyAssigned) HTTPError() web.HTTPError {
}
}
// ErrReminderRelativeToMissing represents an error where a task has a relative reminder without reference date
type ErrReminderRelativeToMissing struct {
TaskID int64
}
// IsErrReminderRelativeToMissing checks if an error is ErrReminderRelativeToMissing.
func IsErrReminderRelativeToMissing(err error) bool {
_, ok := err.(ErrReminderRelativeToMissing)
return ok
}
func (err ErrReminderRelativeToMissing) Error() string {
return fmt.Sprintf("Task [TaskID: %v] has a relative reminder without relative_to", err.TaskID)
}
// ErrCodeRelationDoesNotExist holds the unique world-error code of this error
const ErrCodeReminderRelativeToMissing = 4022
// HTTPError holds the http error description
func (err ErrReminderRelativeToMissing) HTTPError() web.HTTPError {
return web.HTTPError{
HTTPCode: http.StatusBadRequest,
Code: ErrCodeReminderRelativeToMissing,
Message: "Please provide what the reminder date is relative to",
}
}
// =================
// Namespace errors
// =================

View File

@ -80,18 +80,6 @@ func (t *TaskAssigneeCreatedEvent) Name() string {
return "task.assignee.created"
}
// TaskAssigneeDeletedEvent represents a TaskAssigneeDeletedEvent event
type TaskAssigneeDeletedEvent struct {
Task *Task
Assignee *user.User
Doer *user.User
}
// Name defines the name for TaskAssigneeDeletedEvent
func (t *TaskAssigneeDeletedEvent) Name() string {
return "task.assignee.deleted"
}
// TaskCommentCreatedEvent represents an event where a task comment has been created
type TaskCommentCreatedEvent struct {
Task *Task
@ -116,66 +104,6 @@ func (t *TaskCommentUpdatedEvent) Name() string {
return "task.comment.edited"
}
// TaskCommentDeletedEvent represents a TaskCommentDeletedEvent event
type TaskCommentDeletedEvent struct {
Task *Task
Comment *TaskComment
Doer *user.User
}
// Name defines the name for TaskCommentDeletedEvent
func (t *TaskCommentDeletedEvent) Name() string {
return "task.comment.deleted"
}
// TaskAttachmentCreatedEvent represents a TaskAttachmentCreatedEvent event
type TaskAttachmentCreatedEvent struct {
Task *Task
Attachment *TaskAttachment
Doer *user.User
}
// Name defines the name for TaskAttachmentCreatedEvent
func (t *TaskAttachmentCreatedEvent) Name() string {
return "task.attachment.created"
}
// TaskAttachmentDeletedEvent represents a TaskAttachmentDeletedEvent event
type TaskAttachmentDeletedEvent struct {
Task *Task
Attachment *TaskAttachment
Doer *user.User
}
// Name defines the name for TaskAttachmentDeletedEvent
func (t *TaskAttachmentDeletedEvent) Name() string {
return "task.attachment.deleted"
}
// TaskRelationCreatedEvent represents a TaskRelationCreatedEvent event
type TaskRelationCreatedEvent struct {
Task *Task
Relation *TaskRelation
Doer *user.User
}
// Name defines the name for TaskRelationCreatedEvent
func (t *TaskRelationCreatedEvent) Name() string {
return "task.relation.created"
}
// TaskRelationDeletedEvent represents a TaskRelationDeletedEvent event
type TaskRelationDeletedEvent struct {
Task *Task
Relation *TaskRelation
Doer *user.User
}
// Name defines the name for TaskRelationDeletedEvent
func (t *TaskRelationDeletedEvent) Name() string {
return "task.relation.deleted"
}
//////////////////////
// Namespace Events //
//////////////////////

View File

@ -281,7 +281,7 @@ func (b *Bucket) Create(s *xorm.Session, a web.Auth) (err error) {
// @Failure 404 {object} web.HTTPError "The bucket does not exist."
// @Failure 500 {object} models.Message "Internal error"
// @Router /projects/{projectID}/buckets/{bucketID} [post]
func (b *Bucket) Update(s *xorm.Session, _ web.Auth) (err error) {
func (b *Bucket) Update(s *xorm.Session, a web.Auth) (err error) {
doneBucket, err := getDoneBucketForProject(s, b.ProjectID)
if err != nil {
return err
@ -320,7 +320,7 @@ func (b *Bucket) Update(s *xorm.Session, _ web.Auth) (err error) {
// @Failure 404 {object} web.HTTPError "The bucket does not exist."
// @Failure 500 {object} models.Message "Internal error"
// @Router /projects/{projectID}/buckets/{bucketID} [delete]
func (b *Bucket) Delete(s *xorm.Session, _ web.Auth) (err error) {
func (b *Bucket) Delete(s *xorm.Session, a web.Auth) (err error) {
// Prevent removing the last bucket
total, err := s.Where("project_id = ?", b.ProjectID).Count(&Bucket{})

View File

@ -147,7 +147,7 @@ func TestBucket_Delete(t *testing.T) {
tasks := []*Task{}
err = s.Where("bucket_id = ?", 1).Find(&tasks)
assert.NoError(t, err)
assert.Len(t, tasks, 16)
assert.Len(t, tasks, 15)
db.AssertMissing(t, "buckets", map[string]interface{}{
"id": 2,
"project_id": 1,

View File

@ -123,7 +123,7 @@ func (l *Label) Update(s *xorm.Session, a web.Auth) (err error) {
// @Failure 404 {object} web.HTTPError "Label not found."
// @Failure 500 {object} models.Message "Internal error"
// @Router /labels/{id} [delete]
func (l *Label) Delete(s *xorm.Session, _ web.Auth) (err error) {
func (l *Label) Delete(s *xorm.Session, a web.Auth) (err error) {
_, err = s.ID(l.ID).Delete(&Label{})
return err
}
@ -151,8 +151,8 @@ func (l *Label) ReadAll(s *xorm.Session, a web.Auth, search string, page int, pe
return nil, 0, 0, err
}
return GetLabelsByTaskIDs(s, &LabelByTaskIDsOptions{
Search: []string{search},
return getLabelsByTaskIDs(s, &LabelByTaskIDsOptions{
Search: search,
User: u,
GetForUser: u.ID,
Page: page,
@ -175,16 +175,16 @@ func (l *Label) ReadAll(s *xorm.Session, a web.Auth, search string, page int, pe
// @Failure 404 {object} web.HTTPError "Label not found"
// @Failure 500 {object} models.Message "Internal error"
// @Router /labels/{id} [get]
func (l *Label) ReadOne(s *xorm.Session, _ web.Auth) (err error) {
func (l *Label) ReadOne(s *xorm.Session, a web.Auth) (err error) {
label, err := getLabelByIDSimple(s, l.ID)
if err != nil {
return
return err
}
*l = *label
u, err := user.GetUserByID(s, l.CreatedByID)
if err != nil {
return
return err
}
l.CreatedBy = u
@ -192,16 +192,14 @@ func (l *Label) ReadOne(s *xorm.Session, _ web.Auth) (err error) {
}
func getLabelByIDSimple(s *xorm.Session, labelID int64) (*Label, error) {
return GetLabelSimple(s, &Label{ID: labelID})
}
func GetLabelSimple(s *xorm.Session, l *Label) (*Label, error) {
exists, err := s.Get(l)
label := Label{}
exists, err := s.ID(labelID).Get(&label)
if err != nil {
return l, err
return &label, err
}
if !exists {
return &Label{}, ErrLabelDoesNotExist{l.ID}
return &Label{}, ErrLabelDoesNotExist{labelID}
}
return l, err
return &label, err
}

View File

@ -40,7 +40,7 @@ func (l *Label) CanRead(s *xorm.Session, a web.Auth) (bool, int, error) {
// CanCreate checks if the user can create a label
// Currently a dummy.
func (l *Label) CanCreate(_ *xorm.Session, a web.Auth) (bool, error) {
func (l *Label) CanCreate(s *xorm.Session, a web.Auth) (bool, error) {
if _, is := a.(*LinkSharing); is {
return false, nil
}

View File

@ -64,7 +64,7 @@ func (LabelTask) TableName() string {
// @Failure 404 {object} web.HTTPError "Label not found."
// @Failure 500 {object} models.Message "Internal error"
// @Router /tasks/{task}/labels/{label} [delete]
func (lt *LabelTask) Delete(s *xorm.Session, _ web.Auth) (err error) {
func (lt *LabelTask) Delete(s *xorm.Session, a web.Auth) (err error) {
_, err = s.Delete(&LabelTask{LabelID: lt.LabelID, TaskID: lt.TaskID})
return err
}
@ -84,7 +84,7 @@ func (lt *LabelTask) Delete(s *xorm.Session, _ web.Auth) (err error) {
// @Failure 404 {object} web.HTTPError "The label does not exist."
// @Failure 500 {object} models.Message "Internal error"
// @Router /tasks/{task}/labels [put]
func (lt *LabelTask) Create(s *xorm.Session, _ web.Auth) (err error) {
func (lt *LabelTask) Create(s *xorm.Session, a web.Auth) (err error) {
// Check if the label is already added
exists, err := s.Exist(&LabelTask{LabelID: lt.LabelID, TaskID: lt.TaskID})
if err != nil {
@ -118,7 +118,7 @@ func (lt *LabelTask) Create(s *xorm.Session, _ web.Auth) (err error) {
// @Success 200 {array} models.Label "The labels"
// @Failure 500 {object} models.Message "Internal error"
// @Router /tasks/{task}/labels [get]
func (lt *LabelTask) ReadAll(s *xorm.Session, a web.Auth, search string, page int, _ int) (result interface{}, resultCount int, numberOfTotalItems int64, err error) {
func (lt *LabelTask) ReadAll(s *xorm.Session, a web.Auth, search string, page int, perPage int) (result interface{}, resultCount int, numberOfTotalItems int64, err error) {
// Check if the user has the right to see the task
task := Task{ID: lt.TaskID}
canRead, _, err := task.CanRead(s, a)
@ -129,16 +129,16 @@ func (lt *LabelTask) ReadAll(s *xorm.Session, a web.Auth, search string, page in
return nil, 0, 0, ErrNoRightToSeeTask{lt.TaskID, a.GetID()}
}
return GetLabelsByTaskIDs(s, &LabelByTaskIDsOptions{
return getLabelsByTaskIDs(s, &LabelByTaskIDsOptions{
User: &user.User{ID: a.GetID()},
Search: []string{search},
Search: search,
Page: page,
TaskIDs: []int64{lt.TaskID},
})
}
// LabelWithTaskID is a helper struct, contains the label + its task ID
type LabelWithTaskID struct {
// Helper struct, contains the label + its task ID
type labelWithTaskID struct {
TaskID int64 `json:"-"`
Label `xorm:"extends"`
}
@ -146,7 +146,7 @@ type LabelWithTaskID struct {
// LabelByTaskIDsOptions is a struct to not clutter the function with too many optional parameters.
type LabelByTaskIDsOptions struct {
User *user.User
Search []string
Search string
Page int
PerPage int
TaskIDs []int64
@ -155,9 +155,9 @@ type LabelByTaskIDsOptions struct {
GetForUser int64
}
// GetLabelsByTaskIDs is a helper function to get all labels for a set of tasks
// Helper function to get all labels for a set of tasks
// Used when getting all labels for one task as well when getting all lables
func GetLabelsByTaskIDs(s *xorm.Session, opts *LabelByTaskIDsOptions) (ls []*LabelWithTaskID, resultCount int, totalEntries int64, err error) {
func getLabelsByTaskIDs(s *xorm.Session, opts *LabelByTaskIDsOptions) (ls []*labelWithTaskID, resultCount int, totalEntries int64, err error) {
// We still need the task ID when we want to get all labels for a task, but because of this, we get the same label
// multiple times when it is associated to more than one task.
// Because of this whole thing, we need this extra switch here to only group by Task IDs if needed.
@ -170,7 +170,7 @@ func GetLabelsByTaskIDs(s *xorm.Session, opts *LabelByTaskIDsOptions) (ls []*Lab
}
// Get all labels associated with these tasks
var labels []*LabelWithTaskID
var labels []*labelWithTaskID
cond := builder.And(builder.NotNull{"label_tasks.label_id"})
if len(opts.TaskIDs) > 0 && opts.GetForUser == 0 {
cond = builder.And(builder.In("label_tasks.task_id", opts.TaskIDs), cond)
@ -188,14 +188,8 @@ func GetLabelsByTaskIDs(s *xorm.Session, opts *LabelByTaskIDsOptions) (ls []*Lab
}
ids := []int64{}
for _, search := range opts.Search {
search = strings.Trim(search, " ")
if search == "" {
continue
}
vals := strings.Split(search, ",")
if opts.Search != "" {
vals := strings.Split(opts.Search, ",")
for _, val := range vals {
v, err := strconv.ParseInt(val, 10, 64)
if err != nil {
@ -208,19 +202,8 @@ func GetLabelsByTaskIDs(s *xorm.Session, opts *LabelByTaskIDsOptions) (ls []*Lab
if len(ids) > 0 {
cond = builder.And(cond, builder.In("labels.id", ids))
} else if len(opts.Search) > 0 {
var searchcond builder.Cond
for _, search := range opts.Search {
search = strings.Trim(search, " ")
if search == "" {
continue
}
searchcond = builder.Or(searchcond, db.ILIKE("labels.title", search))
}
cond = builder.And(cond, searchcond)
} else {
cond = builder.And(cond, db.ILIKE("labels.title", opts.Search))
}
limit, start := getLimitFromPageIndex(opts.Page, opts.PerPage)
@ -271,6 +254,7 @@ func GetLabelsByTaskIDs(s *xorm.Session, opts *LabelByTaskIDsOptions) (ls []*Lab
Select("count(DISTINCT labels.id)").
Join("LEFT", "label_tasks", "label_tasks.label_id = labels.id").
Where(cond).
And("labels.title LIKE ?", "%"+opts.Search+"%").
Count(&Label{})
if err != nil {
return nil, 0, 0, err
@ -280,7 +264,7 @@ func GetLabelsByTaskIDs(s *xorm.Session, opts *LabelByTaskIDsOptions) (ls []*Lab
}
// Create or update a bunch of task labels
func (t *Task) UpdateTaskLabels(s *xorm.Session, creator web.Auth, labels []*Label) (err error) {
func (t *Task) updateTaskLabels(s *xorm.Session, creator web.Auth, labels []*Label) (err error) {
// If we don't have any new labels, delete everything right away. Saves us some hassle.
if len(labels) == 0 && len(t.Labels) > 0 {
@ -397,7 +381,7 @@ func (ltb *LabelTaskBulk) Create(s *xorm.Session, a web.Auth) (err error) {
if err != nil {
return
}
labels, _, _, err := GetLabelsByTaskIDs(s, &LabelByTaskIDsOptions{
labels, _, _, err := getLabelsByTaskIDs(s, &LabelByTaskIDsOptions{
TaskIDs: []int64{ltb.TaskID},
})
if err != nil {
@ -406,5 +390,5 @@ func (ltb *LabelTaskBulk) Create(s *xorm.Session, a web.Auth) (err error) {
for _, l := range labels {
task.Labels = append(task.Labels, &l.Label)
}
return task.UpdateTaskLabels(s, a, ltb.Labels)
return task.updateTaskLabels(s, a, ltb.Labels)
}

View File

@ -78,7 +78,7 @@ func TestLabelTask_ReadAll(t *testing.T) {
args: args{
a: &user.User{ID: 1},
},
wantLabels: []*LabelWithTaskID{
wantLabels: []*labelWithTaskID{
{
TaskID: 1,
Label: label,
@ -116,7 +116,7 @@ func TestLabelTask_ReadAll(t *testing.T) {
a: &user.User{ID: 1},
search: "VISIBLE",
},
wantLabels: []*LabelWithTaskID{
wantLabels: []*labelWithTaskID{
{
TaskID: 1,
Label: label,

View File

@ -70,7 +70,7 @@ func TestLabel_ReadAll(t *testing.T) {
args: args{
a: &user.User{ID: 1},
},
wantLs: []*LabelWithTaskID{
wantLs: []*labelWithTaskID{
{
Label: Label{
ID: 1,

View File

@ -169,7 +169,7 @@ func (share *LinkSharing) Create(s *xorm.Session, a web.Auth) (err error) {
// @Failure 404 {object} web.HTTPError "Share Link not found."
// @Failure 500 {object} models.Message "Internal error"
// @Router /projects/{project}/shares/{share} [get]
func (share *LinkSharing) ReadOne(s *xorm.Session, _ web.Auth) (err error) {
func (share *LinkSharing) ReadOne(s *xorm.Session, a web.Auth) (err error) {
exists, err := s.Where("id = ?", share.ID).Get(share)
if err != nil {
return err
@ -269,7 +269,7 @@ func (share *LinkSharing) ReadAll(s *xorm.Session, a web.Auth, search string, pa
// @Failure 404 {object} web.HTTPError "Share Link not found."
// @Failure 500 {object} models.Message "Internal error"
// @Router /projects/{project}/shares/{share} [delete]
func (share *LinkSharing) Delete(s *xorm.Session, _ web.Auth) (err error) {
func (share *LinkSharing) Delete(s *xorm.Session, a web.Auth) (err error) {
_, err = s.Where("id = ?", share.ID).Delete(share)
return
}

View File

@ -51,15 +51,6 @@ func RegisterListeners() {
events.RegisterListener((&TaskCreatedEvent{}).Name(), &HandleTaskCreateMentions{})
events.RegisterListener((&TaskUpdatedEvent{}).Name(), &HandleTaskUpdatedMentions{})
events.RegisterListener((&UserDataExportRequestedEvent{}).Name(), &HandleUserDataExport{})
events.RegisterListener((&TaskCommentCreatedEvent{}).Name(), &HandleTaskUpdateLastUpdated{})
events.RegisterListener((&TaskCommentUpdatedEvent{}).Name(), &HandleTaskUpdateLastUpdated{})
events.RegisterListener((&TaskCommentDeletedEvent{}).Name(), &HandleTaskUpdateLastUpdated{})
events.RegisterListener((&TaskAssigneeCreatedEvent{}).Name(), &HandleTaskUpdateLastUpdated{})
events.RegisterListener((&TaskAssigneeDeletedEvent{}).Name(), &HandleTaskUpdateLastUpdated{})
events.RegisterListener((&TaskAttachmentCreatedEvent{}).Name(), &HandleTaskUpdateLastUpdated{})
events.RegisterListener((&TaskAttachmentDeletedEvent{}).Name(), &HandleTaskUpdateLastUpdated{})
events.RegisterListener((&TaskRelationCreatedEvent{}).Name(), &HandleTaskUpdateLastUpdated{})
events.RegisterListener((&TaskRelationDeletedEvent{}).Name(), &HandleTaskUpdateLastUpdated{})
}
//////
@ -75,7 +66,7 @@ func (s *IncreaseTaskCounter) Name() string {
}
// Handle is executed when the event IncreaseTaskCounter listens on is fired
func (s *IncreaseTaskCounter) Handle(_ *message.Message) (err error) {
func (s *IncreaseTaskCounter) Handle(msg *message.Message) (err error) {
return keyvalue.IncrBy(metrics.TaskCountKey, 1)
}
@ -89,7 +80,7 @@ func (s *DecreaseTaskCounter) Name() string {
}
// Handle is executed when the event DecreaseTaskCounter listens on is fired
func (s *DecreaseTaskCounter) Handle(_ *message.Message) (err error) {
func (s *DecreaseTaskCounter) Handle(msg *message.Message) (err error) {
return keyvalue.DecrBy(metrics.TaskCountKey, 1)
}
@ -412,62 +403,9 @@ func (s *HandleTaskUpdatedMentions) Handle(msg *message.Message) (err error) {
Doer: event.Doer,
IsNew: false,
}
_, err = notifyMentionedUsers(sess, event.Task, event.Task.Description, n)
return err
}
// HandleTaskUpdateLastUpdated represents a listener
type HandleTaskUpdateLastUpdated struct {
}
// Name defines the name for the HandleTaskUpdateLastUpdated listener
func (s *HandleTaskUpdateLastUpdated) Name() string {
return "handle.task.update.last.updated"
}
// Handle is executed when the event HandleTaskUpdateLastUpdated listens on is fired
func (s *HandleTaskUpdateLastUpdated) Handle(msg *message.Message) (err error) {
// Using a map here allows us to plug this listener to all kinds of task events
event := map[string]interface{}{}
err = json.Unmarshal(msg.Payload, &event)
if err != nil {
return err
}
task, is := event["Task"].(map[string]interface{})
if !is {
log.Errorf("Event payload does not contain task ID")
return
}
taskID, is := task["id"]
if !is {
log.Errorf("Event payload does not contain a valid task ID")
return
}
var taskIDInt int64
switch v := taskID.(type) {
case int64:
taskIDInt = v
case int:
taskIDInt = int64(v)
case int32:
taskIDInt = int64(v)
case float64:
taskIDInt = int64(v)
case float32:
taskIDInt = int64(v)
default:
log.Errorf("Event payload does not contain a valid task ID")
return
}
sess := db.NewSession()
defer sess.Close()
return updateTaskLastUpdated(sess, &Task{ID: taskIDInt})
}
///////
@ -480,7 +418,7 @@ func (s *IncreaseProjectCounter) Name() string {
return "project.counter.increase"
}
func (s *IncreaseProjectCounter) Handle(_ *message.Message) (err error) {
func (s *IncreaseProjectCounter) Handle(msg *message.Message) (err error) {
return keyvalue.IncrBy(metrics.ProjectCountKey, 1)
}
@ -491,7 +429,7 @@ func (s *DecreaseProjectCounter) Name() string {
return "project.counter.decrease"
}
func (s *DecreaseProjectCounter) Handle(_ *message.Message) (err error) {
func (s *DecreaseProjectCounter) Handle(msg *message.Message) (err error) {
return keyvalue.DecrBy(metrics.ProjectCountKey, 1)
}
@ -553,7 +491,7 @@ func (s *IncreaseNamespaceCounter) Name() string {
}
// Hanlde is executed when the event IncreaseNamespaceCounter listens on is fired
func (s *IncreaseNamespaceCounter) Handle(_ *message.Message) (err error) {
func (s *IncreaseNamespaceCounter) Handle(msg *message.Message) (err error) {
return keyvalue.IncrBy(metrics.NamespaceCountKey, 1)
}
@ -566,8 +504,8 @@ func (s *DecreaseNamespaceCounter) Name() string {
return "namespace.counter.decrease"
}
// Handle is executed when the event DecreaseNamespaceCounter listens on is fired
func (s *DecreaseNamespaceCounter) Handle(_ *message.Message) (err error) {
// Hanlde is executed when the event DecreaseNamespaceCounter listens on is fired
func (s *DecreaseNamespaceCounter) Handle(msg *message.Message) (err error) {
return keyvalue.DecrBy(metrics.NamespaceCountKey, 1)
}
@ -583,8 +521,8 @@ func (s *IncreaseTeamCounter) Name() string {
return "team.counter.increase"
}
// Handle is executed when the event IncreaseTeamCounter listens on is fired
func (s *IncreaseTeamCounter) Handle(_ *message.Message) (err error) {
// Hanlde is executed when the event IncreaseTeamCounter listens on is fired
func (s *IncreaseTeamCounter) Handle(msg *message.Message) (err error) {
return keyvalue.IncrBy(metrics.TeamCountKey, 1)
}
@ -597,8 +535,8 @@ func (s *DecreaseTeamCounter) Name() string {
return "team.counter.decrease"
}
// Handle is executed when the event DecreaseTeamCounter listens on is fired
func (s *DecreaseTeamCounter) Handle(_ *message.Message) (err error) {
// Hanlde is executed when the event DecreaseTeamCounter listens on is fired
func (s *DecreaseTeamCounter) Handle(msg *message.Message) (err error) {
return keyvalue.DecrBy(metrics.TeamCountKey, 1)
}

View File

@ -50,7 +50,7 @@ func (n *Namespace) CanDelete(s *xorm.Session, a web.Auth) (bool, error) {
}
// CanCreate checks if the user can create a new namespace
func (n *Namespace) CanCreate(_ *xorm.Session, a web.Auth) (bool, error) {
func (n *Namespace) CanCreate(s *xorm.Session, a web.Auth) (bool, error) {
if _, is := a.(*LinkSharing); is {
return false, nil
}

View File

@ -124,7 +124,7 @@ func (tn *TeamNamespace) Create(s *xorm.Session, a web.Auth) (err error) {
// @Failure 404 {object} web.HTTPError "team or namespace does not exist."
// @Failure 500 {object} models.Message "Internal error"
// @Router /namespaces/{namespaceID}/teams/{teamID} [delete]
func (tn *TeamNamespace) Delete(s *xorm.Session, _ web.Auth) (err error) {
func (tn *TeamNamespace) Delete(s *xorm.Session, a web.Auth) (err error) {
// Check if the team exists
_, err = GetTeamByID(s, tn.TeamID)
@ -229,7 +229,7 @@ func (tn *TeamNamespace) ReadAll(s *xorm.Session, a web.Auth, search string, pag
// @Failure 404 {object} web.HTTPError "Team or namespace does not exist."
// @Failure 500 {object} models.Message "Internal error"
// @Router /namespaces/{namespaceID}/teams/{teamID} [post]
func (tn *TeamNamespace) Update(s *xorm.Session, _ web.Auth) (err error) {
func (tn *TeamNamespace) Update(s *xorm.Session, a web.Auth) (err error) {
// Check if the right is valid
if err := tn.Right.isValid(); err != nil {

View File

@ -133,7 +133,7 @@ func (nu *NamespaceUser) Create(s *xorm.Session, a web.Auth) (err error) {
// @Failure 404 {object} web.HTTPError "user or namespace does not exist."
// @Failure 500 {object} models.Message "Internal error"
// @Router /namespaces/{namespaceID}/users/{userID} [delete]
func (nu *NamespaceUser) Delete(s *xorm.Session, _ web.Auth) (err error) {
func (nu *NamespaceUser) Delete(s *xorm.Session, a web.Auth) (err error) {
// Check if the user exists
user, err := user2.GetUserByUsername(s, nu.Username)
@ -229,7 +229,7 @@ func (nu *NamespaceUser) ReadAll(s *xorm.Session, a web.Auth, search string, pag
// @Failure 404 {object} web.HTTPError "User or namespace does not exist."
// @Failure 500 {object} models.Message "Internal error"
// @Router /namespaces/{namespaceID}/users/{userID} [post]
func (nu *NamespaceUser) Update(s *xorm.Session, _ web.Auth) (err error) {
func (nu *NamespaceUser) Update(s *xorm.Session, a web.Auth) (err error) {
// Check if the right is valid
if err := nu.Right.isValid(); err != nil {

View File

@ -47,7 +47,7 @@ type DatabaseNotifications struct {
// @Failure 403 {object} web.HTTPError "Link shares cannot have notifications."
// @Failure 500 {object} models.Message "Internal error"
// @Router /notifications [get]
func (d *DatabaseNotifications) ReadAll(s *xorm.Session, a web.Auth, _ string, page int, perPage int) (ls interface{}, resultCount int, numberOfEntries int64, err error) {
func (d *DatabaseNotifications) ReadAll(s *xorm.Session, a web.Auth, search string, page int, perPage int) (ls interface{}, resultCount int, numberOfEntries int64, err error) {
if _, is := a.(*LinkSharing); is {
return nil, 0, 0, ErrGenericForbidden{}
}
@ -79,6 +79,6 @@ func (d *DatabaseNotifications) CanUpdate(s *xorm.Session, a web.Auth) (bool, er
// @Failure 404 {object} web.HTTPError "The notification does not exist."
// @Failure 500 {object} models.Message "Internal error"
// @Router /notifications/{id} [post]
func (d *DatabaseNotifications) Update(s *xorm.Session, _ web.Auth) (err error) {
func (d *DatabaseNotifications) Update(s *xorm.Session, a web.Auth) (err error) {
return notifications.MarkNotificationAsRead(s, &d.DatabaseNotification, d.Read)
}

View File

@ -135,7 +135,7 @@ func (tl *TeamProject) Create(s *xorm.Session, a web.Auth) (err error) {
// @Failure 404 {object} web.HTTPError "Team or project does not exist."
// @Failure 500 {object} models.Message "Internal error"
// @Router /projects/{projectID}/teams/{teamID} [delete]
func (tl *TeamProject) Delete(s *xorm.Session, _ web.Auth) (err error) {
func (tl *TeamProject) Delete(s *xorm.Session, a web.Auth) (err error) {
// Check if the team exists
_, err = GetTeamByID(s, tl.TeamID)
@ -247,7 +247,7 @@ func (tl *TeamProject) ReadAll(s *xorm.Session, a web.Auth, search string, page
// @Failure 404 {object} web.HTTPError "Team or project does not exist."
// @Failure 500 {object} models.Message "Internal error"
// @Router /projects/{projectID}/teams/{teamID} [post]
func (tl *TeamProject) Update(s *xorm.Session, _ web.Auth) (err error) {
func (tl *TeamProject) Update(s *xorm.Session, a web.Auth) (err error) {
// Check if the right is valid
if err := tl.Right.isValid(); err != nil {

View File

@ -142,7 +142,7 @@ func (lu *ProjectUser) Create(s *xorm.Session, a web.Auth) (err error) {
// @Failure 404 {object} web.HTTPError "user or project does not exist."
// @Failure 500 {object} models.Message "Internal error"
// @Router /projects/{projectID}/users/{userID} [delete]
func (lu *ProjectUser) Delete(s *xorm.Session, _ web.Auth) (err error) {
func (lu *ProjectUser) Delete(s *xorm.Session, a web.Auth) (err error) {
// Check if the user exists
u, err := user.GetUserByUsername(s, lu.Username)
@ -244,7 +244,7 @@ func (lu *ProjectUser) ReadAll(s *xorm.Session, a web.Auth, search string, page
// @Failure 404 {object} web.HTTPError "User or project does not exist."
// @Failure 500 {object} models.Message "Internal error"
// @Router /projects/{projectID}/users/{userID} [post]
func (lu *ProjectUser) Update(s *xorm.Session, _ web.Auth) (err error) {
func (lu *ProjectUser) Update(s *xorm.Session, a web.Auth) (err error) {
// Check if the right is valid
if err := lu.Right.isValid(); err != nil {

View File

@ -149,7 +149,7 @@ func getSavedFilterSimpleByID(s *xorm.Session, id int64) (sf *SavedFilter, err e
// @Failure 403 {object} web.HTTPError "The user does not have access to that saved filter."
// @Failure 500 {object} models.Message "Internal error"
// @Router /filters/{id} [get]
func (sf *SavedFilter) ReadOne(s *xorm.Session, _ web.Auth) error {
func (sf *SavedFilter) ReadOne(s *xorm.Session, a web.Auth) error {
// s already contains almost the full saved filter from the rights check, we only need to add the user
u, err := user.GetUserByID(s, sf.OwnerID)
sf.Owner = u
@ -169,7 +169,7 @@ func (sf *SavedFilter) ReadOne(s *xorm.Session, _ web.Auth) error {
// @Failure 404 {object} web.HTTPError "The saved filter does not exist."
// @Failure 500 {object} models.Message "Internal error"
// @Router /filters/{id} [post]
func (sf *SavedFilter) Update(s *xorm.Session, _ web.Auth) error {
func (sf *SavedFilter) Update(s *xorm.Session, a web.Auth) error {
origFilter, err := getSavedFilterSimpleByID(s, sf.ID)
if err != nil {
return err
@ -204,7 +204,7 @@ func (sf *SavedFilter) Update(s *xorm.Session, _ web.Auth) error {
// @Failure 404 {object} web.HTTPError "The saved filter does not exist."
// @Failure 500 {object} models.Message "Internal error"
// @Router /filters/{id} [delete]
func (sf *SavedFilter) Delete(s *xorm.Session, _ web.Auth) error {
func (sf *SavedFilter) Delete(s *xorm.Session, a web.Auth) error {
_, err := s.
Where("id = ?", sf.ID).
Delete(sf)

View File

@ -40,7 +40,7 @@ func (sf *SavedFilter) CanUpdate(s *xorm.Session, auth web.Auth) (bool, error) {
}
// CanCreate checks if a user has the right to update a saved filter
func (sf *SavedFilter) CanCreate(_ *xorm.Session, auth web.Auth) (bool, error) {
func (sf *SavedFilter) CanCreate(s *xorm.Session, auth web.Auth) (bool, error) {
if _, is := auth.(*LinkSharing); is {
return false, nil
}

View File

@ -179,16 +179,7 @@ func (la *TaskAssginee) Delete(s *xorm.Session, a web.Auth) (err error) {
}
err = updateProjectByTaskID(s, la.TaskID)
if err != nil {
return err
}
doer, _ := user.GetFromAuth(a)
return events.Dispatch(&TaskAssigneeDeletedEvent{
Task: &Task{ID: la.TaskID},
Assignee: &user.User{ID: la.UserID},
Doer: doer,
})
return
}
// Create adds a new assignee to a task

View File

@ -20,8 +20,6 @@ import (
"io"
"time"
"code.vikunja.io/api/pkg/events"
"code.vikunja.io/api/pkg/files"
"code.vikunja.io/api/pkg/user"
"code.vikunja.io/web"
@ -46,7 +44,7 @@ type TaskAttachment struct {
}
// TableName returns the table name for task attachments
func (*TaskAttachment) TableName() string {
func (TaskAttachment) TableName() string {
return "task_attachments"
}
@ -86,15 +84,11 @@ func (ta *TaskAttachment) NewAttachment(s *xorm.Session, f io.ReadCloser, realna
return err
}
return events.Dispatch(&TaskAttachmentCreatedEvent{
Task: &Task{ID: ta.TaskID},
Attachment: ta,
Doer: ta.CreatedBy,
})
return nil
}
// ReadOne returns a task attachment
func (ta *TaskAttachment) ReadOne(s *xorm.Session, _ web.Auth) (err error) {
func (ta *TaskAttachment) ReadOne(s *xorm.Session, a web.Auth) (err error) {
exists, err := s.Where("id = ?", ta.ID).Get(ta)
if err != nil {
return
@ -127,7 +121,7 @@ func (ta *TaskAttachment) ReadOne(s *xorm.Session, _ web.Auth) (err error) {
// @Failure 404 {object} models.Message "The task does not exist."
// @Failure 500 {object} models.Message "Internal error"
// @Router /tasks/{id}/attachments [get]
func (ta *TaskAttachment) ReadAll(s *xorm.Session, _ web.Auth, _ string, page int, perPage int) (result interface{}, resultCount int, numberOfTotalItems int64, err error) {
func (ta *TaskAttachment) ReadAll(s *xorm.Session, a web.Auth, search string, page int, perPage int) (result interface{}, resultCount int, numberOfTotalItems int64, err error) {
attachments := []*TaskAttachment{}
limit, start := getLimitFromPageIndex(page, perPage)
@ -215,16 +209,7 @@ func (ta *TaskAttachment) Delete(s *xorm.Session, a web.Auth) error {
if err != nil && files.IsErrFileDoesNotExist(err) {
return nil
}
if err != nil {
return err
}
doer, _ := user.GetFromAuth(a)
return events.Dispatch(&TaskAttachmentDeletedEvent{
Task: &Task{ID: ta.TaskID},
Attachment: ta,
Doer: doer,
})
return err
}
func getTaskAttachmentsByTaskIDs(s *xorm.Session, taskIDs []int64) (attachments []*TaskAttachment, err error) {

View File

@ -266,13 +266,6 @@ func getNativeValueForTaskField(fieldName string, comparator taskFilterComparato
return nil, nil, ErrInvalidTaskField{TaskField: fieldName}
}
if realFieldName == "Reminders" {
field, ok = reflect.TypeOf(&TaskReminder{}).Elem().FieldByName("Reminder")
if !ok {
return nil, nil, ErrInvalidTaskField{TaskField: fieldName}
}
}
if comparator == taskFilterComparatorIn {
vals := strings.Split(value, ",")
valueSlice := []interface{}{}

View File

@ -169,17 +169,9 @@ func TestTaskCollection_ReadAll(t *testing.T) {
label4,
},
RelatedTasks: map[RelationKind][]*Task{},
ReminderDates: []time.Time{
Reminders: []time.Time{
time.Unix(1543626824, 0).In(loc),
},
Reminders: []*TaskReminder{
{
ID: 3,
TaskID: 2,
Reminder: time.Unix(1543626824, 0).In(loc),
Created: time.Unix(1543626724, 0).In(loc),
},
},
Created: time.Unix(1543626724, 0).In(loc),
Updated: time.Unix(1543626724, 0).In(loc),
}
@ -480,32 +472,15 @@ func TestTaskCollection_ReadAll(t *testing.T) {
}
task27 := &Task{
ID: 27,
Title: "task #27 with reminders and start_date",
Title: "task #27 with reminders",
Identifier: "test1-12",
Index: 12,
CreatedByID: 1,
CreatedBy: user1,
ReminderDates: []time.Time{
Reminders: []time.Time{
time.Unix(1543626724, 0).In(loc),
time.Unix(1543626824, 0).In(loc),
},
Reminders: []*TaskReminder{
{
ID: 1,
TaskID: 27,
Reminder: time.Unix(1543626724, 0).In(loc),
Created: time.Unix(1543626724, 0).In(loc),
},
{
ID: 2,
TaskID: 27,
Reminder: time.Unix(1543626824, 0).In(loc),
Created: time.Unix(1543626724, 0).In(loc),
RelativePeriod: -3600,
RelativeTo: "start_date",
},
},
StartDate: time.Unix(1543616724, 0).In(loc),
ProjectID: 1,
BucketID: 1,
RelatedTasks: map[RelationKind][]*Task{},
@ -931,7 +906,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
wantErr: false,
},
{
name: "filtered reminder dates",
name: "filtered reminders",
fields: fields{
FilterBy: []string{"reminders", "reminders"},
FilterValue: []string{"2018-10-01T00:00:00+00:00", "2018-12-10T00:00:00+00:00"},
@ -1271,7 +1246,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
return
}
t.Errorf("Test %s, Task.ReadAll() = %v, \nwant %v, \ndiff: %v", tt.name, got, tt.want, diff)
t.Errorf("Test %s, Task.ReadAll() = %v, want %v, \ndiff: %v", tt.name, got, tt.want, diff)
}
})
}

View File

@ -101,7 +101,7 @@ func (tc *TaskComment) Create(s *xorm.Session, a web.Auth) (err error) {
// @Failure 404 {object} web.HTTPError "The task comment was not found."
// @Failure 500 {object} models.Message "Internal error"
// @Router /tasks/{taskID}/comments/{commentID} [delete]
func (tc *TaskComment) Delete(s *xorm.Session, _ web.Auth) error {
func (tc *TaskComment) Delete(s *xorm.Session, a web.Auth) error {
deleted, err := s.
ID(tc.ID).
NoAutoCondition().
@ -109,16 +109,7 @@ func (tc *TaskComment) Delete(s *xorm.Session, _ web.Auth) error {
if deleted == 0 {
return ErrTaskCommentDoesNotExist{ID: tc.ID}
}
if err != nil {
return err
}
return events.Dispatch(&TaskCommentDeletedEvent{
Task: &Task{ID: tc.TaskID},
Comment: tc,
Doer: tc.Author,
})
return err
}
// Update updates a task text by its ID
@ -135,7 +126,7 @@ func (tc *TaskComment) Delete(s *xorm.Session, _ web.Auth) error {
// @Failure 404 {object} web.HTTPError "The task comment was not found."
// @Failure 500 {object} models.Message "Internal error"
// @Router /tasks/{taskID}/comments/{commentID} [post]
func (tc *TaskComment) Update(s *xorm.Session, _ web.Auth) error {
func (tc *TaskComment) Update(s *xorm.Session, a web.Auth) error {
updated, err := s.
ID(tc.ID).
Cols("comment").
@ -192,7 +183,7 @@ func getTaskCommentSimple(s *xorm.Session, tc *TaskComment) error {
// @Failure 404 {object} web.HTTPError "The task comment was not found."
// @Failure 500 {object} models.Message "Internal error"
// @Router /tasks/{taskID}/comments/{commentID} [get]
func (tc *TaskComment) ReadOne(s *xorm.Session, _ web.Auth) (err error) {
func (tc *TaskComment) ReadOne(s *xorm.Session, a web.Auth) (err error) {
err = getTaskCommentSimple(s, tc)
if err != nil {
return err

View File

@ -19,8 +19,6 @@ package models
import (
"time"
"code.vikunja.io/api/pkg/events"
"xorm.io/builder"
"xorm.io/xorm"
@ -100,7 +98,7 @@ type TaskRelation struct {
}
// TableName holds the table name for the task relation table
func (*TaskRelation) TableName() string {
func (TaskRelation) TableName() string {
return "task_relations"
}
@ -192,16 +190,7 @@ func (rel *TaskRelation) Create(s *xorm.Session, a web.Auth) error {
rel,
otherRelation,
})
if err != nil {
return err
}
doer, _ := user.GetFromAuth(a)
return events.Dispatch(&TaskRelationCreatedEvent{
Task: &Task{ID: rel.TaskID},
Relation: rel,
Doer: doer,
})
return err
}
// Delete removes a task relation
@ -252,14 +241,5 @@ func (rel *TaskRelation) Delete(s *xorm.Session, a web.Auth) error {
_, err = s.
Where(cond).
Delete(&TaskRelation{})
if err != nil {
return err
}
doer, _ := user.GetFromAuth(a)
return events.Dispatch(&TaskRelationDeletedEvent{
Task: &Task{ID: rel.TaskID},
Relation: rel,
Doer: doer,
})
return err
}

View File

@ -33,29 +33,12 @@ import (
"code.vikunja.io/api/pkg/user"
)
// ReminderRelation represents the date attribute of the task which a period based reminder relates to
type ReminderRelation string
// All valid ReminderRelations
const (
ReminderRelationDueDate ReminderRelation = `due_date`
ReminderRelationStartDate ReminderRelation = `start_date`
ReminderRelationEndDate ReminderRelation = `end_date`
)
// TaskReminder holds a reminder on a task.
// If RelativeTo and the assciated date field are defined, then the attribute Reminder will be computed.
// If RelativeTo is missing, than Reminder must be given.
// TaskReminder holds a reminder on a task
type TaskReminder struct {
ID int64 `xorm:"bigint autoincr not null unique pk" json:"-"`
TaskID int64 `xorm:"bigint not null INDEX" json:"-"`
// The absolute time when the user wants to be reminded of the task.
Reminder time.Time `xorm:"DATETIME not null INDEX 'reminder'" json:"reminder"`
Created time.Time `xorm:"created not null" json:"-"`
// A period in seconds relative to another date argument. Negative values mean the reminder triggers before the date. Default: 0, tiggers when RelativeTo is due.
RelativePeriod int64 `xorm:"bigint null" json:"relative_period"`
// The name of the date field to which the relative period refers to.
RelativeTo ReminderRelation `xorm:"varchar(50) null" json:"relative_to"`
ID int64 `xorm:"bigint autoincr not null unique pk"`
TaskID int64 `xorm:"bigint not null INDEX"`
Reminder time.Time `xorm:"DATETIME not null INDEX 'reminder'"`
Created time.Time `xorm:"created not null"`
}
// TableName returns a pretty table name

View File

@ -62,11 +62,7 @@ type Task struct {
// The time when the task is due.
DueDate time.Time `xorm:"DATETIME INDEX null 'due_date'" json:"due_date"`
// An array of datetimes when the user wants to be reminded of the task.
//
// Deprecated: Use Reminders
ReminderDates []time.Time `xorm:"-" json:"reminder_dates"`
// An array of reminders that are associated with this task.
Reminders []*TaskReminder `xorm:"-" json:"reminders"`
Reminders []time.Time `xorm:"-" json:"reminder_dates"`
// The project this task belongs to.
ProjectID int64 `xorm:"bigint INDEX not null" json:"project_id" param:"project"`
// An amount in seconds this task repeats itself. If this is set, when marking the task as done, it will mark itself as "undone" and then increase all remindes and the due date by its amount.
@ -143,8 +139,8 @@ type TaskWithComments struct {
Comments []*TaskComment `xorm:"-" json:"comments"`
}
// TableName returns the table name for tasks
func (*Task) TableName() string {
// TableName returns the table name for projecttasks
func (Task) TableName() string {
return "tasks"
}
@ -198,7 +194,7 @@ type taskOptions struct {
// @Success 200 {array} models.Task "The tasks"
// @Failure 500 {object} models.Message "Internal error"
// @Router /tasks/all [get]
func (t *Task) ReadAll(_ *xorm.Session, _ web.Auth, _ string, _ int, _ int) (result interface{}, resultCount int, totalItems int64, err error) {
func (t *Task) ReadAll(s *xorm.Session, a web.Auth, search string, page int, perPage int) (result interface{}, resultCount int, totalItems int64, err error) {
return nil, 0, 0, nil
}
@ -616,7 +612,7 @@ func addAssigneesToTasks(s *xorm.Session, taskIDs []int64, taskMap map[int64]*Ta
// Get all labels for all the tasks
func addLabelsToTasks(s *xorm.Session, taskIDs []int64, taskMap map[int64]*Task) (err error) {
labels, _, _, err := GetLabelsByTaskIDs(s, &LabelByTaskIDsOptions{
labels, _, _, err := getLabelsByTaskIDs(s, &LabelByTaskIDsOptions{
TaskIDs: taskIDs,
Page: -1,
})
@ -645,8 +641,8 @@ func addAttachmentsToTasks(s *xorm.Session, taskIDs []int64, taskMap map[int64]*
return
}
func getTaskReminderMap(s *xorm.Session, taskIDs []int64) (taskReminders map[int64][]*TaskReminder, err error) {
taskReminders = make(map[int64][]*TaskReminder)
func getTaskReminderMap(s *xorm.Session, taskIDs []int64) (taskReminders map[int64][]time.Time, err error) {
taskReminders = make(map[int64][]time.Time)
// Get all reminders and put them in a map to have it easier later
reminders, err := getRemindersForTasks(s, taskIDs)
@ -655,7 +651,7 @@ func getTaskReminderMap(s *xorm.Session, taskIDs []int64) (taskReminders map[int
}
for _, r := range reminders {
taskReminders[r.TaskID] = append(taskReminders[r.TaskID], r)
taskReminders[r.TaskID] = append(taskReminders[r.TaskID], r.Reminder)
}
return
@ -776,11 +772,6 @@ func addMoreInfoToTasks(s *xorm.Session, taskMap map[int64]*Task, a web.Auth) (e
// Make created by user objects
task.CreatedBy = users[task.CreatedByID]
// Add the reminder dates (Remove, when ReminderDates is removed)
for _, r := range taskReminders[task.ID] {
task.ReminderDates = append(task.ReminderDates, r.Reminder)
}
// Add the reminders
task.Reminders = taskReminders[task.ID]
@ -974,7 +965,7 @@ func createTask(s *xorm.Session, t *Task, a web.Auth, updateAssignees bool) (err
}
// Update the reminders
if err := t.updateReminders(s, t); err != nil {
if err := t.updateReminders(s, t.Reminders); err != nil {
return err
}
@ -1026,19 +1017,15 @@ func (t *Task) Update(s *xorm.Session, a web.Auth) (err error) {
t.ProjectID = ot.ProjectID
}
// Get the stored reminders
// Get the reminders
reminders, err := getRemindersForTasks(s, []int64{t.ID})
if err != nil {
return
}
// Old task has the stored reminders
ot.Reminders = reminders
// Deprecated: remove when ReminderDates is removed
ot.ReminderDates = make([]time.Time, len(reminders))
ot.Reminders = make([]time.Time, len(reminders))
for i, r := range reminders {
ot.ReminderDates[i] = r.Reminder
ot.Reminders[i] = r.Reminder
}
targetBucket, err := setTaskBucket(s, t, &ot, t.BucketID != 0 && t.BucketID != ot.BucketID)
@ -1062,7 +1049,7 @@ func (t *Task) Update(s *xorm.Session, a web.Auth) (err error) {
}
// Update the reminders
if err := ot.updateReminders(s, t); err != nil {
if err := ot.updateReminders(s, t.Reminders); err != nil {
return err
}
@ -1220,21 +1207,6 @@ func (t *Task) Update(s *xorm.Session, a web.Auth) (err error) {
if err != nil {
return err
}
// Update all positions if the newly saved position is < 0.1
if ot.Position < 0.1 {
err = recalculateTaskPositions(s, t.ProjectID)
if err != nil {
return err
}
}
if ot.KanbanPosition < 0.1 {
err = recalculateTaskKanbanPositions(s, t.BucketID)
if err != nil {
return err
}
}
// Get the task updated timestamp in a new struct - if we'd just try to put it into t which we already have, it
// would still contain the old updated date.
nt := &Task{}
@ -1243,8 +1215,6 @@ func (t *Task) Update(s *xorm.Session, a web.Auth) (err error) {
return err
}
t.Updated = nt.Updated
t.Position = nt.Position
t.KanbanPosition = nt.KanbanPosition
doer, _ := user.GetFromAuth(a)
err = events.Dispatch(&TaskUpdatedEvent{
@ -1258,62 +1228,6 @@ func (t *Task) Update(s *xorm.Session, a web.Auth) (err error) {
return updateProjectLastUpdated(s, &Project{ID: t.ProjectID})
}
func recalculateTaskKanbanPositions(s *xorm.Session, bucketID int64) (err error) {
allTasks := []*Task{}
err = s.
Where("bucket_id = ?", bucketID).
OrderBy("kanban_position asc").
Find(&allTasks)
if err != nil {
return
}
maxPosition := math.Pow(2, 32)
for i, task := range allTasks {
currentPosition := maxPosition / float64(len(allTasks)) * (float64(i + 1))
_, err = s.Cols("kanban_position").
Where("id = ?", task.ID).
Update(&Task{KanbanPosition: currentPosition})
if err != nil {
return
}
}
return
}
func recalculateTaskPositions(s *xorm.Session, projectID int64) (err error) {
allTasks := []*Task{}
err = s.
Where("project_id = ?", projectID).
OrderBy("position asc").
Find(&allTasks)
if err != nil {
return
}
maxPosition := math.Pow(2, 32)
for i, task := range allTasks {
currentPosition := maxPosition / float64(len(allTasks)) * (float64(i + 1))
_, err = s.Cols("position").
Where("id = ?", task.ID).
Update(&Task{Position: currentPosition})
if err != nil {
return
}
}
return
}
func addOneMonthToDate(d time.Time) time.Time {
return time.Date(d.Year(), d.Month()+1, d.Day(), d.Hour(), d.Minute(), d.Second(), d.Nanosecond(), config.GetTimeZone())
}
@ -1344,9 +1258,9 @@ func setTaskDatesDefault(oldTask, newTask *Task) {
// To make this easier, we sort them first because we can then rely on the fact the first is the smallest
if len(oldTask.Reminders) > 0 {
for in, r := range oldTask.Reminders {
newTask.Reminders[in].Reminder = r.Reminder.Add(repeatDuration)
for !newTask.Reminders[in].Reminder.After(now) {
newTask.Reminders[in].Reminder = newTask.Reminders[in].Reminder.Add(repeatDuration)
newTask.Reminders[in] = r.Add(repeatDuration)
for !newTask.Reminders[in].After(now) {
newTask.Reminders[in] = newTask.Reminders[in].Add(repeatDuration)
}
}
}
@ -1377,7 +1291,7 @@ func setTaskDatesMonthRepeat(oldTask, newTask *Task) {
newTask.Reminders = oldTask.Reminders
if len(oldTask.Reminders) > 0 {
for in, r := range oldTask.Reminders {
newTask.Reminders[in].Reminder = addOneMonthToDate(r.Reminder)
newTask.Reminders[in] = addOneMonthToDate(r)
}
}
@ -1418,12 +1332,12 @@ func setTaskDatesFromCurrentDateRepeat(oldTask, newTask *Task) {
// To make this easier, we sort them first because we can then rely on the fact the first is the smallest
if len(oldTask.Reminders) > 0 {
sort.Slice(oldTask.Reminders, func(i, j int) bool {
return oldTask.Reminders[i].Reminder.Unix() < oldTask.Reminders[j].Reminder.Unix()
return oldTask.Reminders[i].Unix() < oldTask.Reminders[j].Unix()
})
first := oldTask.Reminders[0].Reminder
first := oldTask.Reminders[0]
for in, r := range oldTask.Reminders {
diff := r.Reminder.Sub(first)
newTask.Reminders[in].Reminder = now.Add(repeatDuration + diff)
diff := r.Sub(first)
newTask.Reminders[in] = now.Add(repeatDuration + diff)
}
}
@ -1491,66 +1405,11 @@ func updateDone(oldTask *Task, newTask *Task) {
}
}
// Deprecated: will be removed when ReminderDates are removed from Task.
// For now the method just creates TaskReminder objects from the ReminderDates and overwrites Reminder.
func (t *Task) overwriteRemindersWithReminderDates(reminderDates []time.Time) {
// If the client still sends old reminder_dates, then these will overwrite
// the Reminders, if the were sent by the client, too.
// We assume that clients still using the old API with reminder_dates do not understand the new reminders.
// Clients who want to use the new Reminder structure must explicitey unset reminder_dates.
// start with empty Reminders
reminders := make([]*TaskReminder, 0)
// append absolute triggers from ReminderDates
for _, reminderDate := range reminderDates {
reminders = append(reminders, &TaskReminder{TaskID: t.ID, Reminder: reminderDate})
}
t.Reminders = reminders
}
// Set the absolute trigger dates for Reminders with relative period
func updateRelativeReminderDates(task *Task) (err error) {
for _, reminder := range task.Reminders {
relativeDuration := time.Duration(reminder.RelativePeriod) * time.Second
if reminder.RelativeTo != "" {
reminder.Reminder = time.Time{}
}
switch reminder.RelativeTo {
case ReminderRelationDueDate:
if !task.DueDate.IsZero() {
reminder.Reminder = task.DueDate.Add(relativeDuration)
}
case ReminderRelationStartDate:
if !task.StartDate.IsZero() {
reminder.Reminder = task.StartDate.Add(relativeDuration)
}
case ReminderRelationEndDate:
if !task.EndDate.IsZero() {
reminder.Reminder = task.EndDate.Add(relativeDuration)
}
default:
if reminder.RelativePeriod != 0 {
err = ErrReminderRelativeToMissing{
TaskID: task.ID,
}
return err
}
}
}
return nil
}
// Removes all old reminders and adds the new ones. This is a lot easier and less buggy than
// trying to figure out which reminders changed and then only re-add those needed. And since it does
// not make a performance difference we'll just do that.
// The parameter is a slice which holds the new reminders.
func (t *Task) updateReminders(s *xorm.Session, task *Task) (err error) {
// Deprecated: This statement must be removed when ReminderDates will be removed
if task.ReminderDates != nil {
task.overwriteRemindersWithReminderDates(task.ReminderDates)
}
// The parameter is a slice with unix dates which holds the new reminders.
func (t *Task) updateReminders(s *xorm.Session, reminders []time.Time) (err error) {
_, err = s.
Where("task_id = ?", t.ID).
@ -1559,55 +1418,30 @@ func (t *Task) updateReminders(s *xorm.Session, task *Task) (err error) {
return
}
err = updateRelativeReminderDates(task)
if err != nil {
return
}
// Resolve duplicates and sort them
reminderMap := make(map[int64]*TaskReminder, len(task.Reminders))
for _, reminder := range task.Reminders {
reminderMap[reminder.Reminder.UTC().Unix()] = reminder
reminderMap := make(map[int64]time.Time, len(reminders))
for _, reminder := range reminders {
reminderMap[reminder.UTC().Unix()] = reminder
}
t.Reminders = make([]*TaskReminder, 0, len(reminderMap))
t.ReminderDates = make([]time.Time, 0, len(reminderMap))
// Loop through all reminders and add them
for _, r := range reminderMap {
taskReminder := &TaskReminder{
TaskID: t.ID,
Reminder: r.Reminder,
RelativePeriod: r.RelativePeriod,
RelativeTo: r.RelativeTo}
_, err = s.Insert(taskReminder)
_, err = s.Insert(&TaskReminder{TaskID: t.ID, Reminder: r})
if err != nil {
return err
}
t.Reminders = append(t.Reminders, taskReminder)
t.ReminderDates = append(t.ReminderDates, taskReminder.Reminder)
}
// sort reminders
sort.Slice(t.Reminders, func(i, j int) bool {
return t.Reminders[i].Reminder.Before(t.Reminders[j].Reminder)
})
if len(t.Reminders) == 0 {
t.Reminders = reminders
if len(reminders) == 0 {
t.Reminders = nil
t.ReminderDates = nil
}
err = updateProjectLastUpdated(s, &Project{ID: t.ProjectID})
return
}
func updateTaskLastUpdated(s *xorm.Session, task *Task) error {
_, err := s.ID(task.ID).Cols("updated").Update(task)
return err
}
// Delete implements the delete method for a task
// Delete implements the delete method for projectTask
// @Summary Delete a task
// @Description Deletes a task from a project. This does not mean "mark it done".
// @tags task
@ -1700,12 +1534,12 @@ func (t *Task) Delete(s *xorm.Session, a web.Auth) (err error) {
// @Router /tasks/{ID} [get]
func (t *Task) ReadOne(s *xorm.Session, a web.Auth) (err error) {
*t, err = GetTaskByIDSimple(s, t.ID)
taskMap := make(map[int64]*Task, 1)
taskMap[t.ID] = &Task{}
*taskMap[t.ID], err = GetTaskByIDSimple(s, t.ID)
if err != nil {
return
}
taskMap := make(map[int64]*Task, 1)
taskMap[t.ID] = t
err = addMoreInfoToTasks(s, taskMap, a)
if err != nil {

View File

@ -70,48 +70,6 @@ func TestTask_Create(t *testing.T) {
events.AssertDispatched(t, &TaskCreatedEvent{})
})
t.Run("with reminders", func(t *testing.T) {
db.LoadAndAssertFixtures(t)
s := db.NewSession()
defer s.Close()
task := &Task{
Title: "Lorem",
Description: "Lorem Ipsum Dolor",
ProjectID: 1,
DueDate: time.Date(2023, time.March, 7, 22, 5, 0, 0, time.Local),
StartDate: time.Date(2023, time.March, 7, 22, 5, 10, 0, time.Local),
EndDate: time.Date(2023, time.March, 7, 22, 5, 20, 0, time.Local),
Reminders: []*TaskReminder{
{
RelativeTo: "due_date",
RelativePeriod: 1,
},
{
RelativeTo: "start_date",
RelativePeriod: -2,
},
{
RelativeTo: "end_date",
RelativePeriod: -1,
},
{
Reminder: time.Date(2023, time.March, 7, 23, 0, 0, 0, time.Local),
},
}}
err := task.Create(s, usr)
assert.NoError(t, err)
assert.Equal(t, time.Date(2023, time.March, 7, 22, 5, 1, 0, time.Local), task.Reminders[0].Reminder)
assert.Equal(t, int64(1), task.Reminders[0].RelativePeriod)
assert.Equal(t, ReminderRelationDueDate, task.Reminders[0].RelativeTo)
assert.Equal(t, time.Date(2023, time.March, 7, 22, 5, 8, 0, time.Local), task.Reminders[1].Reminder)
assert.Equal(t, ReminderRelationStartDate, task.Reminders[1].RelativeTo)
assert.Equal(t, time.Date(2023, time.March, 7, 22, 5, 19, 0, time.Local), task.Reminders[2].Reminder)
assert.Equal(t, ReminderRelationEndDate, task.Reminders[2].RelativeTo)
assert.Equal(t, time.Date(2023, time.March, 7, 23, 0, 0, 0, time.Local), task.Reminders[3].Reminder)
err = s.Commit()
assert.NoError(t, err)
})
t.Run("empty title", func(t *testing.T) {
db.LoadAndAssertFixtures(t)
s := db.NewSession()
@ -140,7 +98,7 @@ func TestTask_Create(t *testing.T) {
assert.Error(t, err)
assert.True(t, IsErrProjectDoesNotExist(err))
})
t.Run("nonexistant user", func(t *testing.T) {
t.Run("noneixtant user", func(t *testing.T) {
db.LoadAndAssertFixtures(t)
s := db.NewSession()
defer s.Close()
@ -410,51 +368,7 @@ func TestTask_Update(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, int64(3), task.Index)
})
t.Run("reminders will be updated", func(t *testing.T) {
db.LoadAndAssertFixtures(t)
s := db.NewSession()
defer s.Close()
task := &Task{
ID: 1,
ProjectID: 1,
Title: "test",
DueDate: time.Date(2023, time.March, 7, 22, 5, 0, 0, time.Local),
StartDate: time.Date(2023, time.March, 7, 22, 5, 10, 0, time.Local),
EndDate: time.Date(2023, time.March, 7, 22, 5, 20, 0, time.Local),
Reminders: []*TaskReminder{
{
RelativeTo: "due_date",
RelativePeriod: 1,
},
{
RelativeTo: "start_date",
RelativePeriod: -2,
},
{
RelativeTo: "end_date",
RelativePeriod: -1,
},
{
Reminder: time.Date(2023, time.March, 7, 23, 0, 0, 0, time.Local),
},
}}
err := task.Update(s, u)
assert.NoError(t, err)
assert.Equal(t, time.Date(2023, time.March, 7, 22, 5, 1, 0, time.Local), task.Reminders[0].Reminder)
assert.Equal(t, int64(1), task.Reminders[0].RelativePeriod)
assert.Equal(t, ReminderRelationDueDate, task.Reminders[0].RelativeTo)
assert.Equal(t, time.Date(2023, time.March, 7, 22, 5, 8, 0, time.Local), task.Reminders[1].Reminder)
assert.Equal(t, ReminderRelationStartDate, task.Reminders[1].RelativeTo)
assert.Equal(t, time.Date(2023, time.March, 7, 22, 5, 19, 0, time.Local), task.Reminders[2].Reminder)
assert.Equal(t, ReminderRelationEndDate, task.Reminders[2].RelativeTo)
assert.Equal(t, time.Date(2023, time.March, 7, 23, 0, 0, 0, time.Local), task.Reminders[3].Reminder)
err = s.Commit()
assert.NoError(t, err)
db.AssertCount(t, "task_reminders", builder.Eq{"task_id": 1}, 4)
})
t.Run("the same reminder multiple times should be saved once", func(t *testing.T) {
t.Run("the same date multiple times should be saved once", func(t *testing.T) {
db.LoadAndAssertFixtures(t)
s := db.NewSession()
defer s.Close()
@ -462,13 +376,9 @@ func TestTask_Update(t *testing.T) {
task := &Task{
ID: 1,
Title: "test",
Reminders: []*TaskReminder{
{
Reminder: time.Unix(1674745156, 0),
},
{
Reminder: time.Unix(1674745156, 223),
},
Reminders: []time.Time{
time.Unix(1674745156, 0),
time.Unix(1674745156, 223),
},
ProjectID: 1,
}
@ -476,42 +386,9 @@ func TestTask_Update(t *testing.T) {
assert.NoError(t, err)
err = s.Commit()
assert.NoError(t, err)
db.AssertCount(t, "task_reminders", builder.Eq{"task_id": 1}, 1)
})
t.Run("update relative reminder when start_date changes", func(t *testing.T) {
db.LoadAndAssertFixtures(t)
s := db.NewSession()
defer s.Close()
// given task with start_date and relative reminder for start_date
taskBefore := &Task{
Title: "test",
ProjectID: 1,
StartDate: time.Date(2022, time.March, 8, 8, 5, 20, 0, time.Local),
Reminders: []*TaskReminder{
{
RelativeTo: "start_date",
RelativePeriod: -60,
},
}}
err := taskBefore.Create(s, u)
assert.NoError(t, err)
err = s.Commit()
assert.NoError(t, err)
assert.Equal(t, time.Date(2022, time.March, 8, 8, 4, 20, 0, time.Local), taskBefore.Reminders[0].Reminder)
// when start_date is modified
task := taskBefore
task.StartDate = time.Date(2023, time.March, 8, 8, 5, 0, 0, time.Local)
task.ReminderDates = nil
err = task.Update(s, u)
assert.NoError(t, err)
// then reminder time is updated
assert.Equal(t, time.Date(2023, time.March, 8, 8, 4, 0, 0, time.Local), task.Reminders[0].Reminder)
err = s.Commit()
assert.NoError(t, err)
})
}
func TestTask_Delete(t *testing.T) {
@ -610,13 +487,9 @@ func TestUpdateDone(t *testing.T) {
oldTask := &Task{
Done: false,
RepeatAfter: 8600,
Reminders: []*TaskReminder{
{
Reminder: time.Unix(1550000000, 0),
},
{
Reminder: time.Unix(1555000000, 0),
},
Reminders: []time.Time{
time.Unix(1550000000, 0),
time.Unix(1555000000, 0),
},
}
newTask := &Task{
@ -634,8 +507,8 @@ func TestUpdateDone(t *testing.T) {
}
assert.Len(t, newTask.Reminders, 2)
assert.Equal(t, expected1, newTask.Reminders[0].Reminder)
assert.Equal(t, expected2, newTask.Reminders[1].Reminder)
assert.Equal(t, expected1, newTask.Reminders[0])
assert.Equal(t, expected2, newTask.Reminders[1])
assert.False(t, newTask.Done)
})
t.Run("update start date", func(t *testing.T) {
@ -712,25 +585,22 @@ func TestUpdateDone(t *testing.T) {
Done: false,
RepeatAfter: 8600,
RepeatMode: TaskRepeatModeFromCurrentDate,
Reminders: []*TaskReminder{
{
Reminder: time.Unix(1550000000, 0),
},
{
Reminder: time.Unix(1555000000, 0),
},
}}
Reminders: []time.Time{
time.Unix(1550000000, 0),
time.Unix(1555000000, 0),
},
}
newTask := &Task{
Done: true,
}
updateDone(oldTask, newTask)
diff := oldTask.Reminders[1].Reminder.Sub(oldTask.Reminders[0].Reminder)
diff := oldTask.Reminders[1].Sub(oldTask.Reminders[0])
assert.Len(t, newTask.Reminders, 2)
// Only comparing unix timestamps because time.Time use nanoseconds which can't ever possibly have the same value
assert.Equal(t, time.Now().Add(time.Duration(oldTask.RepeatAfter)*time.Second).Unix(), newTask.Reminders[0].Reminder.Unix())
assert.Equal(t, time.Now().Add(diff+time.Duration(oldTask.RepeatAfter)*time.Second).Unix(), newTask.Reminders[1].Reminder.Unix())
assert.Equal(t, time.Now().Add(time.Duration(oldTask.RepeatAfter)*time.Second).Unix(), newTask.Reminders[0].Unix())
assert.Equal(t, time.Now().Add(diff+time.Duration(oldTask.RepeatAfter)*time.Second).Unix(), newTask.Reminders[1].Unix())
assert.False(t, newTask.Done)
})
t.Run("start date", func(t *testing.T) {
@ -808,28 +678,23 @@ func TestUpdateDone(t *testing.T) {
oldTask := &Task{
Done: false,
RepeatMode: TaskRepeatModeMonth,
Reminders: []*TaskReminder{
{
Reminder: time.Unix(1550000000, 0),
},
{
Reminder: time.Unix(1555000000, 0),
},
}}
Reminders: []time.Time{
time.Unix(1550000000, 0),
time.Unix(1555000000, 0),
},
}
newTask := &Task{
Done: true,
}
oldReminders := make([]time.Time, len(oldTask.Reminders))
for i, r := range newTask.Reminders {
oldReminders[i] = r.Reminder
}
copy(oldReminders, oldTask.Reminders)
updateDone(oldTask, newTask)
assert.Len(t, newTask.Reminders, len(oldReminders))
for i, r := range newTask.Reminders {
assert.True(t, r.Reminder.After(oldReminders[i]))
assert.NotEqual(t, oldReminders[i].Month(), r.Reminder.Month())
assert.True(t, r.After(oldReminders[i]))
assert.NotEqual(t, oldReminders[i].Month(), r.Month())
}
assert.False(t, newTask.Done)
})

View File

@ -88,7 +88,7 @@ func (tm *TeamMember) Create(s *xorm.Session, a web.Auth) (err error) {
// @Success 200 {object} models.Message "The user was successfully removed from the team."
// @Failure 500 {object} models.Message "Internal error"
// @Router /teams/{id}/members/{userID} [delete]
func (tm *TeamMember) Delete(s *xorm.Session, _ web.Auth) (err error) {
func (tm *TeamMember) Delete(s *xorm.Session, a web.Auth) (err error) {
total, err := s.Where("team_id = ?", tm.TeamID).Count(&TeamMember{})
if err != nil {
@ -120,7 +120,7 @@ func (tm *TeamMember) Delete(s *xorm.Session, _ web.Auth) (err error) {
// @Success 200 {object} models.Message "The member right was successfully changed."
// @Failure 500 {object} models.Message "Internal error"
// @Router /teams/{id}/members/{userID}/admin [post]
func (tm *TeamMember) Update(s *xorm.Session, _ web.Auth) (err error) {
func (tm *TeamMember) Update(s *xorm.Session, a web.Auth) (err error) {
// Find the numeric user id
user, err := user2.GetUserByUsername(s, tm.Username)
if err != nil {

View File

@ -184,7 +184,7 @@ func addMoreInfoToTeams(s *xorm.Session, teams []*Team) (err error) {
// @Failure 403 {object} web.HTTPError "The user does not have access to the team"
// @Failure 500 {object} models.Message "Internal error"
// @Router /teams/{id} [get]
func (t *Team) ReadOne(s *xorm.Session, _ web.Auth) (err error) {
func (t *Team) ReadOne(s *xorm.Session, a web.Auth) (err error) {
team, err := GetTeamByID(s, t.ID)
if team != nil {
*t = *team
@ -338,7 +338,7 @@ func (t *Team) Delete(s *xorm.Session, a web.Auth) (err error) {
// @Failure 400 {object} web.HTTPError "Invalid team object provided."
// @Failure 500 {object} models.Message "Internal error"
// @Router /teams/{id} [post]
func (t *Team) Update(s *xorm.Session, _ web.Auth) (err error) {
func (t *Team) Update(s *xorm.Session, a web.Auth) (err error) {
// Check if we have a name
if t.Name == "" {
return ErrTeamNameCannotBeEmpty{}

View File

@ -22,7 +22,7 @@ import (
)
// CanCreate checks if the user can create a new team
func (t *Team) CanCreate(_ *xorm.Session, a web.Auth) (bool, error) {
func (t *Team) CanCreate(s *xorm.Session, a web.Auth) (bool, error) {
if _, is := a.(*LinkSharing); is {
return false, nil
}

View File

@ -20,7 +20,9 @@ import (
"context"
"encoding/json"
"errors"
"math/rand"
"net/http"
"time"
"code.vikunja.io/web/handler"
@ -64,7 +66,7 @@ type claims struct {
}
func init() {
petname.NonDeterministicMode()
rand.Seed(time.Now().UTC().UnixNano())
}
func (p *Provider) setOicdProvider() (err error) {

View File

@ -40,6 +40,6 @@ const defaultAvatar string = `<?xml version="1.0" encoding="UTF-8"?>
</svg>`
// GetAvatar implements getting the avatar method
func (p *Provider) GetAvatar(_ *user.User, _ int64) (avatar []byte, mimeType string, err error) {
func (p *Provider) GetAvatar(user *user.User, size int64) (avatar []byte, mimeType string, err error) {
return []byte(defaultAvatar), "image/svg+xml", nil
}

View File

@ -151,7 +151,7 @@ func getUnsplashPhotoInfoByID(photoID string) (photo *Photo, err error) {
// @Success 200 {array} background.Image "An array with photos"
// @Failure 500 {object} models.Message "Internal error"
// @Router /backgrounds/unsplash/search [get]
func (p *Provider) Search(_ *xorm.Session, search string, page int64) (result []*background.Image, err error) {
func (p *Provider) Search(s *xorm.Session, search string, page int64) (result []*background.Image, err error) {
// If we don't have a search query, return results from the unsplash featured collection
if search == "" {

View File

@ -32,7 +32,7 @@ type Provider struct {
}
// Search is only used to implement the interface
func (p *Provider) Search(_ *xorm.Session, _ string, _ int64) (result []*background.Image, err error) {
func (p *Provider) Search(s *xorm.Session, search string, page int64) (result []*background.Image, err error) {
return
}
@ -52,7 +52,7 @@ func (p *Provider) Search(_ *xorm.Session, _ string, _ int64) (result []*backgro
// @Failure 404 {object} models.Message "The project does not exist."
// @Failure 500 {object} models.Message "Internal error"
// @Router /projects/{id}/backgrounds/upload [put]
func (p *Provider) Set(s *xorm.Session, img *background.Image, project *models.Project, _ web.Auth) (err error) {
func (p *Provider) Set(s *xorm.Session, img *background.Image, project *models.Project, auth web.Auth) (err error) {
// Remove the old background if one exists
err = project.DeleteBackgroundFileIfExists()
if err != nil {

View File

@ -329,9 +329,7 @@ func convertMicrosoftTodoData(todoData []*project) (vikunjsStructure []*models.N
return nil, err
}
task.Reminders = []*models.TaskReminder{
{Reminder: reminder},
}
task.Reminders = []time.Time{reminder}
}
// Due Date

View File

@ -141,10 +141,8 @@ func TestConverting(t *testing.T) {
{
Task: models.Task{
Title: "Task 5",
Reminders: []*models.TaskReminder{
{
Reminder: testtimeTime,
},
Reminders: []time.Time{
testtimeTime,
},
},
},

View File

@ -20,7 +20,9 @@ import (
"encoding/csv"
"errors"
"io"
"regexp"
"sort"
"strconv"
"strings"
"time"
@ -28,7 +30,6 @@ import (
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/modules/migration"
"code.vikunja.io/api/pkg/user"
"code.vikunja.io/api/pkg/utils"
"github.com/gocarina/gocsv"
)
@ -74,6 +75,36 @@ func (date *tickTickTime) UnmarshalCSV(csv string) (err error) {
return err
}
// Copied from https://stackoverflow.com/a/57617885
var durationRegex = regexp.MustCompile(`P([\d\.]+Y)?([\d\.]+M)?([\d\.]+D)?T?([\d\.]+H)?([\d\.]+M)?([\d\.]+?S)?`)
// ParseDuration converts a ISO8601 duration into a time.Duration
func parseDuration(str string) time.Duration {
matches := durationRegex.FindStringSubmatch(str)
if len(matches) == 0 {
return 0
}
years := parseDurationPart(matches[1], time.Hour*24*365)
months := parseDurationPart(matches[2], time.Hour*24*30)
days := parseDurationPart(matches[3], time.Hour*24)
hours := parseDurationPart(matches[4], time.Hour)
minutes := parseDurationPart(matches[5], time.Second*60)
seconds := parseDurationPart(matches[6], time.Second)
return years + months + days + hours + minutes + seconds
}
func parseDurationPart(value string, unit time.Duration) time.Duration {
if len(value) != 0 {
if parsed, err := strconv.ParseFloat(value[:len(value)-1], 64); err == nil {
return time.Duration(float64(unit) * parsed)
}
}
return 0
}
func convertTickTickToVikunja(tasks []*tickTickTask) (result []*models.NamespaceWithProjectsAndTasks) {
namespace := &models.NamespaceWithProjectsAndTasks{
Namespace: models.Namespace{
@ -116,11 +147,8 @@ func convertTickTickToVikunja(tasks []*tickTickTask) (result []*models.Namespace
}
if !t.DueDate.IsZero() && t.Reminder > 0 {
task.Task.Reminders = []*models.TaskReminder{
{
RelativeTo: models.ReminderRelationDueDate,
RelativePeriod: int64((t.Reminder * -1).Seconds()),
},
task.Task.Reminders = []time.Time{
t.DueDate.Add(t.Reminder * -1),
}
}
@ -200,7 +228,7 @@ func (m *Migrator) Migrate(user *user.User, file io.ReaderAt, size int64) error
task.IsChecklist = true
}
reminder := utils.ParseISO8601Duration(task.ReminderDuration)
reminder := parseDuration(task.ReminderDuration)
if reminder > 0 {
task.Reminder = reminder
}

View File

@ -101,8 +101,7 @@ func TestConvertTicktickTasksToVikunja(t *testing.T) {
{Title: "label1"},
{Title: "label2"},
})
assert.Equal(t, vikunjaTasks[0].Projects[0].Tasks[0].Reminders[0].RelativeTo, models.ReminderRelation("due_date"))
assert.Equal(t, vikunjaTasks[0].Projects[0].Tasks[0].Reminders[0].RelativePeriod, int64(-24*3600))
//assert.Equal(t, vikunjaTasks[0].Projects[0].Tasks[0].Reminders, tickTickTasks[0].) // TODO
assert.Equal(t, vikunjaTasks[0].Projects[0].Tasks[0].Position, tickTickTasks[0].Order)
assert.Equal(t, vikunjaTasks[0].Projects[0].Tasks[0].Done, false)
@ -128,8 +127,7 @@ func TestConvertTicktickTasksToVikunja(t *testing.T) {
{Title: "label2"},
{Title: "other label"},
})
assert.Equal(t, vikunjaTasks[0].Projects[0].Tasks[2].Reminders[0].RelativeTo, models.ReminderRelation("due_date"))
assert.Equal(t, vikunjaTasks[0].Projects[0].Tasks[2].Reminders[0].RelativePeriod, int64(-24*3600))
//assert.Equal(t, vikunjaTasks[0].Projects[0].Tasks[0].Reminders, tickTickTasks[0].) // TODO
assert.Equal(t, vikunjaTasks[0].Projects[0].Tasks[2].Position, tickTickTasks[2].Order)
assert.Equal(t, vikunjaTasks[0].Projects[0].Tasks[2].Done, false)

View File

@ -369,14 +369,14 @@ func convertTodoistToVikunja(sync *sync, doneItems map[string]*doneItem) (fullVi
}
// If the parenId of a task is not 0, create a task relation
// We're looping again here to make sure we have seen all tasks before and have them in our map
// We're looping again here to make sure we have seem all tasks before and have them in our map
for _, i := range sync.Items {
if i.ParentID == "" {
continue
}
if _, exists := tasks[i.ParentID]; !exists {
log.Debugf("[Todoist Migration] Could not find task %s in tasks map while trying to resolve subtasks for task %s", i.ParentID, i.ID)
log.Debugf("[Todoist Migration] Could not find task %s in tasks map while trying to get resolve subtasks for task %s", i.ParentID, i.ID)
continue
}
@ -385,11 +385,6 @@ func convertTodoistToVikunja(sync *sync, doneItems map[string]*doneItem) (fullVi
tasks[i.ParentID].RelatedTasks = make(models.RelatedTaskMap)
}
if _, exists := tasks[i.ID]; !exists {
log.Debugf("[Todoist Migration] Could not find task %s in tasks map while trying to add it as subtask", i.ID)
continue
}
tasks[i.ParentID].RelatedTasks[models.RelationKindSubtask] = append(tasks[i.ParentID].RelatedTasks[models.RelationKindSubtask], &tasks[i.ID].Task)
// Remove the task from the top level structure, otherwise it is added twice
@ -471,10 +466,7 @@ func convertTodoistToVikunja(sync *sync, doneItems map[string]*doneItem) (fullVi
return nil, err
}
tasks[r.ItemID].Reminders = append(tasks[r.ItemID].Reminders, &models.TaskReminder{
Reminder: date.In(config.GetTimeZone()),
},
)
tasks[r.ItemID].Reminders = append(tasks[r.ItemID].Reminders, date.In(config.GetTimeZone()))
}
return []*models.NamespaceWithProjectsAndTasks{

View File

@ -388,9 +388,9 @@ func TestConvertTodoistToVikunja(t *testing.T) {
Description: "Lorem Ipsum dolor sit amet",
Done: false,
Created: time1,
Reminders: []*models.TaskReminder{
{Reminder: time.Date(2020, time.June, 15, 23, 59, 0, 0, time.UTC).In(config.GetTimeZone())},
{Reminder: time.Date(2020, time.June, 16, 7, 0, 0, 0, time.UTC).In(config.GetTimeZone())},
Reminders: []time.Time{
time.Date(2020, time.June, 15, 23, 59, 0, 0, time.UTC).In(config.GetTimeZone()),
time.Date(2020, time.June, 16, 7, 0, 0, 0, time.UTC).In(config.GetTimeZone()),
},
},
},
@ -407,8 +407,8 @@ func TestConvertTodoistToVikunja(t *testing.T) {
Title: "Task400000002",
Done: false,
Created: time1,
Reminders: []*models.TaskReminder{
{Reminder: time.Date(2020, time.July, 15, 7, 0, 0, 0, time.UTC).In(config.GetTimeZone())},
Reminders: []time.Time{
time.Date(2020, time.July, 15, 7, 0, 0, 0, time.UTC).In(config.GetTimeZone()),
},
},
},
@ -421,8 +421,8 @@ func TestConvertTodoistToVikunja(t *testing.T) {
Created: time1,
DoneAt: time3,
Labels: vikunjaLabels,
Reminders: []*models.TaskReminder{
{Reminder: time.Date(2020, time.June, 15, 7, 0, 0, 0, time.UTC).In(config.GetTimeZone())},
Reminders: []time.Time{
time.Date(2020, time.June, 15, 7, 0, 0, 0, time.UTC).In(config.GetTimeZone()),
},
},
},
@ -441,8 +441,8 @@ func TestConvertTodoistToVikunja(t *testing.T) {
DueDate: dueTime,
Created: time1,
DoneAt: time3,
Reminders: []*models.TaskReminder{
{Reminder: time.Date(2020, time.June, 15, 7, 0, 0, 0, time.UTC).In(config.GetTimeZone())},
Reminders: []time.Time{
time.Date(2020, time.June, 15, 7, 0, 0, 0, time.UTC).In(config.GetTimeZone()),
},
},
},
@ -531,8 +531,8 @@ func TestConvertTodoistToVikunja(t *testing.T) {
Title: "Task400000009",
Done: false,
Created: time1,
Reminders: []*models.TaskReminder{
{Reminder: time.Date(2020, time.June, 15, 7, 0, 0, 0, time.UTC).In(config.GetTimeZone())},
Reminders: []time.Time{
time.Date(2020, time.June, 15, 7, 0, 0, 0, time.UTC).In(config.GetTimeZone()),
},
},
},

View File

@ -133,24 +133,6 @@ func getTrelloData(token string) (trelloData []*trello.Board, err error) {
return nil, err
}
if len(card.IDCheckLists) > 0 {
for _, checkListID := range card.IDCheckLists {
checklist, err := client.GetChecklist(checkListID, allArg)
if err != nil {
return nil, err
}
checklist.CheckItems = []trello.CheckItem{}
err = client.Get("checklists/"+checkListID+"/checkItems", allArg, &checklist.CheckItems)
if err != nil {
return nil, err
}
card.Checklists = append(card.Checklists, checklist)
log.Debugf("Retrieved checklist %s for card %s", checkListID, card.ID)
}
}
list.Cards = append(list.Cards, card)
}
@ -232,7 +214,7 @@ func convertTrelloDataToVikunja(trelloData []*trello.Board, token string) (fullV
for _, item := range checklist.CheckItems {
task.Description += "\n* "
if item.State == "complete" {
if item.State == "completed" {
task.Description += "[x]"
} else {
task.Description += "[ ]"

View File

@ -86,7 +86,7 @@ func TestConvertTrelloToVikunja(t *testing.T) {
Name: "Pending Task",
},
{
State: "complete",
State: "completed",
Name: "Completed Task",
},
},

View File

@ -141,12 +141,7 @@ func (v *FileMigrator) Migrate(user *user.User, file io.ReaderAt, size int64) er
comment.ID = 0
}
for _, attachment := range t.Attachments {
attachmentFile, exists := storedFiles[attachment.File.ID]
if !exists {
log.Debugf(logPrefix+"Could not find attachment file %d for attachment %d", attachment.File.ID, attachment.ID)
continue
}
af, err := attachmentFile.Open()
af, err := storedFiles[attachment.File.ID].Open()
if err != nil {
return fmt.Errorf("could not open attachment %d for reading: %w", attachment.ID, err)
}

File diff suppressed because one or more lines are too long

View File

@ -26,10 +26,8 @@ import (
"code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/models"
user2 "code.vikunja.io/api/pkg/user"
"code.vikunja.io/web"
"github.com/samedi/caldav-go/data"
"github.com/samedi/caldav-go/errs"
"xorm.io/xorm"
)
// DavBasePath is the base url path
@ -51,7 +49,7 @@ type VikunjaCaldavProjectStorage struct {
}
// GetResources returns either all projects, links to the principal, or only one project, depending on the request
func (vcls *VikunjaCaldavProjectStorage) GetResources(rpath string, _ bool) ([]data.Resource, error) {
func (vcls *VikunjaCaldavProjectStorage) GetResources(rpath string, withChildren bool) ([]data.Resource, error) {
// It looks like we need to have the same handler for returning both the calendar home set and the user principal
// Since the client seems to ignore the whatever is being returned in the first request and just makes a second one
@ -166,7 +164,7 @@ func (vcls *VikunjaCaldavProjectStorage) GetResourcesByList(rpaths []string) ([]
}
// GetResourcesByFilters fetches a project of resources with a filter
func (vcls *VikunjaCaldavProjectStorage) GetResourcesByFilters(rpath string, _ *data.ResourceFilter) ([]data.Resource, error) {
func (vcls *VikunjaCaldavProjectStorage) GetResourcesByFilters(rpath string, filters *data.ResourceFilter) ([]data.Resource, error) {
// If we already have a project saved, that means the user is making a REPORT request to find out if
// anything changed, in that case we need to return all tasks.
@ -212,7 +210,7 @@ func (vcls *VikunjaCaldavProjectStorage) GetResource(rpath string) (*data.Resour
// save and override the updated unix date to not break any later etag checks
updated := vcls.task.Updated
tasks, err := models.GetTasksByUIDs(s, []string{vcls.task.UID}, vcls.user)
task, err := models.GetTaskSimple(s, &models.Task{ID: vcls.task.ID, UID: vcls.task.UID})
if err != nil {
_ = s.Rollback()
if models.IsErrTaskDoesNotExist(err) {
@ -224,18 +222,14 @@ func (vcls *VikunjaCaldavProjectStorage) GetResource(rpath string) (*data.Resour
return nil, false, err
}
if len(tasks) < 1 {
return nil, false, errs.ResourceNotFoundError
}
vcls.task = tasks[0]
vcls.task = &task
if updated.Unix() > 0 {
vcls.task.Updated = updated
}
rr := VikunjaProjectResourceAdapter{
project: vcls.project,
task: vcls.task,
task: &task,
}
r := data.NewResource(rpath, &rr)
return &r, true, nil
@ -287,13 +281,6 @@ func (vcls *VikunjaCaldavProjectStorage) CreateResource(rpath, content string) (
return nil, err
}
vcls.task.ID = vTask.ID
err = persistLabels(s, vcls.user, vcls.task, vTask.Labels)
if err != nil {
_ = s.Rollback()
return nil, err
}
if err := s.Commit(); err != nil {
return nil, err
}
@ -339,12 +326,6 @@ func (vcls *VikunjaCaldavProjectStorage) UpdateResource(rpath, content string) (
return nil, err
}
err = persistLabels(s, vcls.user, vcls.task, vTask.Labels)
if err != nil {
_ = s.Rollback()
return nil, err
}
if err := s.Commit(); err != nil {
return nil, err
}
@ -359,7 +340,7 @@ func (vcls *VikunjaCaldavProjectStorage) UpdateResource(rpath, content string) (
}
// DeleteResource deletes a resource
func (vcls *VikunjaCaldavProjectStorage) DeleteResource(_ string) error {
func (vcls *VikunjaCaldavProjectStorage) DeleteResource(rpath string) error {
if vcls.task != nil {
s := db.NewSession()
defer s.Close()
@ -387,51 +368,6 @@ func (vcls *VikunjaCaldavProjectStorage) DeleteResource(_ string) error {
return nil
}
func persistLabels(s *xorm.Session, a web.Auth, task *models.Task, labels []*models.Label) (err error) {
labelTitles := []string{}
for _, label := range labels {
labelTitles = append(labelTitles, label.Title)
}
u := &user2.User{
ID: a.GetID(),
}
// Using readall ensures the current user has the permission to see the labels they provided via caldav.
existingLabels, _, _, err := models.GetLabelsByTaskIDs(s, &models.LabelByTaskIDsOptions{
Search: labelTitles,
User: u,
GetForUser: u.ID,
GetUnusedLabels: true,
GroupByLabelIDsOnly: true,
})
if err != nil {
return err
}
labelMap := make(map[string]*models.Label)
for _, l := range existingLabels {
labelMap[l.Title] = &l.Label
}
for _, label := range labels {
if l, has := labelMap[label.Title]; has {
*label = *l
continue
}
err = label.Create(s, a)
if err != nil {
return err
}
}
// Create the label <-> task relation
return task.UpdateTaskLabels(s, a, labels)
}
// VikunjaProjectResourceAdapter holds the actual resource
type VikunjaProjectResourceAdapter struct {
project *models.ProjectWithTasksAndBuckets

View File

@ -128,7 +128,7 @@ func NewEcho() *echo.Echo {
e.HTTPErrorHandler = func(err error, c echo.Context) {
// Only capture errors not already handled by echo
var herr *echo.HTTPError
if errors.As(err, &herr) && herr.Code > 403 {
if errors.As(err, &herr) {
hub := sentryecho.GetHubFromContext(c)
if hub != nil {
hub.WithScope(func(scope *sentry.Scope) {

View File

@ -1,4 +1,5 @@
// Code generated by swaggo/swag. DO NOT EDIT
// Package swagger GENERATED BY SWAG; DO NOT EDIT
// This file was generated by swaggo/swag
package swagger
import "github.com/swaggo/swag"
@ -7790,19 +7791,12 @@ const docTemplate = `{
]
},
"reminder_dates": {
"description": "An array of datetimes when the user wants to be reminded of the task.\n\nDeprecated: Use Reminders",
"description": "An array of datetimes when the user wants to be reminded of the task.",
"type": "array",
"items": {
"type": "string"
}
},
"reminders": {
"description": "An array of reminders that are associated with this task.",
"type": "array",
"items": {
"$ref": "#/definitions/models.TaskReminder"
}
},
"repeat_after": {
"description": "An amount in seconds this task repeats itself. If this is set, when marking the task as done, it will mark itself as \"undone\" and then increase all remindes and the due date by its amount.",
"type": "integer"
@ -8309,19 +8303,6 @@ const docTemplate = `{
"RelationKindCopiedTo"
]
},
"models.ReminderRelation": {
"type": "string",
"enum": [
"due_date",
"start_date",
"end_date"
],
"x-enum-varnames": [
"ReminderRelationDueDate",
"ReminderRelationStartDate",
"ReminderRelationEndDate"
]
},
"models.Right": {
"type": "integer",
"enum": [
@ -8537,19 +8518,12 @@ const docTemplate = `{
]
},
"reminder_dates": {
"description": "An array of datetimes when the user wants to be reminded of the task.\n\nDeprecated: Use Reminders",
"description": "An array of datetimes when the user wants to be reminded of the task.",
"type": "array",
"items": {
"type": "string"
}
},
"reminders": {
"description": "An array of reminders that are associated with this task.",
"type": "array",
"items": {
"$ref": "#/definitions/models.TaskReminder"
}
},
"repeat_after": {
"description": "An amount in seconds this task repeats itself. If this is set, when marking the task as done, it will mark itself as \"undone\" and then increase all remindes and the due date by its amount.",
"type": "integer"
@ -8717,27 +8691,6 @@ const docTemplate = `{
}
}
},
"models.TaskReminder": {
"type": "object",
"properties": {
"relative_period": {
"description": "A period in seconds relative to another date argument. Negative values mean the reminder triggers before the date. Default: 0, tiggers when RelativeTo is due.",
"type": "integer"
},
"relative_to": {
"description": "The name of the date field to which the relative period refers to.",
"allOf": [
{
"$ref": "#/definitions/models.ReminderRelation"
}
]
},
"reminder": {
"description": "The absolute time when the user wants to be reminded of the task.",
"type": "string"
}
}
},
"models.TaskRepeatMode": {
"type": "integer",
"enum": [

View File

@ -7782,19 +7782,12 @@
]
},
"reminder_dates": {
"description": "An array of datetimes when the user wants to be reminded of the task.\n\nDeprecated: Use Reminders",
"description": "An array of datetimes when the user wants to be reminded of the task.",
"type": "array",
"items": {
"type": "string"
}
},
"reminders": {
"description": "An array of reminders that are associated with this task.",
"type": "array",
"items": {
"$ref": "#/definitions/models.TaskReminder"
}
},
"repeat_after": {
"description": "An amount in seconds this task repeats itself. If this is set, when marking the task as done, it will mark itself as \"undone\" and then increase all remindes and the due date by its amount.",
"type": "integer"
@ -8301,19 +8294,6 @@
"RelationKindCopiedTo"
]
},
"models.ReminderRelation": {
"type": "string",
"enum": [
"due_date",
"start_date",
"end_date"
],
"x-enum-varnames": [
"ReminderRelationDueDate",
"ReminderRelationStartDate",
"ReminderRelationEndDate"
]
},
"models.Right": {
"type": "integer",
"enum": [
@ -8529,19 +8509,12 @@
]
},
"reminder_dates": {
"description": "An array of datetimes when the user wants to be reminded of the task.\n\nDeprecated: Use Reminders",
"description": "An array of datetimes when the user wants to be reminded of the task.",
"type": "array",
"items": {
"type": "string"
}
},
"reminders": {
"description": "An array of reminders that are associated with this task.",
"type": "array",
"items": {
"$ref": "#/definitions/models.TaskReminder"
}
},
"repeat_after": {
"description": "An amount in seconds this task repeats itself. If this is set, when marking the task as done, it will mark itself as \"undone\" and then increase all remindes and the due date by its amount.",
"type": "integer"
@ -8709,27 +8682,6 @@
}
}
},
"models.TaskReminder": {
"type": "object",
"properties": {
"relative_period": {
"description": "A period in seconds relative to another date argument. Negative values mean the reminder triggers before the date. Default: 0, tiggers when RelativeTo is due.",
"type": "integer"
},
"relative_to": {
"description": "The name of the date field to which the relative period refers to.",
"allOf": [
{
"$ref": "#/definitions/models.ReminderRelation"
}
]
},
"reminder": {
"description": "The absolute time when the user wants to be reminded of the task.",
"type": "string"
}
}
},
"models.TaskRepeatMode": {
"type": "integer",
"enum": [

View File

@ -196,18 +196,11 @@ definitions:
- $ref: '#/definitions/models.RelatedTaskMap'
description: All related tasks, grouped by their relation kind
reminder_dates:
description: |-
An array of datetimes when the user wants to be reminded of the task.
Deprecated: Use Reminders
description: An array of datetimes when the user wants to be reminded of the
task.
items:
type: string
type: array
reminders:
description: An array of reminders that are associated with this task.
items:
$ref: '#/definitions/models.TaskReminder'
type: array
repeat_after:
description: An amount in seconds this task repeats itself. If this is set,
when marking the task as done, it will mark itself as "undone" and then
@ -603,16 +596,6 @@ definitions:
- RelationKindFollows
- RelationKindCopiedFrom
- RelationKindCopiedTo
models.ReminderRelation:
enum:
- due_date
- start_date
- end_date
type: string
x-enum-varnames:
- ReminderRelationDueDate
- ReminderRelationStartDate
- ReminderRelationEndDate
models.Right:
enum:
- 0
@ -780,18 +763,11 @@ definitions:
- $ref: '#/definitions/models.RelatedTaskMap'
description: All related tasks, grouped by their relation kind
reminder_dates:
description: |-
An array of datetimes when the user wants to be reminded of the task.
Deprecated: Use Reminders
description: An array of datetimes when the user wants to be reminded of the
task.
items:
type: string
type: array
reminders:
description: An array of reminders that are associated with this task.
items:
$ref: '#/definitions/models.TaskReminder'
type: array
repeat_after:
description: An amount in seconds this task repeats itself. If this is set,
when marking the task as done, it will mark itself as "undone" and then
@ -913,22 +889,6 @@ definitions:
description: The ID of the "base" task, the task which has a relation to another.
type: integer
type: object
models.TaskReminder:
properties:
relative_period:
description: 'A period in seconds relative to another date argument. Negative
values mean the reminder triggers before the date. Default: 0, tiggers when
RelativeTo is due.'
type: integer
relative_to:
allOf:
- $ref: '#/definitions/models.ReminderRelation'
description: The name of the date field to which the relative period refers
to.
reminder:
description: The absolute time when the user wants to be reminded of the task.
type: string
type: object
models.TaskRepeatMode:
enum:
- 0

Some files were not shown because too many files have changed in this diff Show More