Compare commits

...

40 Commits

Author SHA1 Message Date
kolaente b5ee39b887
fix(ci): make sure release os packages are properly named 2022-10-02 16:30:06 +02:00
kolaente 0d8451ab6e
fix(ci): make sure release zip files have a .zip ending 2022-10-02 16:30:06 +02:00
renovate 81f09f7dc0 fix(deps): update module github.com/wneessen/go-mail to v0.2.9 (#1264)
Reviewed-on: vikunja/api#1264
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-10-02 12:19:23 +00:00
kolaente 5a40100ac5
feat: provide default user settings for new users via config 2022-10-02 11:00:58 +02:00
renovate 0694314e52 fix(deps): update module github.com/swaggo/swag to v1.8.6 (#1243)
Reviewed-on: vikunja/api#1243
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-10-02 08:40:47 +00:00
renovate a547a9eb25 fix(deps): update module github.com/magefile/mage to v1.14.0 (#1259)
Reviewed-on: vikunja/api#1259
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-10-02 07:29:13 +00:00
renovate 0fcd03f561 fix(deps): update module src.techknowlogick.com/xormigrate to v1.5.0 (#1262)
Reviewed-on: vikunja/api#1262
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-10-01 20:59:56 +00:00
renovate ffedd02b08 fix(deps): update module github.com/yuin/goldmark to v1.5.2 (#1261)
Reviewed-on: vikunja/api#1261
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-10-01 20:59:42 +00:00
renovate c84684a425 fix(deps): update module github.com/wneessen/go-mail to v0.2.8 (#1258)
Reviewed-on: vikunja/api#1258
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-10-01 17:31:10 +00:00
kolaente aed560339b
fix(todoist): properly import all done tasks 2022-10-01 19:29:05 +02:00
kolaente 0612f4d0e0
feat: add gitea issue template 2022-10-01 18:14:49 +02:00
kolaente ce621ee5d6
feat: remove gitea issue template so that only the form is used 2022-10-01 18:09:08 +02:00
kolaente 9c4bb5a244
feat: add github issue templates 2022-10-01 17:53:30 +02:00
kolaente c076f73a87
fix: make sure user searches are always case-insensitive
See vikunja/frontend#2196 (comment)
Resolves https://github.com/go-vikunja/frontend/issues/29
2022-10-01 17:39:08 +02:00
Dominik Pschenitschni 36265fcedf feat(docs): document pnpm (#1251)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/api#1251
Co-authored-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
Co-committed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
2022-10-01 15:30:20 +00:00
konrad 53419180be feat: upgrade xorm (#1197)
Reviewed-on: vikunja/api#1197
2022-10-01 15:10:00 +00:00
renovate c5bd09702a chore(deps): update dependency golang to v1.19 (#1228)
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/api#1228
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-10-01 15:05:12 +00:00
kolaente fcb205a842
fix: use connection string for postgres 2022-10-01 16:57:06 +02:00
kolaente 4323803fd6
feat: upgrade xorm 2022-10-01 16:32:22 +02:00
kolaente 903b8ff438
chore: go mod tidy 2022-10-01 16:26:22 +02:00
kolaente b1fd13bbcb
feat: upgrade xorm 2022-10-01 16:25:29 +02:00
kolaente 878d19beb8
fix: make sure pseudo namespaces and lists always have the current user as owner 2022-10-01 15:19:46 +02:00
kolaente 96ed1e33e3
fix: don't allow setting a list namespace to 0
See https://github.com/go-vikunja/app/issues/13
2022-10-01 15:02:17 +02:00
renovate 374a0f9ce3 fix(deps): update module github.com/spf13/viper to v1.13.0 (#1260)
Reviewed-on: vikunja/api#1260
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-09-30 20:07:21 +00:00
kolaente 580bd5aeaa
fix(deps): update module github.com/lib/pq to v1.10.7 2022-09-30 19:30:18 +02:00
kolaente c359d6a97d
fix(deps): update golang.org/x/term digest to 7a66f97 2022-09-30 19:29:32 +02:00
kolaente 038702a2a0
fix(deps): update golang.org/x/image digest to e7cb969 2022-09-30 19:29:09 +02:00
kolaente 6426d40825
fix(deps): update module github.com/coreos/go-oidc/v3 to v3.4.0 2022-09-30 19:28:42 +02:00
kolaente bbe102dd57
fix(deps): update module src.techknowlogick.com/xgo to v1.5.0+1.19 2022-09-30 19:28:06 +02:00
kolaente 65484bc432
fix(deps): update golang.org/x/oauth2 digest to f213421 2022-09-30 19:27:19 +02:00
kolaente 54f6cc7a64
fix(deps): update golang.org/x/sync digest to 8fcdb60 2022-09-30 19:26:17 +02:00
kolaente f1b2338227
chore(deps): update klakegg/hugo docker tag to v0.101.0 2022-09-30 19:24:49 +02:00
kolaente 45defebcf4
fix: tasks with the same assignee as doer should not appear twice in overdue task mails 2022-09-30 18:35:40 +02:00
kolaente 86ee8273bc
chore: upgrade echo 2022-09-30 13:48:18 +02:00
Luca Bernstein 3adfeb3b34 fix(namespaces): add list subscriptions (#1254)
Add list subscriptions to namespaces call to enable frontend to show subscription state correctly.

Resolves https://github.com/go-vikunja/frontend/issues/75

Reviewed-on: vikunja/api#1254
Reviewed-by: konrad <k@knt.li>
Co-authored-by: Luca Bernstein <luca@lucabernstein.com>
Co-committed-by: Luca Bernstein <luca@lucabernstein.com>
2022-09-29 09:49:24 +00:00
Felix Breidenstein 9bb8a26706
fix(docs): Fix redirect_url example (#50)
The name of the openid provider gets appended to the redirect_url
2022-09-28 15:34:53 +02:00
Luca Bernstein 54b7f7127c fix(caldav): no failed login emails for tokens (#1252)
Prevent Vikunja from sending mail notifications for failed login attempts if CalDav token is used.

Before, as the provided password value was tested against the user password regardless of whether it was a CalDav token, it triggered a failed login attempt email every three times.

Reviewed-on: vikunja/api#1252
Reviewed-by: konrad <k@knt.li>
Co-authored-by: Luca Bernstein <luca@lucabernstein.com>
Co-committed-by: Luca Bernstein <luca@lucabernstein.com>
2022-09-27 15:12:37 +00:00
Luca Bernstein 25609db567 fix(mail): pass mail server timeout (#1253)
Fix error log for mailserver closing logic, as default timeout of 15 seconds of mail client package used triggers before our logic leading to error on close.

Resolves https://github.com/go-vikunja/api/issues/48

Reviewed-on: vikunja/api#1253
Reviewed-by: konrad <k@knt.li>
Co-authored-by: Luca Bernstein <luca@lucabernstein.com>
Co-committed-by: Luca Bernstein <luca@lucabernstein.com>
2022-09-26 16:09:39 +00:00
kolaente 2e3603507c
fix(docs): document pnpm instead of yarn 2022-09-23 12:26:42 +02:00
kolaente 2efc1b5a87
feat(docs): add docs about how to deploy Vikunja in a subdirectory 2022-09-23 12:23:59 +02:00
48 changed files with 1044 additions and 615 deletions

View File

@ -132,14 +132,15 @@ steps:
event: [ push, tag, pull_request ]
- name: lint
image: golang:1.17-alpine
image: golang:1.19-alpine
pull: true
environment:
GOPROXY: 'https://goproxy.kolaente.de'
depends_on: [ build ]
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.47.3
- wget -O - -q https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.49.0
- ./mage-static check:all
when:
event: [ push, tag, pull_request ]
@ -502,7 +503,7 @@ steps:
depends_on: [ sign-release ]
# Build os packages and push it to our bucket
- name: build-os-packages
- name: build-os-packages-unstable
image: goreleaser/nfpm
pull: true
commands:
@ -511,6 +512,25 @@ steps:
- mv dist/os-packages/vikunja*.x86_64.rpm dist/os-packages/vikunja-unstable-x86_64.rpm
- mv dist/os-packages/vikunja*_amd64.deb dist/os-packages/vikunja-unstable-amd64.deb
- mv dist/os-packages/vikunja*_x86_64.apk dist/os-packages/vikunja-unstable-x86_64.apk
when:
branch:
- main
event:
- push
depends_on: [ static-build-linux ]
- name: build-os-packages-version
image: goreleaser/nfpm
pull: true
commands:
- apk add git go
- ./mage-static release:packages
- mv dist/os-packages/vikunja*.x86_64.rpm dist/os-packages/vikunja-${DRONE_TAG##v}-x86_64.rpm
- mv dist/os-packages/vikunja*_amd64.deb dist/os-packages/vikunja-${DRONE_TAG##v}-amd64.deb
- mv dist/os-packages/vikunja*_x86_64.apk dist/os-packages/vikunja-${DRONE_TAG##v}-x86_64.apk
when:
event:
- tag
depends_on: [ static-build-linux ]
# Push the os releases to our pseudo-s3-bucket
@ -534,7 +554,7 @@ steps:
- main
event:
- push
depends_on: [ build-os-packages ]
depends_on: [ build-os-packages-unstable ]
- name: release-os-version
image: plugins/s3
@ -554,44 +574,7 @@ steps:
when:
event:
- tag
depends_on: [ build-os-packages ]
### Broken, disabled until we figure out how to fix it
# - name: deb-structure
# image: kolaente/reprepro
# pull: true
# environment:
# GPG_PRIVATE_KEY:
# from_secret: gpg_privatekey
# commands:
# - export GPG_TTY=$(tty)
# - gpg -qk
# - echo "use-agent" >> ~/.gnupg/gpg.conf
# - gpgconf --kill gpg-agent
# - echo $GPG_PRIVATE_KEY > ~/frederik.gpg
# - gpg --import ~/frederik.gpg
# - mkdir debian/conf -p
# - cp build/reprepro-dist-conf debian/conf/distributions
# - ./mage-static release:reprepro
# depends_on: [ build-os-packages ]
# Push the releases to our pseudo-s3-bucket
- name: release-deb
image: plugins/s3
pull: true
settings:
bucket: vikunja-releases
access_key:
from_secret: aws_access_key_id
secret_key:
from_secret: aws_secret_access_key
endpoint: https://s3.fr-par.scw.cloud
region: fr-par
path_style: true
strip_prefix: debian
source: debian/*/*/*/*/*
target: /deb/
# depends_on: [ deb-structure ]
depends_on: [ build-os-packages-version ]
---
kind: pipeline
@ -622,7 +605,7 @@ steps:
- tar -xzf vikunja-theme.tar.gz
- name: build
image: klakegg/hugo:0.93.3
image: klakegg/hugo:0.101.0
pull: true
commands:
- cd docs
@ -878,6 +861,6 @@ steps:
- failure
---
kind: signature
hmac: e72b631f902689777e3263ae9527e5aa47738b9021538f7cb5034f95ac265f07
hmac: 8b0e235be5cc1aa66fa02f2c5e86508c183ea50e42dd6ff134568a7a7b7d312c
...

View File

@ -1,44 +0,0 @@
<!--
Please fill out this issue template to report a bug.
If you want to propose a new feature, please open a discussion thread in the forum: https://community.vikunja.io
-->
**Version information:**
Frontend Version:
API Version:
Browser and OS Version:
**Steps to reproduce:**
<!--
Add clear steps to reproduce the bug. Provide screenshots where applicable.
-->
1.
2.
...
**Expected behavior:**
<!--
Describe what happened.
-->
**Actual behavior:**
<!--
Describe what happened instead.
-->
**Checklist:**
* [ ] I have provided all required information
* [ ] I am using the latest release or the latest unstable build
* [ ] I was able to reproduce the bug on [try](https://try.vikunja.io)

View File

@ -0,0 +1,58 @@
name: Bug Report
description: Found something you weren't expecting? Report it here!
labels: kind/bug
body:
- type: markdown
attributes:
value: |
NOTE: If your issue is a security concern, please send an email to security@vikunja.io instead of opening a public issue.
- type: markdown
attributes:
value: |
Please fill out this issue template to report a bug.
1. If you want to propose a new feature, please open a discussion thread in the forum: https://community.vikunja.io
2. Please ask questions or configuration/deploy problems on our [Matrix Room](https://matrix.to/#/#vikunja:matrix.org) or forum (https://community.vikunja.io).
3. Make sure you are using the latest release and
take a moment to check that your issue hasn't been reported before.
4. Please give all relevant information below for bug reports, because
incomplete details will be handled as an invalid report and closed.
- type: textarea
id: description
attributes:
label: Description
description: |
Please provide a description of your issue here, with a URL if you were able to reproduce the issue (see below).
- type: input
id: frontend-version
attributes:
label: Vikunja Frontend Version
description: Vikunja frontend version (or commit reference) of your instance
validations:
required: true
- type: input
id: api-version
attributes:
label: Vikunja API Version
description: Vikunja API version (or commit reference) of your instance
validations:
required: true
- type: input
id: browser-version
attributes:
label: Browser and version
description: If your issue is related to a frontend problem, please provide the browser and version you used to reproduce it.
- type: dropdown
id: can-reproduce
attributes:
label: Can you reproduce the bug on the Vikunja demo site?
options:
- "Yes"
- "No"
validations:
required: true
- type: textarea
id: screenshots
attributes:
label: Screenshots
description: If this issue involves the Web Interface, please provide one or more screenshots

View File

@ -1,11 +0,0 @@
# Description
# Checklist
* [ ] I added or improved tests
* [ ] I added or improved docs for my feature
* [ ] Swagger (including `mage do-the-swag`)
* [ ] Error codes
* [ ] New config options (including adding them to `config.yml.saml` and running `mage generate-docs`)

58
.github/ISSUE_TEMPLATE/bug-report.yml vendored Normal file
View File

@ -0,0 +1,58 @@
name: Bug Report
description: Found something you weren't expecting? Report it here!
labels: kind/bug
body:
- type: markdown
attributes:
value: |
NOTE: If your issue is a security concern, please send an email to security@vikunja.io instead of opening a public issue.
- type: markdown
attributes:
value: |
Please fill out this issue template to report a bug.
1. If you want to propose a new feature, please open a discussion thread in the forum: https://community.vikunja.io
2. Please ask questions or configuration/deploy problems on our [Matrix Room](https://matrix.to/#/#vikunja:matrix.org) or forum (https://community.vikunja.io).
3. Make sure you are using the latest release and
take a moment to check that your issue hasn't been reported before.
4. Please give all relevant information below for bug reports, because
incomplete details will be handled as an invalid report and closed.
- type: textarea
id: description
attributes:
label: Description
description: |
Please provide a description of your issue here, with a URL if you were able to reproduce the issue (see below).
- type: input
id: frontend-version
attributes:
label: Vikunja Frontend Version
description: Vikunja frontend version (or commit reference) of your instance
validations:
required: true
- type: input
id: api-version
attributes:
label: Vikunja API Version
description: Vikunja API version (or commit reference) of your instance
validations:
required: true
- type: input
id: browser-version
attributes:
label: Browser and version
description: If your issue is related to a frontend problem, please provide the browser and version you used to reproduce it.
- type: dropdown
id: can-reproduce
attributes:
label: Can you reproduce the bug on the Vikunja demo site?
options:
- "Yes"
- "No"
validations:
required: true
- type: textarea
id: screenshots
attributes:
label: Screenshots
description: If this issue involves the Web Interface, please provide one or more screenshots

17
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@ -0,0 +1,17 @@
blank_issues_enabled: false
contact_links:
- name: Frontend issues
url: https://code.vikunja.io/frontend/issues
about: This is the API repo. Please open frontend-related bug reports and discussions in the frontend repo. Not sure if you issue is frontend or api? Ask in Matrix or the forum first.
- name: Forum
url: https://community.vikunja.io/
about: Feature Requests, Questions, configuration or deployment problems should be discussed in the forum.
- name: Security-related issues
url: https://vikunja.io/contact/#security
about: For security concerns, please send a mail to security@vikunja.io instead of opening a public issue.
- name: Chat on Matrix
url: https://matrix.to/#/#vikunja:matrix.org
about: Please ask any quick questions here.
- name: Translations
url: https://crowdin.com/project/vikunja
about: Any problems or requests for new languages about translations should be handled in crowdin.

2
.gitignore vendored
View File

@ -4,6 +4,8 @@
config.yml
config.yaml
!docs/config.yml
!.github/ISSUE_TEMPLATE/config.yml
!.gitea/ISSUE_TEMPLATE/config.yml
docs/themes/
*.db
Run

View File

@ -1,7 +1,7 @@
##############
# Build stage
FROM golang:1.18-alpine AS build-env
FROM golang:1.19-alpine AS build-env
RUN apk --no-cache add build-base git && \
go install github.com/magefile/mage@latest && \

View File

@ -1,7 +1,7 @@
##############
# Build stage
FROM golang:1.18-buster AS build-env
FROM golang:1.19-buster AS build-env
RUN go install github.com/magefile/mage@latest && \
mv /go/bin/mage /usr/local/go/bin

View File

@ -324,3 +324,28 @@ metrics:
username:
# If set to a non-empty value the /metrics endpoint will require this as a password via basic auth in combination with the username below.
password:
# Provide default settings for new users. When a new user is created, these settings will automatically be set for the user. If you change them in the config file afterwards they will not be changed back for existing users.
defaultsettings:
# The avatar source for the user. Can be `gravatar`, `initials`, `upload` or `marble`. If you set this to `upload` you'll also need to specify `defaultsettings.avatar_file_id`.
avatar_provider: initials
# The id of the file used as avatar.
avatar_file_id: 0
# If set to true users will get task reminders via email.
email_reminders_enabled: false
# If set to true will allow other users to find this user when searching for parts of their name.
discoverable_by_name: false
# If set to true will allow other users to find this user when searching for their exact email.
discoverable_by_email: false
# If set to true will send an email every day with all overdue tasks at a configured time.
overdue_tasks_reminders_enabled: true
# When to send the overdue task reminder email.
overdue_tasks_reminders_time: 9:00
# 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.
language: <unset>
# The time zone of each individual user. This will affect when users get reminders and overdue task emails.
timezone: <time zone set at service.timezone>

View File

@ -1,5 +1,5 @@
---
date: "2019-02-12:00:00+02:00"
date: "2022-09-21:00:00+02:00"
title: "Development"
toc: true
draft: false
@ -36,13 +36,13 @@ Make sure to check the other doc articles for specific development tasks like [t
## Frontend requirements
The code for the frontend is located at [code.vikunja.io/frontend](https://code.vikunja.io/frontend).
More instructions can be found in the repo's README.
You need to have yarn v1 and nodejs in version 16 installed.
You need to have [pnpm](https://pnpm.io/) and nodejs in version 16 or 18 installed.
## Git flow
The `main` branch is the latest and bleeding edge branch with all changes. Unstable releases are automatically
created from this branch.
The `main` branch is the latest and bleeding edge branch with all changes. Unstable releases are automatically created from this branch.
A release gets tagged from the main branch with the version name as tag name.
@ -50,7 +50,6 @@ Backports and point-releases should go to a `release/version` branch, based on t
## Conventional commits
We're using [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) because they greatly simplify
generating release notes.
We're using [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) because they greatly simplify generating release notes.
It is not required to use them when creating a PR, but appreciated.

View File

@ -98,12 +98,12 @@ Check out the docs [in the frontend repo](https://kolaente.dev/vikunja/frontend/
To run the frontend unit tests, run
{{< highlight bash >}}
yarn test:unit
pnpm run test:unit
{{< /highlight >}}
The frontend also has a watcher available that re-runs all unit tests every time you change something.
To use it, simply run
{{< highlight bash >}}
yarn test:unit-watch
pnpm run test:unit-watch
{{< /highlight >}}

View File

@ -1,5 +1,5 @@
---
date: "2019-02-12:00:00+02:00"
date: "2022-09-21:00:00+02:00"
title: "Build from sources"
draft: false
type: "doc"
@ -16,13 +16,13 @@ To completely build Vikunja from source, you need to build the api and frontend.
## API
The Vikunja API has no other dependencies than go itself.
The Vikunja API has no other dependencies than go itself.
That means compiling it boils down to these steps:
1. Make sure [Go](https://golang.org/doc/install) is properly installed on your system. You'll need at least Go `1.17`.
2. Make sure [Mage](https://magefile.org) is properly installed on your system.
3. Clone the repo with `git clone https://code.vikunja.io/api` and switch into the directory.
3. Run `mage build:build` in the source of this repo. This will build a binary in the root of the repo which will be able to run on your system.
4. Run `mage build:build` in the source of this repo. This will build a binary in the root of the repo which will be able to run on your system.
*Note:* Static ressources such as email templates are built into the binary.
For these to work, you may need to run `mage build:generate` before building the vikunja binary.
@ -38,9 +38,7 @@ More options are available, please refer to the [magefile docs]({{< ref "../deve
The code for the frontend is located at [code.vikunja.io/frontend](https://code.vikunja.io/frontend).
You need to have yarn v1 and nodejs in version 16 installed.
1. Make sure [yarn v1](https://yarnpkg.com/getting-started/install) is properly installed on your system.
3. Clone the repo with `git clone https://code.vikunja.io/frontend` and switch into the directory.
3. Install all dependencies with `yarn install`
4. Build the frontend with `yarn build`. This will result in a js bundle in the `dist/` folder which you can deploy.
1. Make sure you have [pnpm](https://pnpm.io/installation) properly installed on your system.
2. Clone the repo with `git clone https://code.vikunja.io/frontend` and switch into the directory.
3. Install all dependencies with `pnpm install`
4. Build the frontend with `pnpm run build`. This will result in a static js bundle in the `dist/` folder which you can deploy.

View File

@ -1174,3 +1174,132 @@ Full path: `metrics.password`
Environment path: `VIKUNJA_METRICS_PASSWORD`
---
## defaultsettings
Provide default settings for new users. When a new user is created, these settings will automatically be set for the user. If you change them in the config file afterwards they will not be changed back for existing users.
### avatar_provider
The avatar source for the user. Can be `gravatar`, `initials`, `upload` or `marble`. If you set this to `upload` you'll also need to specify `defaultsettings.avatar_file_id`.
Default: `initials`
Full path: `defaultsettings.avatar_provider`
Environment path: `VIKUNJA_DEFAULTSETTINGS_AVATAR_PROVIDER`
### avatar_file_id
The id of the file used as avatar.
Default: `0`
Full path: `defaultsettings.avatar_file_id`
Environment path: `VIKUNJA_DEFAULTSETTINGS_AVATAR_FILE_ID`
### email_reminders_enabled
If set to true users will get task reminders via email.
Default: `false`
Full path: `defaultsettings.email_reminders_enabled`
Environment path: `VIKUNJA_DEFAULTSETTINGS_EMAIL_REMINDERS_ENABLED`
### discoverable_by_name
If set to true will allow other users to find this user when searching for parts of their name.
Default: `false`
Full path: `defaultsettings.discoverable_by_name`
Environment path: `VIKUNJA_DEFAULTSETTINGS_DISCOVERABLE_BY_NAME`
### discoverable_by_email
If set to true will allow other users to find this user when searching for their exact email.
Default: `false`
Full path: `defaultsettings.discoverable_by_email`
Environment path: `VIKUNJA_DEFAULTSETTINGS_DISCOVERABLE_BY_EMAIL`
### overdue_tasks_reminders_enabled
If set to true will send an email every day with all overdue tasks at a configured time.
Default: `true`
Full path: `defaultsettings.overdue_tasks_reminders_enabled`
Environment path: `VIKUNJA_DEFAULTSETTINGS_OVERDUE_TASKS_REMINDERS_ENABLED`
### overdue_tasks_reminders_time
When to send the overdue task reminder email.
Default: `9:00`
Full path: `defaultsettings.overdue_tasks_reminders_time`
Environment path: `VIKUNJA_DEFAULTSETTINGS_OVERDUE_TASKS_REMINDERS_TIME`
### default_list_id
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_list_id`
Environment path: `VIKUNJA_DEFAULTSETTINGS_DEFAULT_LIST_ID`
### week_start
Start of the week for the user. `0` is sunday, `1` is monday and so on.
Default: `0`
Full path: `defaultsettings.week_start`
Environment path: `VIKUNJA_DEFAULTSETTINGS_WEEK_START`
### language
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.
Default: `<unset>`
Full path: `defaultsettings.language`
Environment path: `VIKUNJA_DEFAULTSETTINGS_LANGUAGE`
### timezone
The time zone of each individual user. This will affect when users get reminders and overdue task emails.
Default: `<time zone set at service.timezone>`
Full path: `defaultsettings.timezone`
Environment path: `VIKUNJA_DEFAULTSETTINGS_TIMEZONE`

View File

@ -37,7 +37,7 @@ Authelia config:
description: Vikunja
secret: <vikunja secret>
redirect_uris:
- https://vikunja.mydomain.com/auth/openid/ <----- Matching slash at the end
- https://vikunja.mydomain.com/auth/openid/authelia
scopes:
- openid
- email

View File

@ -0,0 +1,39 @@
---
title: "Running Vikunja in a subdirectory"
date: 2022-09-23T12:15:04+02:00
draft: false
menu:
sidebar:
parent: "setup"
---
# Running Vikunja in a subdirectory
Running Vikunja in a subdirectory is not supported out of the box.
However, you can still run it in a subdirectory but need to build the frontend yourself.
## Frontend
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.
Then, run
```
pnpm vite build --base=/SUBPATH
pnpm workbox copyLibraries dist/
```
Where `SUBPATH` is the subdirectory you want to run Vikunja on.
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.
## API
If you're not using a reverse proxy you're good to go.
Simply configure the api url in the frontend as you normally would.
If you're using a reverse proxy you'll need to adjust the paths so that the api is available at `/SUBPATH/api/v1`.
You can check if everything is working correctly by opening `/SUBPATH/api/v1/info` in a browser.

View File

@ -54,13 +54,14 @@ This document describes the different errors Vikunja can return.
| ErrorCode | HTTP Status Code | Description |
|-----------|------------------|-------------------------------------------------------------------------------------------------------------------------------|
| 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". |
| 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

54
go.mod
View File

@ -25,7 +25,7 @@ require (
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
github.com/coreos/go-oidc/v3 v3.2.0
github.com/coreos/go-oidc/v3 v3.4.0
github.com/cweill/gotests v1.6.0
github.com/d4l3k/messagediff v1.2.1
github.com/disintegration/imaging v1.6.2
@ -41,10 +41,10 @@ require (
github.com/iancoleman/strcase v0.2.0
github.com/imdario/mergo v0.3.13
github.com/jinzhu/copier v0.3.5
github.com/labstack/echo/v4 v4.8.0
github.com/labstack/echo/v4 v4.9.0
github.com/labstack/gommon v0.3.1
github.com/lib/pq v1.10.6
github.com/magefile/mage v1.13.0
github.com/lib/pq v1.10.7
github.com/magefile/mage v1.14.0
github.com/mattn/go-sqlite3 v1.14.15
github.com/olekukonko/tablewriter v0.0.5
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
@ -54,27 +54,26 @@ require (
github.com/samedi/caldav-go v3.0.0+incompatible
github.com/spf13/afero v1.9.2
github.com/spf13/cobra v1.5.0
github.com/spf13/viper v1.12.0
github.com/spf13/viper v1.13.0
github.com/stretchr/testify v1.8.0
github.com/swaggo/swag v1.8.4
github.com/swaggo/swag v1.8.6
github.com/tkuchiki/go-timezone v0.2.2
github.com/ulule/limiter/v3 v3.10.0
github.com/vectordotdev/go-datemath v0.1.1-0.20211214182920-0a4ac8742b93
github.com/wneessen/go-mail v0.2.6
github.com/yuin/goldmark v1.4.13
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa
golang.org/x/image v0.0.0-20220722155232-062f8c9fd539
golang.org/x/oauth2 v0.0.0-20220808172628-8227340efae7
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035
github.com/wneessen/go-mail v0.2.9
github.com/yuin/goldmark v1.5.2
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be
golang.org/x/image v0.0.0-20220902085622-e7cb96979f69
golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1
golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec
golang.org/x/term v0.0.0-20220919170432-7a66f970e087
gopkg.in/d4l3k/messagediff.v1 v1.2.1
gopkg.in/yaml.v3 v3.0.1
src.techknowlogick.com/xgo v1.4.1-0.20210311222705-d25c33fcd864
src.techknowlogick.com/xormigrate v1.4.0
xorm.io/builder v0.3.9
xorm.io/core v0.7.3
xorm.io/xorm v1.1.2
src.techknowlogick.com/xgo v1.5.1-0.20220906164532-735bfdfb90d9
src.techknowlogick.com/xormigrate v1.5.0
xorm.io/builder v0.3.12
xorm.io/xorm v1.3.2
)
require (
@ -98,6 +97,7 @@ require (
github.com/go-openapi/jsonreference v0.19.6 // indirect
github.com/go-openapi/spec v0.20.4 // indirect
github.com/go-openapi/swag v0.19.15 // indirect
github.com/goccy/go-json v0.9.11 // indirect
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
@ -111,8 +111,8 @@ require (
github.com/lithammer/shortuuid/v3 v3.0.4 // indirect
github.com/magiconair/properties v1.8.6 // indirect
github.com/mailru/easyjson v0.7.6 // indirect
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mattn/go-colorable v0.1.13 // 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.1 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
@ -120,7 +120,7 @@ require (
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/oklog/ulid v1.3.1 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pelletier/go-toml/v2 v2.0.1 // indirect
github.com/pelletier/go-toml/v2 v2.0.5 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
@ -130,21 +130,21 @@ require (
github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.3.0 // indirect
github.com/subosito/gotenv v1.4.1 // indirect
github.com/syndtr/goleveldb v1.0.0 // indirect
github.com/urfave/cli/v2 v2.3.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.1 // indirect
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e // indirect
golang.org/x/net v0.0.0-20220927171203-f486391704dc // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect
golang.org/x/tools v0.1.10 // indirect
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df // indirect
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/ini.v1 v1.66.4 // indirect
gopkg.in/square/go-jose.v2 v2.5.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)

686
go.sum

File diff suppressed because it is too large Load Diff

View File

@ -27,7 +27,6 @@ import (
"fmt"
"github.com/iancoleman/strcase"
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
@ -665,7 +664,7 @@ func (Release) Zip() error {
fmt.Printf("Zipping %s...\n", info.Name())
c := exec.Command("zip", "-r", RootPath+"/"+DIST+"/zip/"+info.Name(), ".", "-i", "*")
c := exec.Command("zip", "-r", RootPath+"/"+DIST+"/zip/"+info.Name()+".zip", ".", "-i", "*")
c.Dir = path
out, err := c.Output()
fmt.Print(string(out))
@ -701,14 +700,14 @@ func (Release) Packages() error {
// Because nfpm does not support templating, we replace the values in the config file and restore it after running
nfpmConfigPath := RootPath + "/nfpm.yaml"
nfpmconfig, err := ioutil.ReadFile(nfpmConfigPath)
nfpmconfig, err := os.ReadFile(nfpmConfigPath)
if err != nil {
return err
}
fixedConfig := strings.ReplaceAll(string(nfpmconfig), "<version>", VersionNumber)
fixedConfig = strings.ReplaceAll(fixedConfig, "<binlocation>", BinLocation)
if err := ioutil.WriteFile(nfpmConfigPath, []byte(fixedConfig), 0); err != nil {
if err := os.WriteFile(nfpmConfigPath, []byte(fixedConfig), 0); err != nil {
return err
}
@ -721,7 +720,7 @@ func (Release) Packages() error {
runAndStreamOutput(binpath, "pkg", "--packager", "rpm", "--target", releasePath)
runAndStreamOutput(binpath, "pkg", "--packager", "apk", "--target", releasePath)
return ioutil.WriteFile(nfpmConfigPath, nfpmconfig, 0)
return os.WriteFile(nfpmConfigPath, nfpmconfig, 0)
}
type Dev mg.Namespace
@ -885,7 +884,7 @@ func (s *` + name + `) Handle(msg *message.Message) (err error) {
if _, err := f.Seek(idx, 0); err != nil {
return err
}
remainder, err := ioutil.ReadAll(f)
remainder, err := io.ReadAll(f)
if err != nil {
return err
}
@ -1074,7 +1073,7 @@ const (
// Generates the config docs from a commented config.yml.sample file in the repo root.
func GenerateDocs() error {
config, err := ioutil.ReadFile("config.yml.sample")
config, err := os.ReadFile("config.yml.sample")
if err != nil {
return err
}
@ -1124,7 +1123,7 @@ func GenerateDocs() error {
// We write the full file to prevent old content leftovers at the end
// I know, there are probably better ways to do this.
if err := ioutil.WriteFile(configDocPath, []byte(fullConfig), 0); err != nil {
if err := os.WriteFile(configDocPath, []byte(fullConfig), 0); err != nil {
return err
}

View File

@ -161,6 +161,18 @@ const (
MetricsEnabled Key = `metrics.enabled`
MetricsUsername Key = `metrics.username`
MetricsPassword Key = `metrics.password`
DefaultSettingsAvatarProvider Key = `defaultsettings.avatar_provider`
DefaultSettingsAvatarFileID Key = `defaultsettings.avatar_file_id`
DefaultSettingsEmailRemindersEnabled Key = `defaultsettings.email_reminders_enabled`
DefaultSettingsDiscoverableByName Key = `defaultsettings.discoverable_by_name`
DefaultSettingsDiscoverableByEmail Key = `defaultsettings.discoverable_by_email`
DefaultSettingsOverdueTaskRemindersEnabled Key = `defaultsettings.overdue_tasks_reminders_enabled`
DefaultSettingsDefaultListID Key = `defaultsettings.default_list_id`
DefaultSettingsWeekStart Key = `defaultsettings.week_start`
DefaultSettingsLanguage Key = `defaultsettings.language`
DefaultSettingsTimezone Key = `defaultsettings.timezone`
DefaultSettingsOverdueTaskRemindersTime Key = `defaultsettings.overdue_tasks_reminders_time`
)
// GetString returns a string config value
@ -371,6 +383,10 @@ func InitDefaultConfig() {
KeyvalueType.setDefault("memory")
// Metrics
MetricsEnabled.setDefault(false)
// Settings
DefaultSettingsAvatarProvider.setDefault("initials")
DefaultSettingsOverdueTaskRemindersEnabled.setDefault(true)
DefaultSettingsOverdueTaskRemindersTime.setDefault("9:00")
}
// InitConfig initializes the config, sets defaults etc.
@ -438,6 +454,10 @@ func InitConfig() {
MigrationMicrosoftTodoRedirectURL.Set(ServiceFrontendurl.GetString() + "migrate/microsoft-todo")
}
if DefaultSettingsTimezone.GetString() == "" {
DefaultSettingsTimezone.Set(ServiceTimeZone.GetString())
}
if ServiceEnableMetrics.GetBool() {
log.Println("WARNING: service.enablemetrics is deprecated and will be removed in a future release. Please use metrics.enable.")
MetricsEnabled.Set(true)

View File

@ -27,9 +27,9 @@ import (
"code.vikunja.io/api/pkg/config"
"code.vikunja.io/api/pkg/log"
xrc "gitea.com/xorm/xorm-redis-cache"
"xorm.io/core"
"xorm.io/xorm"
"xorm.io/xorm/caches"
"xorm.io/xorm/names"
"xorm.io/xorm/schemas"
_ "github.com/go-sql-driver/mysql" // Because.
@ -80,7 +80,7 @@ func CreateDBEngine() (engine *xorm.Engine, err error) {
log.Fatalf("Error parsing time zone: %s", err)
}
engine.SetTZDatabase(loc)
engine.SetMapper(core.GonicMapper{})
engine.SetMapper(names.GonicMapper{})
logger := log.NewXormLogger("")
engine.SetLogger(logger)
@ -149,11 +149,12 @@ func parsePostgreSQLHostPort(info string) (string, string) {
func initPostgresEngine() (engine *xorm.Engine, err error) {
host, port := parsePostgreSQLHostPort(config.DatabaseHost.GetString())
connStr := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=%s sslcert=%s sslkey=%s sslrootcert=%s",
host,
port,
// postgresql://username:password@host:port/dbname[?paramspec]
connStr := fmt.Sprintf("postgres://%s:%s@%s:%s/%s?sslmode=%s&sslcert=%s&sslkey=%s&sslrootcert=%s",
config.DatabaseUser.GetString(),
config.DatabasePassword.GetString(),
host,
port,
config.DatabaseDatabase.GetString(),
config.DatabaseSslMode.GetString(),
config.DatabaseSslCert.GetString(),

View File

@ -23,9 +23,10 @@ import (
"code.vikunja.io/api/pkg/config"
"code.vikunja.io/api/pkg/log"
"github.com/stretchr/testify/assert"
"xorm.io/core"
"xorm.io/xorm"
"xorm.io/xorm/names"
)
// CreateTestEngine creates an instance of the db engine which lives in memory
@ -48,7 +49,7 @@ func CreateTestEngine() (engine *xorm.Engine, err error) {
}
}
engine.SetMapper(core.GonicMapper{})
engine.SetMapper(names.GonicMapper{})
logger := log.NewXormLogger("DEBUG")
logger.ShowSQL(os.Getenv("UNIT_TESTS_VERBOSE") == "1")
engine.SetLogger(logger)

View File

@ -227,7 +227,7 @@ func TestArchived(t *testing.T) {
assertHandlerErrorCode(t, err, models.ErrCodeListIsArchived)
})
t.Run("unarchivable", func(t *testing.T) {
rec, err := testListHandler.testUpdateWithUser(nil, map[string]string{"list": "22"}, `{"title":"LoremIpsum","is_archived":false}`)
rec, err := testListHandler.testUpdateWithUser(nil, map[string]string{"list": "22"}, `{"title":"LoremIpsum","is_archived":false,"namespace_id":1}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"is_archived":false`)
})

View File

@ -232,12 +232,12 @@ func TestLinkSharing(t *testing.T) {
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared write", func(t *testing.T) {
rec, err := testHandlerListWrite.testUpdateWithLinkShare(nil, map[string]string{"list": "2"}, `{"title":"TestLoremIpsum"}`)
rec, err := testHandlerListWrite.testUpdateWithLinkShare(nil, map[string]string{"list": "2"}, `{"title":"TestLoremIpsum","namespace_id":1}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
})
t.Run("Shared admin", func(t *testing.T) {
rec, err := testHandlerListAdmin.testUpdateWithLinkShare(nil, map[string]string{"list": "3"}, `{"title":"TestLoremIpsum"}`)
rec, err := testHandlerListAdmin.testUpdateWithLinkShare(nil, map[string]string{"list": "3"}, `{"title":"TestLoremIpsum","namespace_id":2}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
})

View File

@ -171,7 +171,7 @@ func TestList(t *testing.T) {
t.Run("Update", func(t *testing.T) {
t.Run("Normal", func(t *testing.T) {
// Check the list was loaded successfully afterwards, see testReadOneWithUser
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "1"}, `{"title":"TestLoremIpsum"}`)
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "1"}, `{"title":"TestLoremIpsum","namespace_id":1}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
// The description should not be updated but returned correctly
@ -183,7 +183,7 @@ func TestList(t *testing.T) {
assertHandlerErrorCode(t, err, models.ErrCodeListDoesNotExist)
})
t.Run("Normal with updating the description", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "1"}, `{"title":"TestLoremIpsum","description":"Lorem Ipsum dolor sit amet"}`)
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "1"}, `{"title":"TestLoremIpsum","description":"Lorem Ipsum dolor sit amet","namespace_id":1}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
assert.Contains(t, rec.Body.String(), `"description":"Lorem Ipsum dolor sit amet`)
@ -211,12 +211,12 @@ func TestList(t *testing.T) {
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Team write", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "7"}, `{"title":"TestLoremIpsum"}`)
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "7"}, `{"title":"TestLoremIpsum","namespace_id":6}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
})
t.Run("Shared Via Team admin", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "8"}, `{"title":"TestLoremIpsum"}`)
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "8"}, `{"title":"TestLoremIpsum","namespace_id":6}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
})
@ -227,12 +227,12 @@ func TestList(t *testing.T) {
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via User write", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "10"}, `{"title":"TestLoremIpsum"}`)
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "10"}, `{"title":"TestLoremIpsum","namespace_id":6}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
})
t.Run("Shared Via User admin", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "11"}, `{"title":"TestLoremIpsum"}`)
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "11"}, `{"title":"TestLoremIpsum","namespace_id":6}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
})
@ -243,12 +243,12 @@ func TestList(t *testing.T) {
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via NamespaceTeam write", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "13"}, `{"title":"TestLoremIpsum"}`)
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "13"}, `{"title":"TestLoremIpsum","namespace_id":8}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
})
t.Run("Shared Via NamespaceTeam admin", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "14"}, `{"title":"TestLoremIpsum"}`)
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "14"}, `{"title":"TestLoremIpsum","namespace_id":9}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
})
@ -259,12 +259,12 @@ func TestList(t *testing.T) {
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via NamespaceUser write", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "16"}, `{"title":"TestLoremIpsum"}`)
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "16"}, `{"title":"TestLoremIpsum","namespace_id":11}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
})
t.Run("Shared Via NamespaceUser admin", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "17"}, `{"title":"TestLoremIpsum"}`)
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "17"}, `{"title":"TestLoremIpsum","namespace_id":12}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
})

View File

@ -55,6 +55,7 @@ func getClient() (*mail.Client, error) {
InsecureSkipVerify: config.MailerSkipTLSVerify.GetBool(),
ServerName: config.MailerHost.GetString(),
}),
mail.WithTimeout((config.MailerQueueTimeout.GetDuration() + 3) * time.Second), // 3s more for us to close before mail server timeout
}
if config.MailerUsername.GetString() != "" && config.MailerPassword.GetString() != "" {

View File

@ -283,6 +283,34 @@ func (err *ErrListCannotBelongToAPseudoNamespace) HTTPError() web.HTTPError {
}
}
// ErrListMustBelongToANamespace represents an error where a list must belong to a namespace
type ErrListMustBelongToANamespace struct {
ListID int64
NamespaceID int64
}
// IsErrListMustBelongToANamespace checks if an error is a list must belong to a namespace error.
func IsErrListMustBelongToANamespace(err error) bool {
_, ok := err.(*ErrListMustBelongToANamespace)
return ok
}
func (err *ErrListMustBelongToANamespace) Error() string {
return fmt.Sprintf("List must belong to a namespace [ListID: %d, NamespaceID: %d]", err.ListID, err.NamespaceID)
}
// ErrCodeListMustBelongToANamespace holds the unique world-error code of this error
const ErrCodeListMustBelongToANamespace = 3010
// HTTPError holds the http error description
func (err *ErrListMustBelongToANamespace) HTTPError() web.HTTPError {
return web.HTTPError{
HTTPCode: http.StatusPreconditionFailed,
Code: ErrCodeListMustBelongToANamespace,
Message: "This list must belong to a namespace.",
}
}
// ================
// List task errors
// ================

View File

@ -476,12 +476,22 @@ func addListDetails(s *xorm.Session, lists []*List, a web.Auth) (err error) {
return err
}
subscriptions, err := GetSubscriptions(s, SubscriptionEntityList, listIDs, a)
if err != nil {
log.Errorf("An error occurred while getting list subscriptions for a namespace item: %s", err.Error())
subscriptions = make(map[int64]*Subscription)
}
for _, list := range lists {
// Don't override the favorite state if it was already set from before (favorite saved filters do this)
if list.IsFavorite {
continue
}
list.IsFavorite = favs[list.ID]
if subscription, exists := subscriptions[list.ID]; exists {
list.Subscription = subscription
}
}
if len(fileIDs) == 0 {
@ -630,6 +640,13 @@ func UpdateList(s *xorm.Session, list *List, auth web.Auth, updateListBackground
return
}
if list.NamespaceID == 0 {
return &ErrListMustBelongToANamespace{
ListID: list.ID,
NamespaceID: list.NamespaceID,
}
}
// We need to specify the cols we want to update here to be able to un-archive lists
colsToUpdate := []string{
"title",

View File

@ -66,6 +66,7 @@ func (ld *ListDuplicate) CanCreate(s *xorm.Session, a web.Auth) (canCreate bool,
// @Failure 403 {object} web.HTTPError "The user does not have access to the list or namespace"
// @Failure 500 {object} models.Message "Internal error"
// @Router /lists/{listID}/duplicate [put]
//
//nolint:gocyclo
func (ld *ListDuplicate) Create(s *xorm.Session, doer web.Auth) (err error) {

View File

@ -140,8 +140,9 @@ func TestList_CreateOrUpdate(t *testing.T) {
db.LoadAndAssertFixtures(t)
s := db.NewSession()
list := List{
ID: 99999999,
Title: "test",
ID: 99999999,
Title: "test",
NamespaceID: 1,
}
err := list.Update(s, usr)
assert.Error(t, err)

View File

@ -335,7 +335,7 @@ func getListsForNamespaces(s *xorm.Session, namespaceIDs []int64, archived bool)
func getSharedListsInNamespace(s *xorm.Session, archived bool, doer *user.User) (sharedListsNamespace *NamespaceWithLists, err error) {
// Create our pseudo namespace to hold the shared lists
sharedListsPseudonamespace := SharedListsPseudoNamespace
sharedListsPseudonamespace.Owner = doer
sharedListsPseudonamespace.OwnerID = doer.ID
sharedListsNamespace = &NamespaceWithLists{
sharedListsPseudonamespace,
[]*List{},
@ -385,12 +385,13 @@ func getSharedListsInNamespace(s *xorm.Session, archived bool, doer *user.User)
func getFavoriteLists(s *xorm.Session, lists []*List, namespaceIDs []int64, doer *user.User) (favoriteNamespace *NamespaceWithLists, err error) {
// Create our pseudo namespace with favorite lists
pseudoFavoriteNamespace := FavoritesPseudoNamespace
pseudoFavoriteNamespace.Owner = doer
pseudoFavoriteNamespace.OwnerID = doer.ID
favoriteNamespace = &NamespaceWithLists{
Namespace: pseudoFavoriteNamespace,
Lists: []*List{{}},
}
*favoriteNamespace.Lists[0] = FavoritesPseudoList // Copying the list to be able to modify it later
favoriteNamespace.Lists[0].Owner = doer
for _, list := range lists {
if !list.IsFavorite {
@ -448,7 +449,7 @@ func getSavedFilters(s *xorm.Session, doer *user.User) (savedFiltersNamespace *N
}
savedFiltersPseudoNamespace := SavedFiltersPseudoNamespace
savedFiltersPseudoNamespace.Owner = doer
savedFiltersPseudoNamespace.OwnerID = doer.ID
savedFiltersNamespace = &NamespaceWithLists{
Namespace: savedFiltersPseudoNamespace,
Lists: make([]*List, 0, len(savedFilters)),
@ -479,6 +480,7 @@ func getSavedFilters(s *xorm.Session, doer *user.User) (savedFiltersNamespace *N
// @Success 200 {array} models.NamespaceWithLists "The Namespaces."
// @Failure 500 {object} models.Message "Internal error"
// @Router /namespaces [get]
//
//nolint:gocyclo
func (n *Namespace) ReadAll(s *xorm.Session, a web.Auth, search string, page int, perPage int) (result interface{}, resultCount int, numberOfTotalItems int64, err error) {
if _, is := a.(*LinkSharing); is {
@ -517,6 +519,7 @@ func (n *Namespace) ReadAll(s *xorm.Session, a web.Auth, search string, page int
if err != nil {
return nil, 0, 0, err
}
ownerMap[doer.ID] = doer
if n.NamespacesOnly {
all := makeNamespaceSlice(namespaces, ownerMap, subscriptionsMap)

View File

@ -18,6 +18,7 @@ package models
import (
"bufio"
"sort"
"strconv"
"strings"
"time"
@ -230,14 +231,23 @@ func (n *UndoneTaskOverdueNotification) Name() string {
// UndoneTasksOverdueNotification represents a UndoneTasksOverdueNotification notification
type UndoneTasksOverdueNotification struct {
User *user.User
Tasks []*Task
Tasks map[int64]*Task
}
// ToMail returns the mail notification for UndoneTasksOverdueNotification
func (n *UndoneTasksOverdueNotification) ToMail() *notifications.Mail {
overdueLine := ""
sortedTasks := make([]*Task, 0, len(n.Tasks))
for _, task := range n.Tasks {
sortedTasks = append(sortedTasks, task)
}
sort.Slice(sortedTasks, func(i, j int) bool {
return sortedTasks[i].DueDate.Before(sortedTasks[j].DueDate)
})
overdueLine := ""
for _, task := range sortedTasks {
until := time.Until(task.DueDate).Round(1*time.Hour) * -1
overdueLine += `* [` + task.Title + `](` + config.ServiceFrontendurl.GetString() + "tasks/" + strconv.FormatInt(task.ID, 10) + `), overdue since ` + utils.HumanizeDuration(until) + "\n"
}

View File

@ -228,28 +228,53 @@ func getSubscriberCondForEntity(entityType SubscriptionEntityType, entityID int6
// that task, if there is none it will look for a subscription on the list the task belongs to and if that also
// doesn't exist it will check for a subscription for the namespace the list is belonging to.
func GetSubscription(s *xorm.Session, entityType SubscriptionEntityType, entityID int64, a web.Auth) (subscription *Subscription, err error) {
subs, err := GetSubscriptions(s, entityType, []int64{entityID}, a)
if err != nil || len(subs) == 0 {
return nil, err
}
if sub, exists := subs[entityID]; exists {
return sub, nil // Take exact match first, if available
}
for _, sub := range subs {
return sub, nil // For parents, take next available
}
return nil, nil
}
// GetSubscriptions returns a map of subscriptions to a set of given entity IDs
func GetSubscriptions(s *xorm.Session, entityType SubscriptionEntityType, entityIDs []int64, a web.Auth) (listsToSubscriptions map[int64]*Subscription, err error) {
u, is := a.(*user.User)
if !is {
return
}
if err := entityType.validate(); err != nil {
return nil, err
}
subscription = &Subscription{}
cond := getSubscriberCondForEntity(entityType, entityID)
exists, err := s.
var entitiesFilter builder.Cond
for _, eID := range entityIDs {
if entitiesFilter == nil {
entitiesFilter = getSubscriberCondForEntity(entityType, eID)
continue
}
entitiesFilter = entitiesFilter.Or(getSubscriberCondForEntity(entityType, eID))
}
var subscriptions []*Subscription
err = s.
Where("user_id = ?", u.ID).
And(cond).
Get(subscription)
if !exists {
And(entitiesFilter).
Find(&subscriptions)
if err != nil {
return nil, err
}
subscription.Entity = subscription.EntityType.String()
return subscription, err
listsToSubscriptions = make(map[int64]*Subscription)
for _, sub := range subscriptions {
sub.Entity = sub.EntityType.String()
listsToSubscriptions[sub.EntityID] = sub
}
return listsToSubscriptions, nil
}
func getSubscribersForEntity(s *xorm.Session, entityType SubscriptionEntityType, entityID int64) (subscriptions []*Subscription, err error) {

View File

@ -92,10 +92,10 @@ func getUndoneOverdueTasks(s *xorm.Session, now time.Time) (usersWithTasks map[i
if !exists {
uts[t.User.ID] = &userWithTasks{
user: t.User,
tasks: []*Task{},
tasks: make(map[int64]*Task),
}
}
uts[t.User.ID].tasks = append(uts[t.User.ID].tasks, t.Task)
uts[t.User.ID].tasks[t.Task.ID] = t.Task
}
}
@ -104,7 +104,7 @@ func getUndoneOverdueTasks(s *xorm.Session, now time.Time) (usersWithTasks map[i
type userWithTasks struct {
user *user.User
tasks []*Task
tasks map[int64]*Task
}
// RegisterOverdueReminderCron registers a function which checks once a day for tasks that are overdue and not done.

View File

@ -979,6 +979,7 @@ func createTask(s *xorm.Session, t *Task, a web.Auth, updateAssignees bool) (err
// @Failure 403 {object} web.HTTPError "The user does not have access to the task (aka its list)"
// @Failure 500 {object} models.Message "Internal error"
// @Router /tasks/{id} [post]
//
//nolint:gocyclo
func (t *Task) Update(s *xorm.Session, a web.Auth) (err error) {
@ -1331,8 +1332,8 @@ func setTaskDatesFromCurrentDateRepeat(oldTask, newTask *Task) {
// This helper function updates the reminders, doneAt, start and end dates of the *old* task
// and saves the new values in the newTask object.
// We make a few assumtions here:
// 1. Everything in oldTask is the truth - we figure out if we update anything at all if oldTask.RepeatAfter has a value > 0
// 2. Because of 1., this functions should not be used to update values other than Done in the same go
// 1. Everything in oldTask is the truth - we figure out if we update anything at all if oldTask.RepeatAfter has a value > 0
// 2. Because of 1., this functions should not be used to update values other than Done in the same go
func updateDone(oldTask *Task, newTask *Task) {
if !oldTask.Done && newTask.Done {
switch oldTask.RepeatMode {

View File

@ -18,7 +18,7 @@ package gravatar
import (
"context"
"io/ioutil"
"io"
"net/http"
"strconv"
"strings"
@ -74,7 +74,7 @@ func (g *Provider) GetAvatar(user *user.User, size int64) ([]byte, string, error
return nil, "", err
}
defer resp.Body.Close()
avatarContent, err := ioutil.ReadAll(resp.Body)
avatarContent, err := io.ReadAll(resp.Body)
if err != nil {
return nil, "", err
}

View File

@ -20,13 +20,14 @@ import (
"bytes"
"image"
"image/png"
"io/ioutil"
"io"
"strconv"
"code.vikunja.io/api/pkg/files"
"code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/modules/keyvalue"
"code.vikunja.io/api/pkg/user"
"github.com/disintegration/imaging"
)
@ -82,7 +83,7 @@ func (p *Provider) GetAvatar(u *user.User, size int64) (avatar []byte, mimeType
return nil, "", err
}
avatar, err = ioutil.ReadAll(buf)
avatar, err = io.ReadAll(buf)
if err != nil {
return nil, "", err
}

View File

@ -18,15 +18,14 @@ package migration
import (
"bytes"
"io/ioutil"
"code.vikunja.io/api/pkg/modules/background/handler"
"io"
"xorm.io/xorm"
"code.vikunja.io/api/pkg/db"
"code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/modules/background/handler"
"code.vikunja.io/api/pkg/user"
)
@ -212,7 +211,7 @@ func insertFromStructure(s *xorm.Session, str []*models.NamespaceWithListsAndTas
// Check if we have a file to create
if len(a.File.FileContent) > 0 {
a.TaskID = t.ID
fr := ioutil.NopCloser(bytes.NewReader(a.File.FileContent))
fr := io.NopCloser(bytes.NewReader(a.File.FileContent))
err = a.NewAttachment(s, fr, a.File.Name, a.File.Size, user)
if err != nil {
return

View File

@ -105,6 +105,10 @@ type item struct {
DateCompleted time.Time `json:"date_completed"`
}
type itemWrapper struct {
Item *item `json:"item"`
}
type doneItem struct {
CompletedDate time.Time `json:"completed_date"`
Content string `json:"content"`
@ -115,7 +119,8 @@ type doneItem struct {
}
type doneItemSync struct {
Items []*doneItem `json:"items"`
Items []*doneItem `json:"items"`
Projects map[string]*project `json:"projects"`
}
type fileAttachment struct {
@ -358,6 +363,11 @@ func convertTodoistToVikunja(sync *sync, doneItems map[int64]*doneItem) (fullVik
tasks[i.ID] = task
if _, exists := lists[i.ProjectID]; !exists {
log.Debugf("[Todoist Migration] Tried to put item %d in project %d but the project does not exist", i.ID, i.ProjectID)
continue
}
lists[i.ProjectID].Tasks = append(lists[i.ProjectID].Tasks, task)
}
@ -540,33 +550,62 @@ func (m *Migration) Migrate(u *user.User) (err error) {
}
log.Debugf("[Todoist Migration] Getting done items for user %d", u.ID)
// Get all done tasks
resp, err = migration.DoPost("https://api.todoist.com/sync/v8/completed/get_all", form)
if err != nil {
return
}
defer resp.Body.Close()
completedSyncResponse := &doneItemSync{}
err = json.NewDecoder(resp.Body).Decode(completedSyncResponse)
if err != nil {
return
}
// Get all done tasks and projects
offset := 0
doneItems := make(map[int64]*doneItem)
sort.Slice(completedSyncResponse.Items, func(i, j int) bool {
return completedSyncResponse.Items[i].CompletedDate.After(completedSyncResponse.Items[j].CompletedDate)
})
doneItems := make(map[int64]*doneItem, len(completedSyncResponse.Items))
for _, i := range completedSyncResponse.Items {
if _, has := doneItems[i.TaskID]; has {
// Only set the newest completion date
continue
for {
resp, err = migration.DoPost("https://api.todoist.com/sync/v8/completed/get_all?limit=200&offset="+strconv.Itoa(offset), form)
if err != nil {
return
}
doneItems[i.TaskID] = i
defer resp.Body.Close()
completedSyncResponse := &doneItemSync{}
err = json.NewDecoder(resp.Body).Decode(completedSyncResponse)
if err != nil {
return
}
sort.Slice(completedSyncResponse.Items, func(i, j int) bool {
return completedSyncResponse.Items[i].CompletedDate.After(completedSyncResponse.Items[j].CompletedDate)
})
for _, i := range completedSyncResponse.Items {
if _, has := doneItems[i.TaskID]; has {
// Only set the newest completion date
continue
}
doneItems[i.TaskID] = i
// need to get done item data
resp, err = migration.DoPost("https://api.todoist.com/sync/v8/items/get", url.Values{
"token": []string{token},
"item_id": []string{strconv.FormatInt(i.TaskID, 10)},
})
if err != nil {
return
}
defer resp.Body.Close()
doneI := &itemWrapper{}
err = json.NewDecoder(resp.Body).Decode(doneI)
if err != nil {
return
}
log.Debugf("[Todoist Migration] Retrieved full task data for done task %d", i.TaskID)
syncResponse.Items = append(syncResponse.Items, doneI.Item)
}
if len(completedSyncResponse.Items) < 200 {
break
}
offset++
log.Debugf("[Todoist Migration] User %d has more than 200 done tasks or projects, looping to get more; iteration %d", u.ID, offset)
}
log.Debugf("[Todoist Migration] Got %d done items for user %d", len(completedSyncResponse.Items), u.ID)
log.Debugf("[Todoist Migration] Got %d done items for user %d", len(doneItems), u.ID)
log.Debugf("[Todoist Migration] Getting archived projects for user %d", u.ID)
// Get all archived projects

View File

@ -17,7 +17,7 @@
package todoist
import (
"io/ioutil"
"os"
"strconv"
"testing"
"time"
@ -47,7 +47,7 @@ func TestConvertTodoistToVikunja(t *testing.T) {
dueTimeWithTime = dueTimeWithTime.In(config.GetTimeZone())
nilTime, err := time.Parse(time.RFC3339Nano, "0001-01-01T00:00:00Z")
assert.NoError(t, err)
exampleFile, err := ioutil.ReadFile(config.ServiceRootpath.GetString() + "/pkg/modules/migration/wunderlist/testimage.jpg")
exampleFile, err := os.ReadFile(config.ServiceRootpath.GetString() + "/pkg/modules/migration/wunderlist/testimage.jpg")
assert.NoError(t, err)
makeTestItem := func(id, projectId int64, hasDueDate, hasLabels, done bool) *item {

View File

@ -18,7 +18,7 @@ package trello
import (
"bytes"
"io/ioutil"
"os"
"testing"
"time"
@ -36,7 +36,7 @@ func TestConvertTrelloToVikunja(t *testing.T) {
time1, err := time.Parse(time.RFC3339Nano, "2014-09-26T08:25:05Z")
assert.NoError(t, err)
exampleFile, err := ioutil.ReadFile(config.ServiceRootpath.GetString() + "/pkg/modules/migration/wunderlist/testimage.jpg")
exampleFile, err := os.ReadFile(config.ServiceRootpath.GetString() + "/pkg/modules/migration/wunderlist/testimage.jpg")
assert.NoError(t, err)
trelloData := []*trello.Board{

View File

@ -17,7 +17,7 @@
package wunderlist
import (
"io/ioutil"
"os"
"strconv"
"testing"
"time"
@ -46,7 +46,7 @@ func TestWunderlistParsing(t *testing.T) {
assert.NoError(t, err)
time4 = time4.In(config.GetTimeZone())
exampleFile, err := ioutil.ReadFile(config.ServiceRootpath.GetString() + "/pkg/modules/migration/wunderlist/testimage.jpg")
exampleFile, err := os.ReadFile(config.ServiceRootpath.GetString() + "/pkg/modules/migration/wunderlist/testimage.jpg")
assert.NoError(t, err)
createTestTask := func(id, listID int, done bool) *task {

View File

@ -22,6 +22,7 @@ import (
"code.vikunja.io/api/pkg/db"
"code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/user"
"xorm.io/xorm"
"github.com/labstack/echo/v4"
"golang.org/x/crypto/bcrypt"
@ -35,37 +36,47 @@ func BasicAuth(username, password string, c echo.Context) (bool, error) {
Username: username,
Password: password,
}
u, err := user.CheckUserCredentials(s, credentials)
if err != nil && !user.IsErrWrongUsernameOrPassword(err) && !user.IsErrAccountIsNotLocal(err) {
log.Errorf("Error during basic auth for caldav: %v", err)
var err error
u, err := checkUserCaldavTokens(s, credentials)
if user.IsErrUserDoesNotExist(err) {
return false, nil
}
if err == nil {
if u == nil {
u, err = user.CheckUserCredentials(s, credentials)
if err != nil {
log.Errorf("Error during basic auth for caldav: %v", err)
return false, nil
}
}
if u != nil && err == nil {
c.Set("userBasicAuth", u)
return true, nil
}
return false, nil
}
tokens, err := user.GetCaldavTokens(u)
func checkUserCaldavTokens(s *xorm.Session, login *user.Login) (*user.User, error) {
usr, err := user.GetUserByUsername(s, login.Username)
if err != nil || usr == nil {
log.Warningf("Error while retrieving users from database: %v", err)
return nil, err
}
tokens, err := user.GetCaldavTokens(usr)
if err != nil {
log.Errorf("Error while getting tokens for caldav auth: %v", err)
return false, nil
return nil, err
}
// Looping over all tokens until we find one that matches
for _, token := range tokens {
err = bcrypt.CompareHashAndPassword([]byte(token.Token), []byte(password))
err = bcrypt.CompareHashAndPassword([]byte(token.Token), []byte(login.Password))
if err != nil {
if errors.Is(err, bcrypt.ErrMismatchedHashAndPassword) {
continue
}
log.Errorf("Error while verifying tokens for caldav auth: %v", err)
return false, nil
return nil, nil
}
c.Set("userBasicAuth", u)
return true, nil
return usr, nil
}
return false, nil
return nil, nil
}

View File

@ -19,7 +19,7 @@ package caldav
import (
"bytes"
"fmt"
"io/ioutil"
"io"
"reflect"
"strconv"
"strings"
@ -61,9 +61,9 @@ func ListHandler(c echo.Context) error {
}
// Try to parse a task from the request payload
body, _ := ioutil.ReadAll(c.Request().Body)
body, _ := io.ReadAll(c.Request().Body)
// Restore the io.ReadCloser to its original state
c.Request().Body = ioutil.NopCloser(bytes.NewBuffer(body))
c.Request().Body = io.NopCloser(bytes.NewBuffer(body))
// Parse it
vtodo := string(body)
if vtodo != "" && strings.HasPrefix(vtodo, `BEGIN:VCALENDAR`) {
@ -127,9 +127,9 @@ func PrincipalHandler(c echo.Context) error {
}
// Try to parse a task from the request payload
body, _ := ioutil.ReadAll(c.Request().Body)
body, _ := io.ReadAll(c.Request().Body)
// Restore the io.ReadCloser to its original state
c.Request().Body = ioutil.NopCloser(bytes.NewBuffer(body))
c.Request().Body = io.NopCloser(bytes.NewBuffer(body))
log.Debugf("[CALDAV] Request Body: %v\n", string(body))
log.Debugf("[CALDAV] Request Headers: %v\n", c.Request().Header)
@ -157,9 +157,9 @@ func EntryHandler(c echo.Context) error {
}
// Try to parse a task from the request payload
body, _ := ioutil.ReadAll(c.Request().Body)
body, _ := io.ReadAll(c.Request().Body)
// Restore the io.ReadCloser to its original state
c.Request().Body = ioutil.NopCloser(bytes.NewBuffer(body))
c.Request().Body = io.NopCloser(bytes.NewBuffer(body))
log.Debugf("[CALDAV] Request Body: %v\n", string(body))
log.Debugf("[CALDAV] Request Headers: %v\n", c.Request().Header)

View File

@ -54,7 +54,17 @@ func CreateUser(s *xorm.Session, user *User) (newUser *User, err error) {
}
user.Status = StatusActive
user.AvatarProvider = "initials"
user.AvatarProvider = config.DefaultSettingsAvatarProvider.GetString()
user.AvatarFileID = config.DefaultSettingsAvatarFileID.GetInt64()
user.EmailRemindersEnabled = config.DefaultSettingsEmailRemindersEnabled.GetBool()
user.DiscoverableByName = config.DefaultSettingsDiscoverableByName.GetBool()
user.DiscoverableByEmail = config.DefaultSettingsDiscoverableByEmail.GetBool()
user.OverdueTasksRemindersEnabled = config.DefaultSettingsOverdueTaskRemindersEnabled.GetBool()
user.OverdueTasksRemindersTime = config.DefaultSettingsOverdueTaskRemindersTime.GetString()
user.DefaultListID = config.DefaultSettingsDefaultListID.GetInt64()
user.WeekStart = config.DefaultSettingsWeekStart.GetInt()
user.Language = config.DefaultSettingsLanguage.GetString()
user.Timezone = config.DefaultSettingsTimezone.GetString()
// Insert it
_, err = s.Insert(user)

View File

@ -369,6 +369,16 @@ func TestListUsers(t *testing.T) {
assert.True(t, len(all) > 0)
assert.Equal(t, all[0].Username, "user1")
})
t.Run("case insensitive", func(t *testing.T) {
db.LoadAndAssertFixtures(t)
s := db.NewSession()
defer s.Close()
all, err := ListUsers(s, "uSEr1", nil)
assert.NoError(t, err)
assert.True(t, len(all) > 0)
assert.Equal(t, all[0].Username, "user1")
})
t.Run("all users", func(t *testing.T) {
db.LoadAndAssertFixtures(t)
s := db.NewSession()

View File

@ -19,8 +19,11 @@ package user
import (
"strings"
"code.vikunja.io/api/pkg/db"
"xorm.io/builder"
"xorm.io/xorm"
"xorm.io/xorm/schemas"
)
type ListUserOpts struct {
@ -45,14 +48,22 @@ func ListUsers(s *xorm.Session, search string, opts *ListUserOpts) (users []*Use
if search != "" {
for _, queryPart := range strings.Split(search, ",") {
var usernameCond builder.Cond = builder.Eq{"username": queryPart}
if db.Type() == schemas.POSTGRES {
usernameCond = builder.Expr("username ILIKE ?", queryPart)
}
if db.Type() == schemas.SQLITE {
usernameCond = builder.Expr("username = ? COLLATE NOCASE", queryPart)
}
conds = append(conds,
builder.Eq{"username": queryPart},
usernameCond,
builder.And(
builder.Eq{"email": queryPart},
builder.Eq{"discoverable_by_email": true},
),
builder.And(
builder.Like{"name", "%" + queryPart + "%"},
db.ILIKE("name", queryPart),
builder.Eq{"discoverable_by_name": true},
),
)