feat: use time constants

This commit is contained in:
Dominik Pschenitschni 2022-10-19 15:18:34 +02:00
parent 78402f4e2e
commit 89f6964d00
Signed by: dpschen
GPG Key ID: B257AC0149F43A77
9 changed files with 96 additions and 77 deletions

View File

@ -65,6 +65,7 @@ import {
} from '@infectoone/vue-ganttastic' } from '@infectoone/vue-ganttastic'
import Loading from '@/components/misc/loading.vue' import Loading from '@/components/misc/loading.vue'
import {MILLISECONDS_A_DAY} from '@/constants/date'
export interface GanttChartProps { export interface GanttChartProps {
isLoading: boolean, isLoading: boolean,
@ -96,7 +97,7 @@ const dateToDate = computed(() => new Date(new Date(filters.value.dateTo).setHou
const DAY_WIDTH_PIXELS = 30 const DAY_WIDTH_PIXELS = 30
const ganttChartWidth = computed(() => { const ganttChartWidth = computed(() => {
const dateDiff = Math.floor((dateToDate.value.valueOf() - dateFromDate.value.valueOf()) / (1000 * 60 * 60 * 24)) const dateDiff = Math.floor((dateToDate.value.valueOf() - dateFromDate.value.valueOf()) / MILLISECONDS_A_DAY)
return dateDiff * DAY_WIDTH_PIXELS return dateDiff * DAY_WIDTH_PIXELS
}) })

View File

@ -3,6 +3,9 @@ import {useRouter} from 'vue-router'
import {useEventListener} from '@vueuse/core' import {useEventListener} from '@vueuse/core'
import {useAuthStore} from '@/stores/auth' import {useAuthStore} from '@/stores/auth'
import {MILLISECONDS_A_HOUR, SECONDS_A_HOUR} from '@/constants/date'
const SECONDS_TOKEN_VALID = 60 * SECONDS_A_HOUR
export function useRenewTokenOnFocus() { export function useRenewTokenOnFocus() {
const router = useRouter() const router = useRouter()
@ -21,7 +24,7 @@ export function useRenewTokenOnFocus() {
return return
} }
const expiresIn = (userInfo.value !== null ? userInfo.value.exp : 0) - +new Date() / 1000 const expiresIn = (userInfo.value !== null ? userInfo.value.exp : 0) - new Date().valueOf() / MILLISECONDS_A_HOUR
// If the token expiry is negative, it is already expired and we have no choice but to redirect // If the token expiry is negative, it is already expired and we have no choice but to redirect
// the user to the login page // the user to the login page
@ -32,7 +35,7 @@ export function useRenewTokenOnFocus() {
} }
// Check if the token is valid for less than 60 hours and renew if thats the case // Check if the token is valid for less than 60 hours and renew if thats the case
if (expiresIn < 60 * 3600) { if (expiresIn < SECONDS_TOKEN_VALID) {
authStore.renewToken() authStore.renewToken()
console.debug('renewed token') console.debug('renewed token')
} }

View File

@ -1 +1,14 @@
export const DATEFNS_DATE_FORMAT_KEBAB = 'yyyy-LL-dd' export const DATEFNS_DATE_FORMAT_KEBAB = 'yyyy-LL-dd'
export const SECONDS_A_MINUTE = 60
export const SECONDS_A_HOUR = SECONDS_A_MINUTE * 60
export const SECONDS_A_DAY = SECONDS_A_HOUR * 24
export const SECONDS_A_WEEK = SECONDS_A_DAY * 7
export const SECONDS_A_MONTH = SECONDS_A_DAY * 30
export const SECONDS_A_YEAR = SECONDS_A_DAY * 365
export const MILLISECONDS_A_SECOND = 1000
export const MILLISECONDS_A_MINUTE = SECONDS_A_MINUTE * MILLISECONDS_A_SECOND
export const MILLISECONDS_A_HOUR = SECONDS_A_HOUR * MILLISECONDS_A_SECOND
export const MILLISECONDS_A_DAY = SECONDS_A_DAY * MILLISECONDS_A_SECOND
export const MILLISECONDS_A_WEEK = SECONDS_A_WEEK * MILLISECONDS_A_SECOND

View File

@ -1,3 +1,5 @@
import {MILLISECONDS_A_WEEK} from "@/constants/date";
export function getNextWeekDate(): Date { export function getNextWeekDate(): Date {
return new Date((new Date()).getTime() + 7 * 24 * 60 * 60 * 1000) return new Date((new Date()).getTime() + MILLISECONDS_A_WEEK)
} }

View File

@ -1,6 +1,28 @@
import {SECONDS_A_HOUR} from '@/constants/date'
import { REPEAT_TYPES, type IRepeatAfter } from '@/types/IRepeatAfter' import { REPEAT_TYPES, type IRepeatAfter } from '@/types/IRepeatAfter'
import { nativeEnum, number, object, preprocess } from 'zod' import { nativeEnum, number, object, preprocess } from 'zod'
/**
* Parses `repeatAfterSeconds` into a usable js object.
*/
export function parseRepeatAfter(repeatAfterSeconds: number): IRepeatAfter {
let repeatAfter: IRepeatAfter = {type: 'hours', amount: repeatAfterSeconds / SECONDS_A_HOUR}
// if its dividable by 24, its something with days, otherwise hours
if (repeatAfterSeconds % SECONDS_A_DAY === 0) {
if (repeatAfterSeconds % SECONDS_A_WEEK === 0) {
repeatAfter = {type: 'weeks', amount: repeatAfterSeconds / SECONDS_A_WEEK}
} else if (repeatAfterSeconds % SECONDS_A_MONTH === 0) {
repeatAfter = {type:'months', amount: repeatAfterSeconds / SECONDS_A_MONTH}
} else if (repeatAfterSeconds % SECONDS_A_YEAR === 0) {
repeatAfter = {type: 'years', amount: repeatAfterSeconds / SECONDS_A_YEAR}
} else {
repeatAfter = {type: 'days', amount: repeatAfterSeconds / SECONDS_A_DAY}
}
}
return repeatAfter
}
export const RepeatsSchema = preprocess( export const RepeatsSchema = preprocess(
(repeats: unknown) => { (repeats: unknown) => {
// Parses the "repeat after x seconds" from the task into a usable js object inside the task. // Parses the "repeat after x seconds" from the task into a usable js object inside the task.
@ -9,32 +31,7 @@ export const RepeatsSchema = preprocess(
return repeats return repeats
} }
const repeatAfterHours = (repeats / 60) / 60 return parseRepeatAfter(repeats)
const repeatAfter : IRepeatAfter = {
type: 'hours',
amount: repeatAfterHours,
}
// if its dividable by 24, its something with days, otherwise hours
if (repeatAfterHours % 24 === 0) {
const repeatAfterDays = repeatAfterHours / 24
if (repeatAfterDays % 7 === 0) {
repeatAfter.type = 'weeks'
repeatAfter.amount = repeatAfterDays / 7
} else if (repeatAfterDays % 30 === 0) {
repeatAfter.type = 'months'
repeatAfter.amount = repeatAfterDays / 30
} else if (repeatAfterDays % 365 === 0) {
repeatAfter.type = 'years'
repeatAfter.amount = repeatAfterDays / 365
} else {
repeatAfter.type = 'days'
repeatAfter.amount = repeatAfterDays
}
}
return repeatAfter
}, },
object({ object({
type: nativeEnum(REPEAT_TYPES), type: nativeEnum(REPEAT_TYPES),

View File

@ -1,5 +1,5 @@
import {PRIORITIES, type Priority} from '@/constants/priorities'
import { PRIORITIES, type Priority } from '@/constants/priorities' import {SECONDS_A_DAY, SECONDS_A_HOUR, SECONDS_A_MONTH, SECONDS_A_WEEK, SECONDS_A_YEAR} from '@/constants/date'
import type {ITask} from '@/modelTypes/ITask' import type {ITask} from '@/modelTypes/ITask'
import type {ILabel} from '@/modelTypes/ILabel' import type {ILabel} from '@/modelTypes/ILabel'
@ -10,10 +10,10 @@ import type {ISubscription} from '@/modelTypes/ISubscription'
import type {IBucket} from '@/modelTypes/IBucket' import type {IBucket} from '@/modelTypes/IBucket'
import type {IRepeatAfter} from '@/types/IRepeatAfter' import type {IRepeatAfter} from '@/types/IRepeatAfter'
import type {IRelationKind} from '@/types/IRelationKind'
import {TASK_REPEAT_MODES, type IRepeatMode} from '@/types/IRepeatMode' import {TASK_REPEAT_MODES, type IRepeatMode} from '@/types/IRepeatMode'
import {parseDateOrNull} from '@/helpers/parseDateOrNull' import {parseDateOrNull} from '@/helpers/parseDateOrNull'
import type { IRelationKind } from '@/types/IRelationKind'
import AbstractModel from './abstractModel' import AbstractModel from './abstractModel'
import LabelModel from './label' import LabelModel from './label'
@ -36,6 +36,27 @@ export function getHexColor(hexColor: string): string {
return hexColor return hexColor
} }
/**
* Parses `repeatAfterSeconds` into a usable js object.
*/
export function parseRepeatAfter(repeatAfterSeconds: number): IRepeatAfter {
let repeatAfter: IRepeatAfter = {type: 'hours', amount: repeatAfterSeconds / SECONDS_A_HOUR}
// if its dividable by 24, its something with days, otherwise hours
if (repeatAfterSeconds % SECONDS_A_DAY === 0) {
if (repeatAfterSeconds % SECONDS_A_WEEK === 0) {
repeatAfter = {type: 'weeks', amount: repeatAfterSeconds / SECONDS_A_WEEK}
} else if (repeatAfterSeconds % SECONDS_A_MONTH === 0) {
repeatAfter = {type:'months', amount: repeatAfterSeconds / SECONDS_A_MONTH}
} else if (repeatAfterSeconds % SECONDS_A_YEAR === 0) {
repeatAfter = {type: 'years', amount: repeatAfterSeconds / SECONDS_A_YEAR}
} else {
repeatAfter = {type: 'days', amount: repeatAfterSeconds / SECONDS_A_DAY}
}
}
return repeatAfter
}
export default class TaskModel extends AbstractModel<ITask> implements ITask { export default class TaskModel extends AbstractModel<ITask> implements ITask {
id = 0 id = 0
title = '' title = ''
@ -95,7 +116,7 @@ export default class TaskModel extends AbstractModel<ITask> implements ITask {
this.endDate = parseDateOrNull(this.endDate) this.endDate = parseDateOrNull(this.endDate)
// Parse the repeat after into something usable // Parse the repeat after into something usable
this.parseRepeatAfter() this.repeatAfter = parseRepeatAfter(this.repeatAfter as number)
this.reminderDates = this.reminderDates.map(d => new Date(d)) this.reminderDates = this.reminderDates.map(d => new Date(d))
@ -151,33 +172,6 @@ export default class TaskModel extends AbstractModel<ITask> implements ITask {
// Helper functions // Helper functions
/////////////// ///////////////
/**
* Parses the "repeat after x seconds" from the task into a usable js object inside the task.
* This function should only be called from the constructor.
*/
parseRepeatAfter() {
const repeatAfterHours = (this.repeatAfter as number / 60) / 60
this.repeatAfter = {type: 'hours', amount: repeatAfterHours}
// if its dividable by 24, its something with days, otherwise hours
if (repeatAfterHours % 24 === 0) {
const repeatAfterDays = repeatAfterHours / 24
if (repeatAfterDays % 7 === 0) {
this.repeatAfter.type = 'weeks'
this.repeatAfter.amount = repeatAfterDays / 7
} else if (repeatAfterDays % 30 === 0) {
this.repeatAfter.type = 'months'
this.repeatAfter.amount = repeatAfterDays / 30
} else if (repeatAfterDays % 365 === 0) {
this.repeatAfter.type = 'years'
this.repeatAfter.amount = repeatAfterDays / 365
} else {
this.repeatAfter.type = 'days'
this.repeatAfter.amount = repeatAfterDays
}
}
}
async cancelScheduledNotifications() { async cancelScheduledNotifications() {
if (!SUPPORTS_TRIGGERED_NOTIFICATION) { if (!SUPPORTS_TRIGGERED_NOTIFICATION) {
return return

View File

@ -1,9 +1,10 @@
import {beforeEach, afterEach, describe, it, expect, vi} from 'vitest' import {beforeEach, afterEach, describe, it, expect, vi} from 'vitest'
import {parseTaskText, PrefixMode} from './parseTaskText' import {parseTaskText, PrefixMode} from './parseTaskText'
import {getDateFromText, getDateFromTextIn, parseDate} from '../helpers/time/parseDate' import {getDateFromText, parseDate} from '../helpers/time/parseDate'
import {calculateDayInterval} from '../helpers/time/calculateDayInterval' import {calculateDayInterval} from '../helpers/time/calculateDayInterval'
import {PRIORITIES} from '@/constants/priorities' import {PRIORITIES} from '@/constants/priorities'
import { MILLISECONDS_A_DAY } from '@/constants/date'
describe('Parse Task Text', () => { describe('Parse Task Text', () => {
beforeEach(() => { beforeEach(() => {
@ -296,7 +297,7 @@ describe('Parse Task Text', () => {
expect(result.date.getMonth()).toBe(expectedDate.getMonth()) expect(result.date.getMonth()).toBe(expectedDate.getMonth())
}) })
it('should recognize dates of the month in the future', () => { it('should recognize dates of the month in the future', () => {
const nextDay = new Date(+new Date() + 60 * 60 * 24 * 1000) const nextDay = new Date(+new Date() + MILLISECONDS_A_DAY)
const result = parseTaskText(`Lorem Ipsum ${nextDay.getDate()}nd`) const result = parseTaskText(`Lorem Ipsum ${nextDay.getDate()}nd`)
expect(result.text).toBe('Lorem Ipsum') expect(result.text).toBe('Lorem Ipsum')

View File

@ -6,6 +6,7 @@ import LabelService from './label'
import {formatISO} from 'date-fns' import {formatISO} from 'date-fns'
import {colorFromHex} from '@/helpers/color/colorFromHex' import {colorFromHex} from '@/helpers/color/colorFromHex'
import {SECONDS_A_DAY, SECONDS_A_HOUR, SECONDS_A_WEEK, SECONDS_A_MONTH, SECONDS_A_YEAR} from '@/constants/date'
const parseDate = date => { const parseDate = date => {
if (date) { if (date) {
@ -73,19 +74,19 @@ export default class TaskService extends AbstractService<ITask> {
if (model.repeatAfter !== null && (model.repeatAfter.amount !== null || model.repeatAfter.amount !== 0)) { if (model.repeatAfter !== null && (model.repeatAfter.amount !== null || model.repeatAfter.amount !== 0)) {
switch (model.repeatAfter.type) { switch (model.repeatAfter.type) {
case 'hours': case 'hours':
repeatAfterSeconds = model.repeatAfter.amount * 60 * 60 repeatAfterSeconds = model.repeatAfter.amount * SECONDS_A_HOUR
break break
case 'days': case 'days':
repeatAfterSeconds = model.repeatAfter.amount * 60 * 60 * 24 repeatAfterSeconds = model.repeatAfter.amount * SECONDS_A_DAY
break break
case 'weeks': case 'weeks':
repeatAfterSeconds = model.repeatAfter.amount * 60 * 60 * 24 * 7 repeatAfterSeconds = model.repeatAfter.amount * SECONDS_A_WEEK
break break
case 'months': case 'months':
repeatAfterSeconds = model.repeatAfter.amount * 60 * 60 * 24 * 30 repeatAfterSeconds = model.repeatAfter.amount * SECONDS_A_MONTH
break break
case 'years': case 'years':
repeatAfterSeconds = model.repeatAfter.amount * 60 * 60 * 24 * 365 repeatAfterSeconds = model.repeatAfter.amount * SECONDS_A_YEAR
break break
} }
} }

View File

@ -14,6 +14,7 @@ import type {IUserSettings} from '@/modelTypes/IUserSettings'
import router from '@/router' import router from '@/router'
import {useConfigStore} from '@/stores/config' import {useConfigStore} from '@/stores/config'
import UserSettingsModel from '@/models/userSettings' import UserSettingsModel from '@/models/userSettings'
import {MILLISECONDS_A_SECOND} from '@/constants/date'
export interface AuthState { export interface AuthState {
authenticated: boolean, authenticated: boolean,
@ -130,8 +131,10 @@ export const useAuthStore = defineStore('auth', {
} }
}, },
// Registers a new user and logs them in. /**
// Not sure if this is the right place to put the logic in, maybe a seperate js component would be better suited. * Registers a new user and logs them in.
* Not sure if this is the right place to put the logic in, maybe a seperate js component would be better suited.
*/
async register(credentials) { async register(credentials) {
const HTTP = HTTPFactory() const HTTP = HTTPFactory()
this.setIsLoading(true) this.setIsLoading(true)
@ -181,14 +184,17 @@ export const useAuthStore = defineStore('auth', {
return response.data return response.data
}, },
// Populates user information from jwt token saved in local storage in store /**
* Populates user information from jwt token saved in local storage in store
*/
checkAuth() { checkAuth() {
const now = new Date()
const inOneMinute = new Date(new Date().setMinutes(now.getMinutes() + 1))
// This function can be called from multiple places at the same time and shortly after one another. // This function can be called from multiple places at the same time and shortly after one another.
// To prevent hitting the api too frequently or race conditions, we check at most once per minute. // To prevent hitting the api too frequently or race conditions, we check at most once per minute.
if ( if (
this.lastUserInfoRefresh !== null && this.lastUserInfoRefresh !== null &&
this.lastUserInfoRefresh > (new Date()).setMinutes((new Date()).getMinutes() + 1) this.lastUserInfoRefresh > inOneMinute
) { ) {
return return
} }
@ -201,7 +207,7 @@ export const useAuthStore = defineStore('auth', {
.replace('-', '+') .replace('-', '+')
.replace('_', '/') .replace('_', '/')
const info = new UserModel(JSON.parse(atob(base64))) const info = new UserModel(JSON.parse(atob(base64)))
const ts = Math.round((new Date()).getTime() / 1000) const ts = Math.round((new Date()).getTime() / MILLISECONDS_A_SECOND)
authenticated = info.exp >= ts authenticated = info.exp >= ts
this.setUser(info) this.setUser(info)
@ -277,9 +283,8 @@ export const useAuthStore = defineStore('auth', {
/** /**
* Try to verify the email * Try to verify the email
* @returns {Promise<boolean>} if the email was successfully confirmed
*/ */
async verifyEmail() { async verifyEmail(): Promise<boolean> {
const emailVerifyToken = localStorage.getItem('emailConfirmToken') const emailVerifyToken = localStorage.getItem('emailConfirmToken')
if (emailVerifyToken) { if (emailVerifyToken) {
const stopLoading = setModuleLoading(this) const stopLoading = setModuleLoading(this)
@ -320,7 +325,9 @@ export const useAuthStore = defineStore('auth', {
} }
}, },
// Renews the api token and saves it to local storage /**
* Renews the api token and saves it to local storage
*/
renewToken() { renewToken() {
// FIXME: Timeout to avoid race conditions when authenticated as a user (=auth token in localStorage) and as a // FIXME: Timeout to avoid race conditions when authenticated as a user (=auth token in localStorage) and as a
// link share in another tab. Without the timeout both the token renew and link share auth are executed at // link share in another tab. Without the timeout both the token renew and link share auth are executed at