From 89f6964d0063c513c66b17b09f4353bff5be04f8 Mon Sep 17 00:00:00 2001 From: Dominik Pschenitschni Date: Wed, 19 Oct 2022 15:18:34 +0200 Subject: [PATCH] feat: use time constants --- src/components/tasks/gantt-chart.vue | 3 +- src/composables/useRenewTokenOnFocus.ts | 7 +++- src/constants/date.ts | 13 ++++++ src/helpers/time/getNextWeekDate.ts | 4 +- src/modelSchema/common/repeats.ts | 49 ++++++++++------------ src/models/task.ts | 56 +++++++++++-------------- src/modules/parseTaskText.test.ts | 5 ++- src/services/task.ts | 11 ++--- src/stores/auth.ts | 25 +++++++---- 9 files changed, 96 insertions(+), 77 deletions(-) diff --git a/src/components/tasks/gantt-chart.vue b/src/components/tasks/gantt-chart.vue index 3ffa62fbb..d39a23c3e 100644 --- a/src/components/tasks/gantt-chart.vue +++ b/src/components/tasks/gantt-chart.vue @@ -65,6 +65,7 @@ import { } from '@infectoone/vue-ganttastic' import Loading from '@/components/misc/loading.vue' +import {MILLISECONDS_A_DAY} from '@/constants/date' export interface GanttChartProps { isLoading: boolean, @@ -96,7 +97,7 @@ const dateToDate = computed(() => new Date(new Date(filters.value.dateTo).setHou const DAY_WIDTH_PIXELS = 30 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 }) diff --git a/src/composables/useRenewTokenOnFocus.ts b/src/composables/useRenewTokenOnFocus.ts index 06305984e..7a669270d 100644 --- a/src/composables/useRenewTokenOnFocus.ts +++ b/src/composables/useRenewTokenOnFocus.ts @@ -3,6 +3,9 @@ import {useRouter} from 'vue-router' import {useEventListener} from '@vueuse/core' 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() { const router = useRouter() @@ -21,7 +24,7 @@ export function useRenewTokenOnFocus() { 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 // 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 - if (expiresIn < 60 * 3600) { + if (expiresIn < SECONDS_TOKEN_VALID) { authStore.renewToken() console.debug('renewed token') } diff --git a/src/constants/date.ts b/src/constants/date.ts index 2896808a8..835f2c4da 100644 --- a/src/constants/date.ts +++ b/src/constants/date.ts @@ -1 +1,14 @@ 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 \ No newline at end of file diff --git a/src/helpers/time/getNextWeekDate.ts b/src/helpers/time/getNextWeekDate.ts index d0bb303fb..5fbbdd99e 100644 --- a/src/helpers/time/getNextWeekDate.ts +++ b/src/helpers/time/getNextWeekDate.ts @@ -1,3 +1,5 @@ +import {MILLISECONDS_A_WEEK} from "@/constants/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) } diff --git a/src/modelSchema/common/repeats.ts b/src/modelSchema/common/repeats.ts index 175b0c727..da89e1f95 100644 --- a/src/modelSchema/common/repeats.ts +++ b/src/modelSchema/common/repeats.ts @@ -1,6 +1,28 @@ +import {SECONDS_A_HOUR} from '@/constants/date' import { REPEAT_TYPES, type IRepeatAfter } from '@/types/IRepeatAfter' 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( (repeats: unknown) => { // 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 } - const repeatAfterHours = (repeats / 60) / 60 - - 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 + return parseRepeatAfter(repeats) }, object({ type: nativeEnum(REPEAT_TYPES), diff --git a/src/models/task.ts b/src/models/task.ts index 5f4e01d70..a133c5e65 100644 --- a/src/models/task.ts +++ b/src/models/task.ts @@ -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 {ILabel} from '@/modelTypes/ILabel' @@ -10,10 +10,10 @@ import type {ISubscription} from '@/modelTypes/ISubscription' import type {IBucket} from '@/modelTypes/IBucket' import type {IRepeatAfter} from '@/types/IRepeatAfter' +import type {IRelationKind} from '@/types/IRelationKind' import {TASK_REPEAT_MODES, type IRepeatMode} from '@/types/IRepeatMode' import {parseDateOrNull} from '@/helpers/parseDateOrNull' -import type { IRelationKind } from '@/types/IRelationKind' import AbstractModel from './abstractModel' import LabelModel from './label' @@ -36,6 +36,27 @@ export function getHexColor(hexColor: string): string { 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 implements ITask { id = 0 title = '' @@ -95,7 +116,7 @@ export default class TaskModel extends AbstractModel implements ITask { this.endDate = parseDateOrNull(this.endDate) // 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)) @@ -151,33 +172,6 @@ export default class TaskModel extends AbstractModel implements ITask { // 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() { if (!SUPPORTS_TRIGGERED_NOTIFICATION) { return diff --git a/src/modules/parseTaskText.test.ts b/src/modules/parseTaskText.test.ts index 83b291a70..728d1c8e3 100644 --- a/src/modules/parseTaskText.test.ts +++ b/src/modules/parseTaskText.test.ts @@ -1,9 +1,10 @@ import {beforeEach, afterEach, describe, it, expect, vi} from 'vitest' 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 {PRIORITIES} from '@/constants/priorities' +import { MILLISECONDS_A_DAY } from '@/constants/date' describe('Parse Task Text', () => { beforeEach(() => { @@ -296,7 +297,7 @@ describe('Parse Task Text', () => { expect(result.date.getMonth()).toBe(expectedDate.getMonth()) }) 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`) expect(result.text).toBe('Lorem Ipsum') diff --git a/src/services/task.ts b/src/services/task.ts index bb41b6853..0eee3227b 100644 --- a/src/services/task.ts +++ b/src/services/task.ts @@ -6,6 +6,7 @@ import LabelService from './label' import {formatISO} from 'date-fns' 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 => { if (date) { @@ -73,19 +74,19 @@ export default class TaskService extends AbstractService { if (model.repeatAfter !== null && (model.repeatAfter.amount !== null || model.repeatAfter.amount !== 0)) { switch (model.repeatAfter.type) { case 'hours': - repeatAfterSeconds = model.repeatAfter.amount * 60 * 60 + repeatAfterSeconds = model.repeatAfter.amount * SECONDS_A_HOUR break case 'days': - repeatAfterSeconds = model.repeatAfter.amount * 60 * 60 * 24 + repeatAfterSeconds = model.repeatAfter.amount * SECONDS_A_DAY break case 'weeks': - repeatAfterSeconds = model.repeatAfter.amount * 60 * 60 * 24 * 7 + repeatAfterSeconds = model.repeatAfter.amount * SECONDS_A_WEEK break case 'months': - repeatAfterSeconds = model.repeatAfter.amount * 60 * 60 * 24 * 30 + repeatAfterSeconds = model.repeatAfter.amount * SECONDS_A_MONTH break case 'years': - repeatAfterSeconds = model.repeatAfter.amount * 60 * 60 * 24 * 365 + repeatAfterSeconds = model.repeatAfter.amount * SECONDS_A_YEAR break } } diff --git a/src/stores/auth.ts b/src/stores/auth.ts index ace2e4d51..4b6107ef4 100644 --- a/src/stores/auth.ts +++ b/src/stores/auth.ts @@ -14,6 +14,7 @@ import type {IUserSettings} from '@/modelTypes/IUserSettings' import router from '@/router' import {useConfigStore} from '@/stores/config' import UserSettingsModel from '@/models/userSettings' +import {MILLISECONDS_A_SECOND} from '@/constants/date' export interface AuthState { 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) { const HTTP = HTTPFactory() this.setIsLoading(true) @@ -181,14 +184,17 @@ export const useAuthStore = defineStore('auth', { 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() { - + 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. // To prevent hitting the api too frequently or race conditions, we check at most once per minute. if ( this.lastUserInfoRefresh !== null && - this.lastUserInfoRefresh > (new Date()).setMinutes((new Date()).getMinutes() + 1) + this.lastUserInfoRefresh > inOneMinute ) { return } @@ -201,7 +207,7 @@ export const useAuthStore = defineStore('auth', { .replace('-', '+') .replace('_', '/') 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 this.setUser(info) @@ -277,9 +283,8 @@ export const useAuthStore = defineStore('auth', { /** * Try to verify the email - * @returns {Promise} if the email was successfully confirmed */ - async verifyEmail() { + async verifyEmail(): Promise { const emailVerifyToken = localStorage.getItem('emailConfirmToken') if (emailVerifyToken) { 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() { // 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