Compare commits

...

6 Commits

Author SHA1 Message Date
kolaente 8fa8b03aa6
fix(tests): only look in src for tests 2023-01-29 20:24:44 +01:00
Yurii Vlasov e4499f44b7 Docker refactoring (#3018)
Co-authored-by: Yurii Vlasov <yv@itsvit.org>
Reviewed-on: vikunja/frontend#3018
Reviewed-by: konrad <k@knt.li>
Co-authored-by: Yurii Vlasov <yuriy@vlasov.pro>
Co-committed-by: Yurii Vlasov <yuriy@vlasov.pro>
2023-01-29 14:47:22 +00:00
kolaente b799233bca
fix(quick add magic): correctly parse "next {weekday}" on the beginning of the text
Resolves vikunja/frontend#3022
2023-01-29 15:32:01 +01:00
renovate be0ae4bc29 chore(deps): update dependency eslint to v8.33.0 2023-01-29 13:49:19 +00:00
renovate 60d99f3bba chore(deps): update pnpm to v7.26.2 2023-01-29 13:48:52 +00:00
renovate fa666d2817 fix(deps): update dependency @vueuse/core to v9.12.0 2023-01-29 04:04:10 +00:00
11 changed files with 357 additions and 278 deletions

View File

@ -1,49 +1,68 @@
# Stage 1: Build application
FROM --platform=$BUILDPLATFORM node:18-alpine AS compile-image
# syntax=docker/dockerfile:1
# ┬─┐┬ ┐o┬ ┬─┐
# │─││ │││ │ │
# ┘─┘┘─┘┘┘─┘┘─┘
FROM node:18-alpine AS builder
WORKDIR /build
ARG USE_RELEASE=false
ARG RELEASE_VERSION=main
ENV PNPM_CACHE_FOLDER .cache/pnpm/
ADD . ./
RUN \
if [ $USE_RELEASE = true ]; then \
wget https://dl.vikunja.io/frontend/vikunja-frontend-$RELEASE_VERSION.zip -O frontend-release.zip && \
unzip frontend-release.zip -d dist/ && \
exit 0; \
fi && \
# https://pnpm.io/installation#using-corepack
corepack enable && \
# we don't use corepack prepare here by intend since
# we have renovate to keep our dependencies up to date
# Build the frontend
pnpm install && \
apk add --no-cache git && \
echo '{"VERSION": "'$(git describe --tags --always --abbrev=10 | sed 's/-/+/' | sed 's/^v//' | sed 's/-g/-/')'"}' > src/version.json && \
pnpm run build
COPY package.json ./
COPY pnpm-lock.yaml ./
# Stage 2: copy
FROM nginx:alpine
RUN if [ "$USE_RELEASE" != true ]; then \
# https://pnpm.io/installation#using-corepack
corepack enable && \
pnpm install; \
fi
COPY nginx.conf /etc/nginx/nginx.conf
COPY scripts/run.sh /run.sh
COPY . ./
# copy compiled files from stage 1
COPY --from=compile-image /build/dist /usr/share/nginx/html
RUN if [ "$USE_RELEASE" != true ]; then \
apk add --no-cache --virtual .build-deps git jq && \
git describe --tags --always --abbrev=10 | sed 's/-/+/; s/^v//; s/-g/-/' | \
xargs -0 -I{} jq -Mcnr --arg version {} '{VERSION:$version}' | \
tee src/version.json && \
apk del .build-deps; \
fi
# Unprivileged user
ENV PUID 1000
ENV PGID 1000
RUN if [ "$USE_RELEASE" = true ]; then \
wget "https://dl.vikunja.io/frontend/vikunja-frontend-${RELEASE_VERSION}.zip" -O frontend-release.zip && \
unzip frontend-release.zip -d dist/; \
else \
# we don't use corepack prepare here by intend since
# we have renovate to keep our dependencies up to date
# Build the frontend
pnpm run build; \
fi
# ┌┐┐┌─┐o┌┐┐┐ │
# ││││ ┬││││┌┼┘
# ┘└┘┘─┘┘┘└┘┘ └
FROM nginx:stable-alpine AS runner
WORKDIR /usr/share/nginx/html
LABEL maintainer="maintainers@vikunja.io"
RUN apk add --no-cache \
# for sh file
bash \
# installs usermod and groupmod
shadow
ENV VIKUNJA_HTTP_PORT 80
ENV VIKUNJA_HTTP2_PORT 81
ENV VIKUNJA_LOG_FORMAT main
ENV VIKUNJA_API_URL http://localhost:3456/api/v1
ENV VIKUNJA_SENTRY_ENABLED false
ENV VIKUNJA_SENTRY_DSN https://85694a2d757547cbbc90cd4b55c5a18d@o1047380.ingest.sentry.io/6024480
CMD "/run.sh"
COPY docker/injector.sh /docker-entrypoint.d/50-injector.sh
COPY docker/nginx.conf /etc/nginx/nginx.conf
COPY docker/templates/. /etc/nginx/templates/
# copy compiled files from stage 1
COPY --from=builder /build/dist ./
# manage permissions
RUN chmod 0755 /docker-entrypoint.d/*.sh /etc/nginx/templates && \
chmod -R 0644 /etc/nginx/nginx.conf && \
chown -R nginx:nginx ./ /etc/nginx/conf.d /etc/nginx/templates
# unprivileged user
USER nginx

View File

@ -18,6 +18,14 @@ If you find any security-related issues you don't want to disclose publicly, ple
## Docker
There is a [docker image available](https://hub.docker.com/r/vikunja/api) with support for http/2 and aggressive caching enabled.
In order to build it from sources run the command below. (Docker >= v19.03)
```shell
export DOCKER_BUILDKIT=1
docker build -t vikunja/frontend .
```
Refer to Refer [to multi-platform documentation](https://docs.docker.com/build/building/multi-platform/) in order to build for the different platform.
## Project setup

15
docker/injector.sh Normal file
View File

@ -0,0 +1,15 @@
#!/usr/bin/env sh
set -e
echo "info: API URL is $VIKUNJA_API_URL"
echo "info: Sentry enabled: $VIKUNJA_SENTRY_ENABLED"
# Escape the variable to prevent sed from complaining
VIKUNJA_API_URL="$(echo "$VIKUNJA_API_URL" | sed -r 's/([:;])/\\\1/g')"
VIKUNJA_SENTRY_DSN="$(echo "$VIKUNJA_SENTRY_DSN" | sed -r 's/([:;])/\\\1/g')"
sed -ri "s:^(\s*window.API_URL\s*=)\s*.+:\1 '${VIKUNJA_API_URL}':g" /usr/share/nginx/html/index.html
sed -ri "s:^(\s*window.SENTRY_ENABLED\s*=)\s*.+:\1 ${VIKUNJA_SENTRY_ENABLED}:g" /usr/share/nginx/html/index.html
sed -ri "s:^(\s*window.SENTRY_DSN\s*=)\s*.+:\1 '${VIKUNJA_SENTRY_DSN}':g" /usr/share/nginx/html/index.html
date -uIseconds | xargs echo 'info: started at'

112
docker/nginx.conf Normal file
View File

@ -0,0 +1,112 @@
# Generated by nginxconfig.io
# https://www.digitalocean.com/community/tools/nginx?domains.0.server.domain=localhost&domains.0.server.documentRoot=%2Fusr%2Fshare%2Fnginx%2Fhtml&domains.0.server.cdnSubdomain=true&domains.0.https.https=false&domains.0.php.php=false&domains.0.routing.index=index.html&domains.0.routing.fallbackHtml=true&domains.0.routing.fallbackPhp=false&global.performance.assetsExpiration=1d&global.performance.mediaExpiration=1d&global.performance.svgExpiration=1d&global.performance.fontsExpiration=1d&global.logging.accessLog=%2Fdev%2Fstdout&global.logging.errorLog=%2Fdev%2Fstderr%20warn&global.logging.logNotFound=true&global.nginx.user=nginx&global.nginx.pid=%2Fvar%2Frun%2Fnginx.pid&global.nginx.clientMaxBodySize=50&global.docker.dockerfile=true&global.tools.modularizedStructure=false&global.tools.symlinkVhost=false
# and then edited manually ;)
pid /tmp/nginx.pid;
worker_processes auto;
worker_rlimit_nofile 65535;
events {
multi_accept on;
worker_connections 1024;
}
http {
charset utf-8;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
server_tokens off;
types_hash_max_size 2048;
types_hash_bucket_size 64;
# rootless
client_body_temp_path /tmp/client_temp;
proxy_temp_path /tmp/proxy_temp_path;
fastcgi_temp_path /tmp/fastcgi_temp;
uwsgi_temp_path /tmp/uwsgi_temp;
scgi_temp_path /tmp/scgi_temp;
# MIME
include mime.types;
default_type application/octet-stream;
types {
application/manifest+json webmanifest;
}
# Logging
log_format json escape=json
'{'
'"bytes_sent": "$bytes_sent",'
'"http_user_agent": "$http_user_agent",'
'"nginx_version": "$nginx_version",'
'"query_string": "$query_string",'
'"realip_remote_addr": "$realip_remote_addr",'
'"remote_addr": "$remote_addr",'
'"remote_user": "$remote_user",'
'"request_length": "$request_length",'
'"request_method": "$request_method",'
'"request_time": "$request_time",'
'"server_addr": "$server_addr",'
'"server_port": "$server_port",'
'"server_protocol": "$server_protocol",'
'"status": "$status",'
'"time_local": "$time_local",'
'"uri": "$uri"'
'}';
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /dev/stdout main;
error_log /dev/stderr warn;
keepalive_timeout 65;
# compression
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_min_length 256;
gzip_types
text/plain
text/css
application/json
application/x-javascript
application/javascript
text/xml
application/xml
application/xml+rss
text/javascript
application/vnd.ms-fontobject
application/x-font-ttf
font/opentype
image/svg+xml
image/x-icon
audio/wav;
map_hash_max_size 128;
map_hash_bucket_size 128;
map $sent_http_content_type $expires {
default off;
text/css max;
application/javascript max;
text/javascript max;
application/vnd.ms-fontobject max;
application/x-font-ttf max;
font/opentype max;
font/woff2 max;
image/svg+xml max;
image/x-icon max;
audio/wav max;
~images/ max;
~font/ max;
}
include /etc/nginx/conf.d/*.conf;
}

View File

@ -0,0 +1,71 @@
server {
listen ${VIKUNJA_HTTP_PORT};
listen [::]:${VIKUNJA_HTTP_PORT};
## Needed when behind HAProxy with SSL termination + HTTP/2 support
listen ${VIKUNJA_HTTP2_PORT} default_server http2 proxy_protocol;
listen [::]:${VIKUNJA_HTTP2_PORT} default_server http2 proxy_protocol;
server_name _;
expires $expires;
root /usr/share/nginx/html;
access_log /dev/stdout ${VIKUNJA_LOG_FORMAT};
# security headers
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
add_header Content-Security-Policy "default-src 'self' http: https: ws: wss: data: blob: 'unsafe-inline'; frame-ancestors 'self';" always;
add_header Permissions-Policy "interest-cohort=()" always;
# . files
location ~ /\.(?!well-known) {
deny all;
}
# assume that everything else is handled by the application router, by injecting the index.html.
location / {
autoindex off;
expires off;
add_header Cache-Control "public, max-age=0, s-maxage=0, must-revalidate" always;
try_files $uri /index.html =404;
}
# favicon.ico
location = /favicon.ico {
log_not_found off;
access_log off;
}
# robots.txt
location = /robots.txt {
log_not_found off;
access_log off;
expires -1; # no-cache
}
location = /ready {
return 200 "";
access_log off;
expires -1; # no-cache
}
# all assets contain hash in filename, cache forever
location ^~ /assets/ {
add_header Cache-Control "public, max-age=31536000, s-maxage=31536000, immutable";
try_files $uri =404;
}
# all workbox scripts are compiled with hash in filename, cache forever3
location ^~ /workbox- {
add_header Cache-Control "public, max-age=31536000, s-maxage=31536000, immutable";
try_files $uri =404;
}
# assets, media
location ~* .(txt|webmanifest|css|js|mjs|map|svg|jpg|jpeg|png|ico|ttf|woff|woff2|wav)$ {
try_files $uri $uri/ =404;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html { }
}

View File

@ -1,117 +0,0 @@
user nginx;
worker_processes 1;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
types {
application/manifest+json webmanifest;
}
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_min_length 256;
gzip_types
text/plain
text/css
application/json
application/x-javascript
application/javascript
text/xml
application/xml
application/xml+rss
text/javascript
application/vnd.ms-fontobject
application/x-font-ttf
font/opentype
image/svg+xml
image/x-icon
audio/wav;
map_hash_max_size 128;
map_hash_bucket_size 128;
# Expires map
map $sent_http_content_type $expires {
default off;
text/css max;
application/javascript max;
text/javascript max;
application/vnd.ms-fontobject max;
application/x-font-ttf max;
font/opentype max;
font/woff2 max;
image/svg+xml max;
image/x-icon max;
audio/wav max;
~images/ max;
~font/ max;
}
server {
listen 80;
listen [::]:80;
listen 81 default_server http2 proxy_protocol; ## Needed when behind HAProxy with SSL termination + HTTP/2 support
listen [::]:81 default_server http2 proxy_protocol; ## Needed when behind HAProxy with SSL termination + HTTP/2 support
server_name _;
expires $expires;
root /usr/share/nginx/html;
# all assets contain hash in filename, cache forever
location ^~ /assets/ {
add_header Cache-Control "public, max-age=31536000, s-maxage=31536000, immutable";
try_files $uri =404;
}
# all workbox scripts are compiled with hash in filename, cache forever3
location ^~ /workbox- {
add_header Cache-Control "public, max-age=31536000, s-maxage=31536000, immutable";
try_files $uri =404;
}
# assume that everything else is handled by the application router, by injecting the index.html.
location / {
autoindex off;
expires off;
add_header Cache-Control "public, max-age=0, s-maxage=0, must-revalidate" always;
try_files $uri /index.html =404;
}
location ~* .(txt|webmanifest|css|js|mjs|map|svg|jpg|jpeg|png|ico|ttf|woff|woff2|wav)$ {
try_files $uri $uri/ =404;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
}
}
}

View File

@ -13,7 +13,7 @@
},
"homepage": "https://vikunja.io/",
"funding": "https://opencollective.com/vikunja",
"packageManager": "pnpm@7.26.1",
"packageManager": "pnpm@7.26.2",
"keywords": [
"todo",
"productivity",
@ -34,7 +34,7 @@
"test:e2e-record": "start-server-and-test preview http://127.0.0.1:4173 'cypress run --e2e --browser chrome --record'",
"test:e2e-dev-dev": "start-server-and-test preview:dev http://127.0.0.1:4173 'cypress open --e2e'",
"test:e2e-dev": "start-server-and-test preview http://127.0.0.1:4173 'cypress open --e2e'",
"test:unit": "vitest",
"test:unit": "vitest --dir ./src",
"typecheck": "vue-tsc --noEmit && vue-tsc --noEmit -p tsconfig.vitest.json --composite false",
"browserslist:update": "pnpm dlx browserslist@latest --update-db",
"fonts:update": "pnpm fonts:download && pnpm fonts:subset",
@ -58,7 +58,7 @@
"@types/is-touch-device": "1.0.0",
"@types/lodash.clonedeep": "4.5.7",
"@types/sortablejs": "1.15.0",
"@vueuse/core": "9.11.1",
"@vueuse/core": "9.12.0",
"axios": "1.2.6",
"blurhash": "2.0.4",
"bulma-css-variables": "0.9.33",
@ -121,7 +121,7 @@
"csstype": "3.1.1",
"cypress": "12.4.1",
"esbuild": "0.17.5",
"eslint": "8.32.0",
"eslint": "8.33.0",
"eslint-plugin-vue": "9.9.0",
"happy-dom": "8.1.5",
"histoire": "0.12.4",

View File

@ -36,7 +36,7 @@ specifiers:
'@vue/eslint-config-typescript': 11.0.2
'@vue/test-utils': 2.2.8
'@vue/tsconfig': 0.1.3
'@vueuse/core': 9.11.1
'@vueuse/core': 9.12.0
autoprefixer: 10.4.13
axios: 1.2.6
blurhash: 2.0.4
@ -52,7 +52,7 @@ specifiers:
dompurify: 2.4.3
easymde: 2.18.0
esbuild: 0.17.5
eslint: 8.32.0
eslint: 8.33.0
eslint-plugin-vue: 9.9.0
fast-deep-equal: 3.1.3
flatpickr: 4.6.13
@ -112,7 +112,7 @@ dependencies:
'@types/is-touch-device': 1.0.0
'@types/lodash.clonedeep': 4.5.7
'@types/sortablejs': 1.15.0
'@vueuse/core': 9.11.1_vue@3.2.45
'@vueuse/core': 9.12.0_vue@3.2.45
axios: 1.2.6
blurhash: 2.0.4
bulma-css-variables: 0.9.33
@ -162,11 +162,11 @@ devDependencies:
'@types/marked': 4.0.8
'@types/node': 18.11.18
'@types/postcss-preset-env': 7.7.0
'@typescript-eslint/eslint-plugin': 5.49.0_iu322prlnwsygkcra5kbpy22si
'@typescript-eslint/parser': 5.49.0_7uibuqfxkfaozanbtbziikiqje
'@typescript-eslint/eslint-plugin': 5.49.0_rsaczafy73x3xqauzesvzbsgzy
'@typescript-eslint/parser': 5.49.0_zkdaqh7it7uc4cvz2haft7rc6u
'@vitejs/plugin-legacy': 3.0.2_terser@5.10.0+vite@4.0.4
'@vitejs/plugin-vue': 4.0.0_vite@4.0.4+vue@3.2.45
'@vue/eslint-config-typescript': 11.0.2_5gm7ezxcdo2zu65gboxarjsumy
'@vue/eslint-config-typescript': 11.0.2_w5nyi6nqxtergbgmiezudsbkue
'@vue/test-utils': 2.2.8_o3nmwetfthgqihjljurbyiqgn4
'@vue/tsconfig': 0.1.3_@types+node@18.11.18
autoprefixer: 10.4.13_postcss@8.4.21
@ -175,8 +175,8 @@ devDependencies:
csstype: 3.1.1
cypress: 12.4.1
esbuild: 0.17.5
eslint: 8.32.0
eslint-plugin-vue: 9.9.0_eslint@8.32.0
eslint: 8.33.0
eslint-plugin-vue: 9.9.0_eslint@8.33.0
happy-dom: 8.1.5
histoire: 0.12.4_jtbuayq2ea5xwom4rabzheoelm
netlify-cli: 12.9.1_@types+node@18.11.18
@ -2383,7 +2383,7 @@ packages:
dayjs: ^1.11.5
vue: ^3.2.40
dependencies:
'@vueuse/core': 9.11.1_vue@3.2.45
'@vueuse/core': 9.12.0_vue@3.2.45
dayjs: 1.11.7
vue: 3.2.45
transitivePeerDependencies:
@ -4004,7 +4004,7 @@ packages:
dev: true
optional: true
/@typescript-eslint/eslint-plugin/5.49.0_iu322prlnwsygkcra5kbpy22si:
/@typescript-eslint/eslint-plugin/5.49.0_rsaczafy73x3xqauzesvzbsgzy:
resolution: {integrity: sha512-IhxabIpcf++TBaBa1h7jtOWyon80SXPRLDq0dVz5SLFC/eW6tofkw/O7Ar3lkx5z5U6wzbKDrl2larprp5kk5Q==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
@ -4015,12 +4015,12 @@ packages:
typescript:
optional: true
dependencies:
'@typescript-eslint/parser': 5.49.0_7uibuqfxkfaozanbtbziikiqje
'@typescript-eslint/parser': 5.49.0_zkdaqh7it7uc4cvz2haft7rc6u
'@typescript-eslint/scope-manager': 5.49.0
'@typescript-eslint/type-utils': 5.49.0_7uibuqfxkfaozanbtbziikiqje
'@typescript-eslint/utils': 5.49.0_7uibuqfxkfaozanbtbziikiqje
'@typescript-eslint/type-utils': 5.49.0_zkdaqh7it7uc4cvz2haft7rc6u
'@typescript-eslint/utils': 5.49.0_zkdaqh7it7uc4cvz2haft7rc6u
debug: 4.3.4
eslint: 8.32.0
eslint: 8.33.0
ignore: 5.2.0
natural-compare-lite: 1.4.0
regexpp: 3.2.0
@ -4031,7 +4031,7 @@ packages:
- supports-color
dev: true
/@typescript-eslint/parser/5.49.0_7uibuqfxkfaozanbtbziikiqje:
/@typescript-eslint/parser/5.49.0_zkdaqh7it7uc4cvz2haft7rc6u:
resolution: {integrity: sha512-veDlZN9mUhGqU31Qiv2qEp+XrJj5fgZpJ8PW30sHU+j/8/e5ruAhLaVDAeznS7A7i4ucb/s8IozpDtt9NqCkZg==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
@ -4045,7 +4045,7 @@ packages:
'@typescript-eslint/types': 5.49.0
'@typescript-eslint/typescript-estree': 5.49.0_typescript@4.9.4
debug: 4.3.4
eslint: 8.32.0
eslint: 8.33.0
typescript: 4.9.4
transitivePeerDependencies:
- supports-color
@ -4059,7 +4059,7 @@ packages:
'@typescript-eslint/visitor-keys': 5.49.0
dev: true
/@typescript-eslint/type-utils/5.49.0_7uibuqfxkfaozanbtbziikiqje:
/@typescript-eslint/type-utils/5.49.0_zkdaqh7it7uc4cvz2haft7rc6u:
resolution: {integrity: sha512-eUgLTYq0tR0FGU5g1YHm4rt5H/+V2IPVkP0cBmbhRyEmyGe4XvJ2YJ6sYTmONfjmdMqyMLad7SB8GvblbeESZA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
@ -4070,9 +4070,9 @@ packages:
optional: true
dependencies:
'@typescript-eslint/typescript-estree': 5.49.0_typescript@4.9.4
'@typescript-eslint/utils': 5.49.0_7uibuqfxkfaozanbtbziikiqje
'@typescript-eslint/utils': 5.49.0_zkdaqh7it7uc4cvz2haft7rc6u
debug: 4.3.4
eslint: 8.32.0
eslint: 8.33.0
tsutils: 3.21.0_typescript@4.9.4
typescript: 4.9.4
transitivePeerDependencies:
@ -4126,7 +4126,7 @@ packages:
- supports-color
dev: true
/@typescript-eslint/utils/5.49.0_7uibuqfxkfaozanbtbziikiqje:
/@typescript-eslint/utils/5.49.0_zkdaqh7it7uc4cvz2haft7rc6u:
resolution: {integrity: sha512-cPJue/4Si25FViIb74sHCLtM4nTSBXtLx1d3/QT6mirQ/c65bV8arBEebBJJizfq8W2YyMoPI/WWPFWitmNqnQ==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
@ -4137,9 +4137,9 @@ packages:
'@typescript-eslint/scope-manager': 5.49.0
'@typescript-eslint/types': 5.49.0
'@typescript-eslint/typescript-estree': 5.49.0_typescript@4.9.4
eslint: 8.32.0
eslint: 8.33.0
eslint-scope: 5.1.1
eslint-utils: 3.0.0_eslint@8.32.0
eslint-utils: 3.0.0_eslint@8.33.0
semver: 7.3.8
transitivePeerDependencies:
- supports-color
@ -4334,7 +4334,7 @@ packages:
resolution: {integrity: sha512-JD5fcdIuFxU4fQyXUu3w2KpAJHzTVdN+p4iOX2lMWSHMOoQdMAcpFLZzm9Z/2nmsoZ1a96QEhZ26e50xLBsgOQ==}
dev: false
/@vue/eslint-config-typescript/11.0.2_5gm7ezxcdo2zu65gboxarjsumy:
/@vue/eslint-config-typescript/11.0.2_w5nyi6nqxtergbgmiezudsbkue:
resolution: {integrity: sha512-EiKud1NqlWmSapBFkeSrE994qpKx7/27uCGnhdqzllYDpQZroyX/O6bwjEpeuyKamvLbsGdO6PMR2faIf+zFnw==}
engines: {node: ^14.17.0 || >=16.0.0}
peerDependencies:
@ -4345,12 +4345,12 @@ packages:
typescript:
optional: true
dependencies:
'@typescript-eslint/eslint-plugin': 5.49.0_iu322prlnwsygkcra5kbpy22si
'@typescript-eslint/parser': 5.49.0_7uibuqfxkfaozanbtbziikiqje
eslint: 8.32.0
eslint-plugin-vue: 9.9.0_eslint@8.32.0
'@typescript-eslint/eslint-plugin': 5.49.0_rsaczafy73x3xqauzesvzbsgzy
'@typescript-eslint/parser': 5.49.0_zkdaqh7it7uc4cvz2haft7rc6u
eslint: 8.33.0
eslint-plugin-vue: 9.9.0_eslint@8.33.0
typescript: 4.9.4
vue-eslint-parser: 9.0.3_eslint@8.32.0
vue-eslint-parser: 9.0.3_eslint@8.33.0
transitivePeerDependencies:
- supports-color
dev: true
@ -4416,24 +4416,24 @@ packages:
'@types/node': 18.11.18
dev: true
/@vueuse/core/9.11.1_vue@3.2.45:
resolution: {integrity: sha512-E/cizD1w9ILkq4axYjZrXLkKaBfzloaby2n3NMjUfd6yI/jkfTVgc6iwy/Cw2e++Ld4LphGbO+3MhzizvwUslQ==}
/@vueuse/core/9.12.0_vue@3.2.45:
resolution: {integrity: sha512-h/Di8Bvf6xRcvS/PvUVheiMYYz3U0tH3X25YxONSaAUBa841ayMwxkuzx/DGUMCW/wHWzD8tRy2zYmOC36r4sg==}
dependencies:
'@types/web-bluetooth': 0.0.16
'@vueuse/metadata': 9.11.1
'@vueuse/shared': 9.11.1_vue@3.2.45
'@vueuse/metadata': 9.12.0
'@vueuse/shared': 9.12.0_vue@3.2.45
vue-demi: 0.12.1_vue@3.2.45
transitivePeerDependencies:
- '@vue/composition-api'
- vue
dev: false
/@vueuse/metadata/9.11.1:
resolution: {integrity: sha512-ABjkrG+VXggNhjfGyw5e/sekxTZfXTwjrYXkkWQmQ7Biyv+Gq9UD6IDNfeGvQZEINI0Qzw6nfuO2UFCd3hlrxQ==}
/@vueuse/metadata/9.12.0:
resolution: {integrity: sha512-9oJ9MM9lFLlmvxXUqsR1wLt1uF7EVbP5iYaHJYqk+G2PbMjY6EXvZeTjbdO89HgoF5cI6z49o2zT/jD9SVoNpQ==}
dev: false
/@vueuse/shared/9.11.1_vue@3.2.45:
resolution: {integrity: sha512-UTZYGAjT96hWn4buf4wstZbeheBVNcKPQuej6qpoSkjF1atdaeCD6kqm9uGL2waHfisSgH9mq0qCRiBOk5C/2w==}
/@vueuse/shared/9.12.0_vue@3.2.45:
resolution: {integrity: sha512-TWuJLACQ0BVithVTRbex4Wf1a1VaRuSpVeyEd4vMUWl54PzlE0ciFUshKCXnlLuD0lxIaLK4Ypj3NXYzZh4+SQ==}
dependencies:
vue-demi: 0.12.1_vue@3.2.45
transitivePeerDependencies:
@ -7201,19 +7201,19 @@ packages:
source-map: 0.6.1
dev: true
/eslint-plugin-vue/9.9.0_eslint@8.32.0:
/eslint-plugin-vue/9.9.0_eslint@8.33.0:
resolution: {integrity: sha512-YbubS7eK0J7DCf0U2LxvVP7LMfs6rC6UltihIgval3azO3gyDwEGVgsCMe1TmDiEkl6GdMKfRpaME6QxIYtzDQ==}
engines: {node: ^14.17.0 || >=16.0.0}
peerDependencies:
eslint: ^6.2.0 || ^7.0.0 || ^8.0.0
dependencies:
eslint: 8.32.0
eslint-utils: 3.0.0_eslint@8.32.0
eslint: 8.33.0
eslint-utils: 3.0.0_eslint@8.33.0
natural-compare: 1.4.0
nth-check: 2.0.1
postcss-selector-parser: 6.0.10
semver: 7.3.7
vue-eslint-parser: 9.0.3_eslint@8.32.0
vue-eslint-parser: 9.0.3_eslint@8.33.0
xml-name-validator: 4.0.0
transitivePeerDependencies:
- supports-color
@ -7242,13 +7242,13 @@ packages:
eslint-visitor-keys: 1.3.0
dev: false
/eslint-utils/3.0.0_eslint@8.32.0:
/eslint-utils/3.0.0_eslint@8.33.0:
resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==}
engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0}
peerDependencies:
eslint: '>=5'
dependencies:
eslint: 8.32.0
eslint: 8.33.0
eslint-visitor-keys: 2.1.0
dev: true
@ -7267,8 +7267,8 @@ packages:
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dev: true
/eslint/8.32.0:
resolution: {integrity: sha512-nETVXpnthqKPFyuY2FNjz/bEd6nbosRgKbkgS/y1C7LJop96gYHWpiguLecMHQ2XCPxn77DS0P+68WzG6vkZSQ==}
/eslint/8.33.0:
resolution: {integrity: sha512-WjOpFQgKK8VrCnAtl8We0SUOy/oVZ5NHykyMiagV1M9r8IFpIJX7DduK6n1mpfhlG7T1NLWm2SuD8QB7KFySaA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
hasBin: true
dependencies:
@ -7283,7 +7283,7 @@ packages:
doctrine: 3.0.0
escape-string-regexp: 4.0.0
eslint-scope: 7.1.1
eslint-utils: 3.0.0_eslint@8.32.0
eslint-utils: 3.0.0_eslint@8.33.0
eslint-visitor-keys: 3.3.0
espree: 9.4.0
esquery: 1.4.0
@ -14776,14 +14776,14 @@ packages:
vue: 3.2.45
dev: false
/vue-eslint-parser/9.0.3_eslint@8.32.0:
/vue-eslint-parser/9.0.3_eslint@8.33.0:
resolution: {integrity: sha512-yL+ZDb+9T0ELG4VIFo/2anAOz8SvBdlqEnQnvJ3M7Scq56DvtjY0VY88bByRZB0D4J0u8olBcfrXTVONXsh4og==}
engines: {node: ^14.17.0 || >=16.0.0}
peerDependencies:
eslint: '>=6.0.0'
dependencies:
debug: 4.3.4
eslint: 8.32.0
eslint: 8.33.0
eslint-scope: 7.1.1
eslint-visitor-keys: 3.3.0
espree: 9.4.0

View File

@ -1,28 +0,0 @@
#!/bin/bash
# This shell script sets the api url based on an environment variable and starts nginx in foreground.
VIKUNJA_API_URL="${VIKUNJA_API_URL:-"/api/v1"}"
VIKUNJA_SENTRY_ENABLED="${VIKUNJA_SENTRY_ENABLED:-"false"}"
VIKUNJA_SENTRY_DSN="${VIKUNJA_SENTRY_DSN:-"https://85694a2d757547cbbc90cd4b55c5a18d@o1047380.ingest.sentry.io/6024480"}"
VIKUNJA_HTTP_PORT="${VIKUNJA_HTTP_PORT:-80}"
VIKUNJA_HTTPS_PORT="${VIKUNJA_HTTPS_PORT:-443}"
echo "Using $VIKUNJA_API_URL as default api url"
# Escape the variable to prevent sed from complaining
VIKUNJA_API_URL=$(echo $VIKUNJA_API_URL |sed 's/\//\\\//g')
sed -i "s/http\:\/\/localhost\:3456//g" /usr/share/nginx/html/index.html # replacing in two steps to make sure api urls from releases are properly replaced as well
sed -i "s/'\/api\/v1/'$VIKUNJA_API_URL/g" /usr/share/nginx/html/index.html
sed -i "s/\.SENTRY_ENABLED = false/\.SENTRY_ENABLED = $VIKUNJA_SENTRY_ENABLED/g" /usr/share/nginx/html/index.html
sed -i "s|\.SENTRY_DSN = '.*'|\.SENTRY_DSN = '$VIKUNJA_SENTRY_DSN'|g" /usr/share/nginx/html/index.html
sed -i "s/listen 80/listen $VIKUNJA_HTTP_PORT/g" /etc/nginx/nginx.conf
sed -i "s/listen 443/listen $VIKUNJA_HTTPS_PORT/g" /etc/nginx/nginx.conf
# Set the uid and gid of the nginx run user
usermod --non-unique --uid ${PUID} nginx
groupmod --non-unique --gid ${PGID} nginx
nginx -g "daemon off;"

View File

@ -233,7 +233,7 @@ export const getDateFromTextIn = (text: string, now: Date = new Date()) => {
}
const getDateFromWeekday = (text: string): dateFoundResult => {
const matcher = / (next )?(monday|mon|tuesday|tue|wednesday|wed|thursday|thu|friday|fri|saturday|sat|sunday|sun)($| )/g
const matcher = /(^| )(next )?(monday|mon|tuesday|tue|wednesday|wed|thursday|thu|friday|fri|saturday|sat|sunday|sun)($| )/g
const results: string[] | null = matcher.exec(text.toLowerCase()) // The i modifier does not seem to work.
if (results === null) {
return {
@ -246,7 +246,7 @@ const getDateFromWeekday = (text: string): dateFoundResult => {
const currentDay: number = date.getDay()
let day = 0
switch (results[2]) {
switch (results[3]) {
case 'mon':
case 'monday':
day = 1

View File

@ -124,6 +124,18 @@ describe('Parse Task Text', () => {
expect(result?.date?.getMonth()).toBe(nextMonday.getMonth())
expect(result?.date?.getDate()).toBe(nextMonday.getDate())
})
it('should recognize next monday on the beginning of the sentence', () => {
const result = parseTaskText('next monday Lorem Ipsum')
const untilNextMonday = calculateDayInterval('nextMonday')
expect(result.text).toBe('Lorem Ipsum')
const nextMonday = new Date()
nextMonday.setDate(nextMonday.getDate() + untilNextMonday)
expect(result?.date?.getFullYear()).toBe(nextMonday.getFullYear())
expect(result?.date?.getMonth()).toBe(nextMonday.getMonth())
expect(result?.date?.getDate()).toBe(nextMonday.getDate())
})
it('should recognize next monday and ignore casing', () => {
const result = parseTaskText('Lorem Ipsum nExt Monday')
@ -216,46 +228,7 @@ describe('Parse Task Text', () => {
expect(result?.date?.getDate()).toBe(date.getDate())
})
const cases = {
'monday': 1,
'Monday': 1,
'mon': 1,
'Mon': 1,
'tuesday': 2,
'Tuesday': 2,
'tue': 2,
'Tue': 2,
'wednesday': 3,
'Wednesday': 3,
'wed': 3,
'Wed': 3,
'thursday': 4,
'Thursday': 4,
'thu': 4,
'Thu': 4,
'friday': 5,
'Friday': 5,
'fri': 5,
'Fri': 5,
'saturday': 6,
'Saturday': 6,
'sat': 6,
'Sat': 6,
'sunday': 7,
'Sunday': 7,
'sun': 7,
'Sun': 7,
} as Record<string, number>
for (const c in cases) {
it(`should recognize ${c} as weekday`, () => {
const result = parseTaskText(`Lorem Ipsum ${c}`)
expect(result.text).toBe('Lorem Ipsum')
const nextDate = new Date()
nextDate.setDate(nextDate.getDate() + ((cases[c] + 7 - nextDate.getDay()) % 7))
expect(`${result?.date?.getFullYear()}-${result?.date?.getMonth()}-${result?.date?.getDate()}`).toBe(`${nextDate.getFullYear()}-${nextDate.getMonth()}-${nextDate.getDate()}`)
})
}
it('should recognize weekdays with time', () => {
const result = parseTaskText('Lorem Ipsum thu at 14:00')
@ -369,20 +342,34 @@ describe('Parse Task Text', () => {
describe('Parse weekdays', () => {
const days = {
'mon': 1,
'monday': 1,
'tue': 2,
'Monday': 1,
'mon': 1,
'Mon': 1,
'tuesday': 2,
'wed': 3,
'Tuesday': 2,
'tue': 2,
'Tue': 2,
'wednesday': 3,
'thu': 4,
'Wednesday': 3,
'wed': 3,
'Wed': 3,
'thursday': 4,
'fri': 5,
'Thursday': 4,
'thu': 4,
'Thu': 4,
'friday': 5,
'sat': 6,
'Friday': 5,
'fri': 5,
'Fri': 5,
'saturday': 6,
'sun': 7,
'Saturday': 6,
'sat': 6,
'Sat': 6,
'sunday': 7,
'Sunday': 7,
'sun': 7,
'Sun': 7,
} as Record<string, number>
const prefix = [
@ -399,6 +386,18 @@ describe('Parse Task Text', () => {
const distance = (days[d] + 7 - next.getDay()) % 7
next.setDate(next.getDate() + distance)
expect(result.text).toBe('Lorem Ipsum')
expect(result?.date?.getFullYear()).toBe(next.getFullYear())
expect(result?.date?.getMonth()).toBe(next.getMonth())
expect(result?.date?.getDate()).toBe(next.getDate())
})
it(`should recognize ${p}${d} at the beginning of the text`, () => {
const result = parseTaskText(`${p}${d} Lorem Ipsum`)
const next = new Date()
const distance = (days[d] + 7 - next.getDay()) % 7
next.setDate(next.getDate() + distance)
expect(result.text).toBe('Lorem Ipsum')
expect(result?.date?.getFullYear()).toBe(next.getFullYear())
expect(result?.date?.getMonth()).toBe(next.getMonth())