From 5198e6b06ecca3dc9cb4c8897128d43108cc4a91 Mon Sep 17 00:00:00 2001 From: Dominik Pschenitschni Date: Thu, 11 Aug 2022 23:42:45 +0200 Subject: [PATCH] feat: add zod schemas --- package.json | 5 +- src/modelSchema/LabelTask.ts | 12 ++++ src/modelSchema/abstract.ts | 9 +++ src/modelSchema/attachment.ts | 19 +++++++ src/modelSchema/avatar.ts | 11 ++++ src/modelSchema/backgroundImage.ts | 18 ++++++ src/modelSchema/bucket.ts | 23 ++++++++ src/modelSchema/caldavToken.ts | 14 +++++ src/modelSchema/common/RelationKind.ts | 21 +++++++ src/modelSchema/common/date.ts | 11 ++++ src/modelSchema/common/filter.ts | 79 ++++++++++++++++++++++++++ src/modelSchema/common/hexColor.ts | 11 ++++ src/modelSchema/common/id.ts | 6 ++ src/modelSchema/common/repeats.ts | 32 ++++++++--- src/modelSchema/common/textField.ts | 3 + src/modelSchema/emailUpdate.ts | 9 +++ src/modelSchema/file.ts | 13 +++++ src/modelSchema/label.ts | 32 +++++++++++ src/modelSchema/linkShare.ts | 22 +++++++ src/modelSchema/list.ts | 31 ++++++++++ src/modelSchema/listDuplication.ts | 13 +++++ src/modelSchema/namespace.ts | 24 ++++++++ src/modelSchema/notification.ts | 51 +++++++++++++++++ src/modelSchema/passwordReset.ts | 12 ++++ src/modelSchema/passwordUpdate.ts | 13 +++++ src/modelSchema/savedFilter.ts | 20 +++++++ src/modelSchema/subscription.ts | 18 ++++++ src/modelSchema/task.ts | 79 ++++++++++++++++++++++++++ src/modelSchema/taskAssignee.ts | 14 +++++ src/modelSchema/taskComment.ts | 18 ++++++ src/modelSchema/taskRelation.ts | 21 +++++++ src/modelSchema/team.ts | 24 ++++++++ src/modelSchema/teamList.ts | 11 ++++ src/modelSchema/teamMember.ts | 12 ++++ src/modelSchema/teamNamespace.ts | 11 ++++ src/modelSchema/teamShareBase.ts | 18 ++++++ src/modelSchema/token.ts | 7 +++ src/modelSchema/totp.ts | 11 ++++ src/modelSchema/user.ts | 20 +++++++ src/modelSchema/userList.ts | 11 ++++ src/modelSchema/userNamespace.ts | 11 ++++ src/modelSchema/userSettings.ts | 28 +++++++++ src/modelSchema/userShareBase.ts | 17 ++++++ src/modelTypes/IAvatar.ts | 3 +- yarn.lock | 15 +++++ 45 files changed, 853 insertions(+), 10 deletions(-) create mode 100644 src/modelSchema/LabelTask.ts create mode 100644 src/modelSchema/abstract.ts create mode 100644 src/modelSchema/attachment.ts create mode 100644 src/modelSchema/avatar.ts create mode 100644 src/modelSchema/backgroundImage.ts create mode 100644 src/modelSchema/bucket.ts create mode 100644 src/modelSchema/caldavToken.ts create mode 100644 src/modelSchema/common/RelationKind.ts create mode 100644 src/modelSchema/common/date.ts create mode 100644 src/modelSchema/common/filter.ts create mode 100644 src/modelSchema/common/hexColor.ts create mode 100644 src/modelSchema/common/id.ts create mode 100644 src/modelSchema/common/textField.ts create mode 100644 src/modelSchema/emailUpdate.ts create mode 100644 src/modelSchema/file.ts create mode 100644 src/modelSchema/label.ts create mode 100644 src/modelSchema/linkShare.ts create mode 100644 src/modelSchema/list.ts create mode 100644 src/modelSchema/listDuplication.ts create mode 100644 src/modelSchema/namespace.ts create mode 100644 src/modelSchema/notification.ts create mode 100644 src/modelSchema/passwordReset.ts create mode 100644 src/modelSchema/passwordUpdate.ts create mode 100644 src/modelSchema/savedFilter.ts create mode 100644 src/modelSchema/subscription.ts create mode 100644 src/modelSchema/task.ts create mode 100644 src/modelSchema/taskAssignee.ts create mode 100644 src/modelSchema/taskComment.ts create mode 100644 src/modelSchema/taskRelation.ts create mode 100644 src/modelSchema/team.ts create mode 100644 src/modelSchema/teamList.ts create mode 100644 src/modelSchema/teamMember.ts create mode 100644 src/modelSchema/teamNamespace.ts create mode 100644 src/modelSchema/teamShareBase.ts create mode 100644 src/modelSchema/token.ts create mode 100644 src/modelSchema/totp.ts create mode 100644 src/modelSchema/user.ts create mode 100644 src/modelSchema/userList.ts create mode 100644 src/modelSchema/userNamespace.ts create mode 100644 src/modelSchema/userSettings.ts create mode 100644 src/modelSchema/userShareBase.ts diff --git a/package.json b/package.json index e5d7bc227..49fadf588 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "snake-case": "3.0.4", "ufo": "0.8.5", "v-tooltip": "4.0.0-beta.17", + "validator": "^13.7.0", "vue": "3.2.38", "vue-advanced-cropper": "2.8.3", "vue-drag-resize": "2.0.3", @@ -65,6 +66,7 @@ "@fortawesome/free-solid-svg-icons": "6.2.0", "@fortawesome/vue-fontawesome": "3.0.1", "@types/flexsearch": "0.7.3", + "@types/validator": "^13.7.5", "@typescript-eslint/eslint-plugin": "5.36.2", "@typescript-eslint/parser": "5.36.2", "@vitejs/plugin-legacy": "2.1.0", @@ -95,7 +97,8 @@ "vitest": "0.23.1", "vue-tsc": "0.40.9", "wait-on": "6.0.1", - "workbox-cli": "6.5.4" + "workbox-cli": "6.5.4", + "zod": "3.18.0" }, "postcss": { "plugins": { diff --git a/src/modelSchema/LabelTask.ts b/src/modelSchema/LabelTask.ts new file mode 100644 index 000000000..79926397c --- /dev/null +++ b/src/modelSchema/LabelTask.ts @@ -0,0 +1,12 @@ +import type { TypeOf } from 'zod' + +import { AbstractSchema } from './abstract' +import { IdSchema } from './common/id' + +export const LabelTaskSchema = AbstractSchema.extend({ + id: IdSchema.nullable(), + taskId: IdSchema.nullable(), + labelId: IdSchema.nullable(), +}) + +export type LabelTask = TypeOf \ No newline at end of file diff --git a/src/modelSchema/abstract.ts b/src/modelSchema/abstract.ts new file mode 100644 index 000000000..505e61993 --- /dev/null +++ b/src/modelSchema/abstract.ts @@ -0,0 +1,9 @@ +import { object, nativeEnum, type TypeOf } from 'zod' + +import {RIGHTS} from '@/constants/rights' + +export const AbstractSchema = object({ + maxRight: nativeEnum(RIGHTS).nullable(), +}) + +export type IAbstract = TypeOf \ No newline at end of file diff --git a/src/modelSchema/attachment.ts b/src/modelSchema/attachment.ts new file mode 100644 index 000000000..6eec1526a --- /dev/null +++ b/src/modelSchema/attachment.ts @@ -0,0 +1,19 @@ +import type { TypeOf } from 'zod' + +import { DateSchema } from './common/date' +import { IdSchema } from './common/id' + +import { AbstractSchema } from './abstract' +import { UserSchema } from './user' +import { FileSchema } from './file' + +export const AttachmentSchema = AbstractSchema.extend({ + id: IdSchema.default(0), + + taskId: IdSchema.default(0), // iTaskSchema.shape.id + createdBy: UserSchema, + file: FileSchema, + created: DateSchema.nullable(), +}) + +export type IAttachment = TypeOf \ No newline at end of file diff --git a/src/modelSchema/avatar.ts b/src/modelSchema/avatar.ts new file mode 100644 index 000000000..71aaa3a21 --- /dev/null +++ b/src/modelSchema/avatar.ts @@ -0,0 +1,11 @@ +import { z, type TypeOf, string, object } from 'zod' + +export const AVATAR_PROVIDER = ['default', 'initials', 'gravatar', 'marble', 'upload'] as const +export const AvatarProviderSchema = z.enum(AVATAR_PROVIDER) +export type IAvatarProvider = TypeOf + +export const AvatarSchema = object({ + // FIXME: shouldn't the default be 'default'? + avatarProvider: string().or(AvatarProviderSchema).default(''), +}) +export type IAvatar = TypeOf \ No newline at end of file diff --git a/src/modelSchema/backgroundImage.ts b/src/modelSchema/backgroundImage.ts new file mode 100644 index 000000000..c35ddd241 --- /dev/null +++ b/src/modelSchema/backgroundImage.ts @@ -0,0 +1,18 @@ +import { type TypeOf, object, record, string, unknown } from 'zod' + +import { IdSchema } from './common/id' + +export const BackgroundImageSchema = object({ + id: IdSchema.default(0), + url: string().url().default(''), + thumb: string().default(''), + // FIXME: not sure if this needs to defined, since it seems provider specific + // { + // author: string(), + // authorName: string(), + // } + info: record(unknown()).default({}), + blurHash: string().default(''), +}) + +export type BackgroundImage = TypeOf \ No newline at end of file diff --git a/src/modelSchema/bucket.ts b/src/modelSchema/bucket.ts new file mode 100644 index 000000000..a32632e49 --- /dev/null +++ b/src/modelSchema/bucket.ts @@ -0,0 +1,23 @@ +import { type TypeOf, number, array, boolean } from 'zod' +import { UserSchema } from './user' +import { TaskSchema } from './task' +import { DateSchema } from './common/date' +import { IdSchema } from './common/id' +import { AbstractSchema } from './abstract' +import { TextFieldSchema } from './common/textField' + +export const BucketSchema = AbstractSchema.extend({ + id: IdSchema.default(0), + title: TextFieldSchema, + listId: IdSchema.default(0), + limit: number().default(0), + tasks: array(TaskSchema).default([]), + isDoneBucket: boolean().default(false), + position: number().default(0), + + createdBy: UserSchema.nullable(), + created: DateSchema.nullable(), + updated: DateSchema.nullable(), +}) + +export type IBucket = TypeOf \ No newline at end of file diff --git a/src/modelSchema/caldavToken.ts b/src/modelSchema/caldavToken.ts new file mode 100644 index 000000000..cca98e5d7 --- /dev/null +++ b/src/modelSchema/caldavToken.ts @@ -0,0 +1,14 @@ + +import type { TypeOf } from 'zod' + +import { DateSchema } from './common/date' +import { IdSchema } from './common/id' + +import { AbstractSchema } from './abstract' + +export const CaldavTokenSchema = AbstractSchema.extend({ + id: IdSchema, + created: DateSchema, +}) + +export type CaldavToken = TypeOf diff --git a/src/modelSchema/common/RelationKind.ts b/src/modelSchema/common/RelationKind.ts new file mode 100644 index 000000000..8e05ad440 --- /dev/null +++ b/src/modelSchema/common/RelationKind.ts @@ -0,0 +1,21 @@ +import { nativeEnum, type TypeOf } from 'zod' + +export const RELATION_KIND = { + 'SUBTASK': 'subtask', + 'PARENTTASK': 'parenttask', + 'RELATED': 'related', + 'DUPLICATES': 'duplicates', + 'BLOCKING': 'blocking', + 'BLOCKED': 'blocked', + 'PROCEDES': 'precedes', + 'FOLLOWS': 'follows', + 'COPIEDFROM': 'copiedfrom', + 'COPIEDTO': 'copiedto', +} as const + +export const RELATION_KINDS = [...Object.values(RELATION_KIND)] as const + +export const RelationKindSchema = nativeEnum(RELATION_KIND) + +export type IRelationKind = TypeOf + \ No newline at end of file diff --git a/src/modelSchema/common/date.ts b/src/modelSchema/common/date.ts new file mode 100644 index 000000000..4238450b6 --- /dev/null +++ b/src/modelSchema/common/date.ts @@ -0,0 +1,11 @@ +import { preprocess, date } from 'zod' + +export const DateSchema = preprocess((arg) => { + if ( + // FIXME: Add comment why we check for `0001` + typeof arg == 'string' && !arg.startsWith('0001') || + arg instanceof Date + ) { + return new Date(arg) + } +}, date()) \ No newline at end of file diff --git a/src/modelSchema/common/filter.ts b/src/modelSchema/common/filter.ts new file mode 100644 index 000000000..f1fb5a0a6 --- /dev/null +++ b/src/modelSchema/common/filter.ts @@ -0,0 +1,79 @@ +import { z, nativeEnum, type TypeOf, array, boolean, object, number } from 'zod' + +export enum SORT_BY { + ID = 'id', + DONE = 'done', + TITLE = 'title', + PRIORITY = 'priority', + DONE_AT = 'done_at', + DUE_DATE = 'due_date', + START_DATE = 'start_date', + END_DATE = 'end_date', + PERCENT_DONE = 'percent_done', + CREATED = 'created', + UPDATED = 'updated', + POSITION = 'position', + KANBAN_POSITION = 'kanban_position', + } + +export enum ORDER_BY { + ASC = 'asc', + DESC = 'desc', + NONE = 'none', +} + +export enum FILTER_BY { + DONE = 'done', + DUE_DATE = 'due_date', + START_DATE = 'start_date', + END_DATE = 'end_date', + NAMESPACE = 'namespace', + ASSIGNEES = 'assignees', + LIST_ID = 'list_id', + BUCKET_ID = 'bucket_id', + PRIORITY = 'priority', + PERCENT_DONE = 'percent_done', + LABELS = 'labels', + UNDEFINED = 'undefined', // FIXME: Why do we have a value that is undefined as string? +} + +export enum FILTER_COMPARATOR { + EQUALS = 'equals', + LESS = 'less', + GREATER = 'greater', + GREATER_EQUALS = 'greater_equals', + LESS_EQUALS = 'less_equals', + IN = 'in', +} + +export enum FILTER_CONCAT { + AND = 'and', + OR = 'or', + IN = 'in', +} + +const TASKS_PER_BUCKET = 25 + +export const FilterSchema = object({ + sortBy: array(nativeEnum(SORT_BY)).default([SORT_BY.DONE, SORT_BY.ID]), // FIXME: create from taskSchema, + // fixme default order seem so also be `desc` + // see line from ListTable: + // if (typeof order === 'undefined' || order === 'none') { + orderBy: array(nativeEnum(ORDER_BY)).default([ORDER_BY.ASC, ORDER_BY.DESC]), + // FIXME: create from taskSchema + filterBy: array(nativeEnum(FILTER_BY)).default([FILTER_BY.DONE]), + // FIXME: create from taskSchema + // FIXME: might need to preprocess values, e.g. date. + // see line from 'filters.vue': + // params.filter_value = params.filter_value.map(v => v instanceof Date ? v.toISOString() : v) + filterValue: array(z.enum(['false'])).default(['false']), + // FIXME: is `in` value correct? + // found in `quick-actions.vue`: + // params.filter_comparator.push('in') + filterComparator: array(nativeEnum(FILTER_COMPARATOR)).default([FILTER_COMPARATOR.EQUALS]), + filterConcat: z.nativeEnum(FILTER_CONCAT).default(FILTER_CONCAT.AND), + filterIncludeNulls: boolean().default(true), + perPage: number().default(TASKS_PER_BUCKET), // FIXME: is perPage is just available for the bucket endpoint? +}) + +export type IFilter = TypeOf \ No newline at end of file diff --git a/src/modelSchema/common/hexColor.ts b/src/modelSchema/common/hexColor.ts new file mode 100644 index 000000000..c4133f4cb --- /dev/null +++ b/src/modelSchema/common/hexColor.ts @@ -0,0 +1,11 @@ +import { string } from 'zod' + +import isHexColor from 'validator/lib/isHexColor' + +export const HexColorSchema = string().transform( + (value) => { + if (!value || value.startsWith('#')) { + return value + } + return '#' + value + }).refine(value => isHexColor(value)) \ No newline at end of file diff --git a/src/modelSchema/common/id.ts b/src/modelSchema/common/id.ts new file mode 100644 index 000000000..2ba5780f7 --- /dev/null +++ b/src/modelSchema/common/id.ts @@ -0,0 +1,6 @@ +import { number, preprocess } from 'zod' + +export const IdSchema = preprocess( + (value: unknown) => Number(value), + number().positive().int(), +) \ No newline at end of file diff --git a/src/modelSchema/common/repeats.ts b/src/modelSchema/common/repeats.ts index 175b0c727..ec3c83432 100644 --- a/src/modelSchema/common/repeats.ts +++ b/src/modelSchema/common/repeats.ts @@ -1,5 +1,19 @@ -import { REPEAT_TYPES, type IRepeatAfter } from '@/types/IRepeatAfter' -import { nativeEnum, number, object, preprocess } from 'zod' +import { nativeEnum, number, object, preprocess, type TypeOf } from 'zod' + +export enum REPEAT_TYPES { + HOURS = 'hours', + DAYS = 'days', + WEEKS = 'weeks', + MONTHS = 'months', + YEARS = 'years', +} + +export type IRepeatType = typeof REPEAT_TYPES[keyof typeof REPEAT_TYPES] + +export interface IRepeatAfter { + type: IRepeatType, + amount: number, +} export const RepeatsSchema = preprocess( (repeats: unknown) => { @@ -12,7 +26,7 @@ export const RepeatsSchema = preprocess( const repeatAfterHours = (repeats / 60) / 60 const repeatAfter : IRepeatAfter = { - type: 'hours', + type: REPEAT_TYPES.HOURS, amount: repeatAfterHours, } @@ -20,16 +34,16 @@ export const RepeatsSchema = preprocess( if (repeatAfterHours % 24 === 0) { const repeatAfterDays = repeatAfterHours / 24 if (repeatAfterDays % 7 === 0) { - repeatAfter.type = 'weeks' + repeatAfter.type = REPEAT_TYPES.WEEKS repeatAfter.amount = repeatAfterDays / 7 } else if (repeatAfterDays % 30 === 0) { - repeatAfter.type = 'months' + repeatAfter.type = REPEAT_TYPES.MONTHS repeatAfter.amount = repeatAfterDays / 30 } else if (repeatAfterDays % 365 === 0) { - repeatAfter.type = 'years' + repeatAfter.type = REPEAT_TYPES.YEARS repeatAfter.amount = repeatAfterDays / 365 } else { - repeatAfter.type = 'days' + repeatAfter.type = REPEAT_TYPES.DAYS repeatAfter.amount = repeatAfterDays } } @@ -40,4 +54,6 @@ export const RepeatsSchema = preprocess( type: nativeEnum(REPEAT_TYPES), amount: number().int(), }), -) \ No newline at end of file +) + +export type RepeatAfter = TypeOf \ No newline at end of file diff --git a/src/modelSchema/common/textField.ts b/src/modelSchema/common/textField.ts new file mode 100644 index 000000000..bda96f493 --- /dev/null +++ b/src/modelSchema/common/textField.ts @@ -0,0 +1,3 @@ +import {string} from 'zod' + +export const TextFieldSchema = string().transform((value) => value.trim()).default('') \ No newline at end of file diff --git a/src/modelSchema/emailUpdate.ts b/src/modelSchema/emailUpdate.ts new file mode 100644 index 000000000..10c36aa8c --- /dev/null +++ b/src/modelSchema/emailUpdate.ts @@ -0,0 +1,9 @@ +import { type TypeOf, string } from 'zod' +import { AbstractSchema } from './abstract' + +export const EmailUpdateSchema = AbstractSchema.extend({ + newEmail: string().email().default(''), + password: string().default(''), +}) + +export type EmailUpdate = TypeOf diff --git a/src/modelSchema/file.ts b/src/modelSchema/file.ts new file mode 100644 index 000000000..515539909 --- /dev/null +++ b/src/modelSchema/file.ts @@ -0,0 +1,13 @@ +import { type TypeOf, object, number, string } from 'zod' +import { DateSchema } from './common/date' +import { IdSchema } from './common/id' + +export const FileSchema = object({ + id: IdSchema.default(0), + mime: string().default(''), + name: string().default(''), + size: number().default(0), + created: DateSchema.nullable(), +}) + +export type File = TypeOf diff --git a/src/modelSchema/label.ts b/src/modelSchema/label.ts new file mode 100644 index 000000000..2f49f09c8 --- /dev/null +++ b/src/modelSchema/label.ts @@ -0,0 +1,32 @@ +import { type TypeOf, string } from 'zod' + +import { UserSchema } from './user' + +import { DateSchema } from './common/date' +import { IdSchema } from './common/id' +import { HexColorSchema } from './common/hexColor' +import { AbstractSchema } from './abstract' + +import { colorIsDark } from '@/helpers/color/colorIsDark' + +const DEFAULT_LABEL_BACKGROUND_COLOR = 'e8e8e8' + +export const LabelSchema = AbstractSchema.extend({ + id: IdSchema.default(0), + title: string().default(''), + hexColor: HexColorSchema.default(DEFAULT_LABEL_BACKGROUND_COLOR), + textColor: string(), // implicit + description: string().default(''), + createdBy: UserSchema, // FIXME: default: current user? + listId: IdSchema.default(0), + + created: DateSchema.nullable(), + updated: DateSchema.nullable(), +}).transform((obj) => { + // FIXME: remove textColor location => should be defined in UI + obj.textColor = colorIsDark(obj.hexColor) ? '#4a4a4a' : '#ffffff' + return obj +}, +) + +export type ILabel = TypeOf diff --git a/src/modelSchema/linkShare.ts b/src/modelSchema/linkShare.ts new file mode 100644 index 000000000..9890be06f --- /dev/null +++ b/src/modelSchema/linkShare.ts @@ -0,0 +1,22 @@ +import { type TypeOf, number, string, nativeEnum } from 'zod' +import { UserSchema } from './user' +import { IdSchema } from './common/id' +import { RIGHTS } from '@/constants/rights' +import { DateSchema } from './common/date' +import { AbstractSchema } from './abstract' + +export const LinkShareSchema = AbstractSchema.extend({ + id: IdSchema.default(0), + hash: string().default(''), + right: nativeEnum(RIGHTS).default(RIGHTS.READ), + sharedBy: UserSchema, + sharingType: number().default(0), // FIXME: use correct numbers + listId: IdSchema.default(0), + name: string().default(''), + password: string().default(''), + + created: DateSchema.nullable(), + updated: DateSchema.nullable(), +}) + +export type LinkShare = TypeOf diff --git a/src/modelSchema/list.ts b/src/modelSchema/list.ts new file mode 100644 index 000000000..07f25170a --- /dev/null +++ b/src/modelSchema/list.ts @@ -0,0 +1,31 @@ +import { type TypeOf, boolean, number, string, array, any } from 'zod' +import { AbstractSchema } from './abstract' + +import { DateSchema } from './common/date' +import { IdSchema } from './common/id' + +import { SubscriptionSchema } from './subscription' +import { TaskSchema } from './task' +import { UserSchema } from './user' + +export const ListSchema = AbstractSchema.extend({ + id: IdSchema.default(0), + hash: string().default(''), + description: string().default(''), + owner: UserSchema, + tasks: array(TaskSchema), + namespaceId: IdSchema.default(0), // INamespace['id'], + isArchived: boolean().default(false), + hexColor: string().default(''), + identifier: string().default(''), + backgroundInformation: any().nullable().default(null), // FIXME: what is this for? + isFavorite: boolean().default(false), + subscription: SubscriptionSchema.nullable(), + position: number().default(0), + backgroundBlurHash: string().default(''), + + created: DateSchema.nullable(), + updated: DateSchema.nullable(), +}) + +export type List = TypeOf diff --git a/src/modelSchema/listDuplication.ts b/src/modelSchema/listDuplication.ts new file mode 100644 index 000000000..5d9fb3300 --- /dev/null +++ b/src/modelSchema/listDuplication.ts @@ -0,0 +1,13 @@ +import type { TypeOf } from 'zod' + +import { AbstractSchema } from './abstract' +import { IdSchema } from './common/id' +import { ListSchema } from './list' + +export const ListDuplicationSchema = AbstractSchema.extend({ + listId: IdSchema.default(0), + namespaceId: IdSchema.default(0), // INamespace['id'], + list: ListSchema, +}) + +export type ListDuplication = TypeOf \ No newline at end of file diff --git a/src/modelSchema/namespace.ts b/src/modelSchema/namespace.ts new file mode 100644 index 000000000..de6faa69f --- /dev/null +++ b/src/modelSchema/namespace.ts @@ -0,0 +1,24 @@ +import { type TypeOf, boolean, string, array } from 'zod' + +import { AbstractSchema } from './abstract' +import { ListSchema } from './list' +import { UserSchema } from './user' +import { SubscriptionSchema } from './subscription' +import { IdSchema } from './common/id' +import { HexColorSchema } from './common/hexColor' + +export const NamespaceSchema = AbstractSchema.extend({ + id: IdSchema.default(0), + title: string().default(''), + description: string().default(''), + owner: UserSchema, + lists: array(ListSchema), + isArchived: boolean().default(false), + hexColor: HexColorSchema.default(''), + subscription: SubscriptionSchema.nullable(), + + created: IdSchema.nullable(), + updated: IdSchema.nullable(), +}) + +export type Namespace = TypeOf diff --git a/src/modelSchema/notification.ts b/src/modelSchema/notification.ts new file mode 100644 index 000000000..921dc94a9 --- /dev/null +++ b/src/modelSchema/notification.ts @@ -0,0 +1,51 @@ +import { type TypeOf, union, boolean, object, string } from 'zod' +import { AbstractSchema } from './abstract' +import { DateSchema } from './common/date' +import { IdSchema } from './common/id' +import { TaskSchema } from './task' +import { TaskCommentSchema } from './taskComment' +import { TeamSchema } from './team' +import { UserSchema } from './user' + +const NotificationTypeSchema = object({ + doer: UserSchema, +}) + +const NotificationTypeTask = NotificationTypeSchema.extend({ + task: TaskSchema, + comment: TaskCommentSchema, +}) + +const NotificationTypeAssigned = NotificationTypeSchema.extend({ + task: TaskSchema, + assignee: UserSchema, +}) + +const NotificationTypeDeleted = NotificationTypeSchema.extend({ + task: TaskSchema, +}) + +const NotificationTypeCreated = NotificationTypeSchema.extend({ + task: TaskSchema, +}) + +const NotificationTypeMemberAdded = NotificationTypeSchema.extend({ + member: UserSchema, + team: TeamSchema, +}) + +export const NotificationSchema = AbstractSchema.extend({ + id: IdSchema.default(0), + name: string().default(''), + notification: union([ + NotificationTypeTask, + NotificationTypeAssigned, + NotificationTypeDeleted, + NotificationTypeCreated, + NotificationTypeMemberAdded, + ]), + read: boolean().default(false), + readAt: DateSchema.nullable(), +}) + +export type Notification = TypeOf diff --git a/src/modelSchema/passwordReset.ts b/src/modelSchema/passwordReset.ts new file mode 100644 index 000000000..7d4e33db5 --- /dev/null +++ b/src/modelSchema/passwordReset.ts @@ -0,0 +1,12 @@ +import { type TypeOf, string } from 'zod' +import { AbstractSchema } from './abstract' + + +// FIXME: is it correct that this extends the Abstract Schema? +export const PasswordResetSchema = AbstractSchema.extend({ + token: string().default(''), + newPassword: string().default(''), + email: string().email().default(''), +}) + +export type PasswordReset = TypeOf diff --git a/src/modelSchema/passwordUpdate.ts b/src/modelSchema/passwordUpdate.ts new file mode 100644 index 000000000..b613ac3f6 --- /dev/null +++ b/src/modelSchema/passwordUpdate.ts @@ -0,0 +1,13 @@ +import { type TypeOf, string } from 'zod' +import { AbstractSchema } from './abstract' + +// FIXME: is it correct that this extends the Abstract Schema? +export const PasswordUpdateSchema = AbstractSchema.extend({ + newPassword: string().default(''), + oldPassword: string().default(''), +}).refine((data) => data.newPassword === data.oldPassword, { + message: 'Passwords don\'t match', + path: ['confirm'], // path of error +}) + +export type PasswordUpdate = TypeOf diff --git a/src/modelSchema/savedFilter.ts b/src/modelSchema/savedFilter.ts new file mode 100644 index 000000000..a17704186 --- /dev/null +++ b/src/modelSchema/savedFilter.ts @@ -0,0 +1,20 @@ +import { type TypeOf, string } from 'zod' +import { AbstractSchema } from './abstract' +import { DateSchema } from './common/date' +import { FilterSchema } from './common/filter' +import { IdSchema } from './common/id' +import { UserSchema } from './user' + +// FIXME: is it correct that this extends the Abstract Schema? +export const SavedFilterSchema = AbstractSchema.extend({ + id: IdSchema.default(0), + title: string().default(''), + description: string().default(''), + filters: FilterSchema, + + owner: UserSchema, + created: DateSchema.nullable(), + updated: DateSchema.nullable(), +}) + +export type SavedFilter = TypeOf diff --git a/src/modelSchema/subscription.ts b/src/modelSchema/subscription.ts new file mode 100644 index 000000000..7ed68be07 --- /dev/null +++ b/src/modelSchema/subscription.ts @@ -0,0 +1,18 @@ +import { type TypeOf, string } from 'zod' + +import { DateSchema } from './common/date' +import { IdSchema } from './common/id' + +import { AbstractSchema } from './abstract' +import { UserSchema } from './user' + +export const SubscriptionSchema = AbstractSchema.extend({ + id: IdSchema.default(0), + entity: string().default(''), // FIXME: correct type? + entityId: IdSchema.default(0), // FIXME: correct type? + user: UserSchema, + + created: DateSchema.nullable(), +}) + +export type Subscription = TypeOf diff --git a/src/modelSchema/task.ts b/src/modelSchema/task.ts new file mode 100644 index 000000000..79527940c --- /dev/null +++ b/src/modelSchema/task.ts @@ -0,0 +1,79 @@ +import { type ZodType, type TypeOf,nativeEnum, boolean, number, string, array, record, unknown, lazy } from 'zod' +import { AttachmentSchema } from './attachment' +import { LabelSchema } from './label' +import { UserSchema } from './user' +import { SubscriptionSchema } from './subscription' + +import { PRIORITIES } from '@/constants/priorities' + +import { RepeatsSchema } from './common/repeats' +import { DateSchema } from './common/date' +import { IdSchema } from './common/id' +import { HexColorSchema } from './common/hexColor' +import { AbstractSchema } from './abstract' +import { TextFieldSchema } from './common/textField' +import { TASK_REPEAT_MODES } from '@/types/IRepeatMode' +import { RelationKindSchema } from './common/RelationKind' + +const LabelsSchema = array(LabelSchema) + .transform((labels) => labels.sort((f, s) => f.title > s.title ? 1 : -1)) // FIXME: use + .default([]) + +export type ILabels = TypeOf + +const RelatedTasksSchema = record(RelationKindSchema, record(string(), unknown())) +export type IRelatedTasksSchema = TypeOf + +const RelatedTasksLazySchema : ZodType = lazy(() => + record(RelationKindSchema, TaskSchema), +) +export type IRelatedTasksLazySchema = TypeOf + +export const TaskSchema = AbstractSchema.extend({ + id: IdSchema.default(0), + title: TextFieldSchema, + description: TextFieldSchema, + done: boolean().default(false), + doneAt: DateSchema.nullable().default(null), + priority: nativeEnum(PRIORITIES).default(PRIORITIES.UNSET), + labels: LabelsSchema, + assignees: array(UserSchema).default([]), + + dueDate: DateSchema.nullable(), // FIXME: default value is `0`. Shouldn't this be `null`? + startDate: DateSchema.nullable(), // FIXME: default value is `0`. Shouldn't this be `null`? + endDate: DateSchema.nullable(), // FIXME: default value is `0`. Shouldn't this be `null`? + repeatAfter: RepeatsSchema, // FIXME: default value is `0`. Shouldn't this be `null`? + repeatFromCurrentDate: boolean().default(false), + repeatMode: nativeEnum(TASK_REPEAT_MODES).default(TASK_REPEAT_MODES.REPEAT_MODE_DEFAULT), + + // TODO: schedule notifications + // FIXME: triggered notificaitons not supported anymore / remove feature? + reminderDates: array(DateSchema).default([]), + parentTaskId: IdSchema.default(0), // shouldn't this have `null` as default? + hexColor: HexColorSchema.default(''), + percentDone: number().default(0), + relatedTasks: RelatedTasksSchema.default({}), + attachments: array(AttachmentSchema).default([]), + identifier: string().default(''), + index: number().default(0), + isFavorite: boolean().default(false), + subscription: SubscriptionSchema.nullable().default(null), + + position: number().default(0), + kanbanPosition: number().default(0), + + createdBy: UserSchema, + created: DateSchema.nullable(), + updated: DateSchema.nullable(), + + listId: IdSchema.default(0), //IList['id'], // Meta, only used when creating a new task + bucketId: IdSchema.default(0), // IBucket['id'], +}).transform((obj) => { + if (obj.identifier === `-${obj.index}`) { + obj.identifier = '' + } + return obj +}) + + +export type Task = TypeOf diff --git a/src/modelSchema/taskAssignee.ts b/src/modelSchema/taskAssignee.ts new file mode 100644 index 000000000..69b4f036f --- /dev/null +++ b/src/modelSchema/taskAssignee.ts @@ -0,0 +1,14 @@ +import type { TypeOf } from 'zod' + +import { DateSchema } from './common/date' +import { IdSchema } from './common/id' + +import { AbstractSchema } from './abstract' + +export const TaskAssigneeSchema = AbstractSchema.extend({ + created: DateSchema.nullable(), + userId: IdSchema.default(0), // IUser['id'] + taskId: IdSchema.default(0), // ITask['id'] +}) + +export type TaskAssignee = TypeOf diff --git a/src/modelSchema/taskComment.ts b/src/modelSchema/taskComment.ts new file mode 100644 index 000000000..9e1d6e9e8 --- /dev/null +++ b/src/modelSchema/taskComment.ts @@ -0,0 +1,18 @@ +import { type TypeOf, string } from 'zod' +import { AbstractSchema } from './abstract' +import { DateSchema } from './common/date' +import { IdSchema } from './common/id' +import { UserSchema } from './user' + + +export const TaskCommentSchema = AbstractSchema.extend({ + id: IdSchema.default(0), + taskId: IdSchema.default(0), + comment: string().default(''), + author: UserSchema, + + created: DateSchema.nullable(), + updated: DateSchema.nullable(), +}) + +export type TaskComment = TypeOf diff --git a/src/modelSchema/taskRelation.ts b/src/modelSchema/taskRelation.ts new file mode 100644 index 000000000..35a863c35 --- /dev/null +++ b/src/modelSchema/taskRelation.ts @@ -0,0 +1,21 @@ +import { RELATION_KIND } from '@/types/IRelationKind' +import { type TypeOf, nativeEnum } from 'zod' +import { AbstractSchema } from './abstract' +import { DateSchema } from './common/date' +import { IdSchema } from './common/id' +import { UserSchema } from './user' + + +export const TaskRelationSchema = AbstractSchema.extend({ + id: IdSchema.default(0), + otherTaskId: IdSchema.default(0), + taskId: IdSchema.default(0), + relationKind: nativeEnum(RELATION_KIND).nullable().default(null), // FIXME: default value was empty string? + + createdBy: UserSchema, + // FIXME: shouldn't the empty value of dates be `new Date()` + // Because e.g. : `new Date(null)` => Thu Jan 01 1970 01:00:00 GMT+0100 (Central European Standard Time) + created: DateSchema.nullable().default(null), +}) + +export type ITaskRelation = TypeOf diff --git a/src/modelSchema/team.ts b/src/modelSchema/team.ts new file mode 100644 index 000000000..6119c4dc9 --- /dev/null +++ b/src/modelSchema/team.ts @@ -0,0 +1,24 @@ +import { type TypeOf, array, nativeEnum, string } from 'zod' + +import { RIGHTS } from '@/constants/rights' + +import { DateSchema } from './common/date' +import { IdSchema } from './common/id' + +import { AbstractSchema } from './abstract' +import { UserSchema } from './user' +import { TeamMemberSchema } from './teamMember' + +export const TeamSchema = AbstractSchema.extend({ + id: IdSchema.default(0), + name: string().default(''), + description: string().default(''), + members: array(TeamMemberSchema), + right: nativeEnum(RIGHTS).default(RIGHTS.READ), + + createdBy: UserSchema, // FIXME: default was {}, + created: DateSchema.nullable(), + updated: DateSchema.nullable(), +}) + +export type Team = TypeOf \ No newline at end of file diff --git a/src/modelSchema/teamList.ts b/src/modelSchema/teamList.ts new file mode 100644 index 000000000..02a1e853c --- /dev/null +++ b/src/modelSchema/teamList.ts @@ -0,0 +1,11 @@ +import type { TypeOf } from 'zod' + +import { IdSchema } from './common/id' + +import { TeamShareBaseSchema } from './teamShareBase' + +export const TeamListSchema = TeamShareBaseSchema.extend({ + listId: IdSchema.default(0), // IList['id'] +}) + +export type TeamList = TypeOf diff --git a/src/modelSchema/teamMember.ts b/src/modelSchema/teamMember.ts new file mode 100644 index 000000000..39f895354 --- /dev/null +++ b/src/modelSchema/teamMember.ts @@ -0,0 +1,12 @@ +import { type TypeOf, boolean } from 'zod' + +import { IdSchema } from './common/id' + +import { UserSchema } from './user' + +export const TeamMemberSchema = UserSchema.extend({ + admin: boolean().default(false), + teamId: IdSchema.default(0), // IList['id'] +}) + +export type TeamMember = TypeOf \ No newline at end of file diff --git a/src/modelSchema/teamNamespace.ts b/src/modelSchema/teamNamespace.ts new file mode 100644 index 000000000..5254f60a6 --- /dev/null +++ b/src/modelSchema/teamNamespace.ts @@ -0,0 +1,11 @@ +import type { TypeOf } from 'zod' + +import { IdSchema } from './common/id' + +import { TeamShareBaseSchema } from './teamShareBase' + +export const TeamNamespaceSchema = TeamShareBaseSchema.extend({ + namespaceId: IdSchema.default(0), // INamespace['id'] +}) + +export type ITeamNamespace = TypeOf diff --git a/src/modelSchema/teamShareBase.ts b/src/modelSchema/teamShareBase.ts new file mode 100644 index 000000000..6b429a764 --- /dev/null +++ b/src/modelSchema/teamShareBase.ts @@ -0,0 +1,18 @@ +import { type TypeOf, nativeEnum } from 'zod' + +import { AbstractSchema } from './abstract' + +import { DateSchema } from './common/date' +import { IdSchema } from './common/id' + +import { RIGHTS } from '@/constants/rights' + +export const TeamShareBaseSchema = AbstractSchema.extend({ + teamId: IdSchema.default(0), // ITeam['id'] + right: nativeEnum(RIGHTS).default(RIGHTS.READ), + + created: DateSchema.nullable(), + updated: DateSchema.nullable(), +}) + +export type ITeamShareBase = TypeOf diff --git a/src/modelSchema/token.ts b/src/modelSchema/token.ts new file mode 100644 index 000000000..3a7fd99ea --- /dev/null +++ b/src/modelSchema/token.ts @@ -0,0 +1,7 @@ +import { object, string, type TypeOf } from 'zod' + +export const TokenSchema = object({ + token: string(), +}) + +export type IToken = TypeOf \ No newline at end of file diff --git a/src/modelSchema/totp.ts b/src/modelSchema/totp.ts new file mode 100644 index 000000000..8c51664d8 --- /dev/null +++ b/src/modelSchema/totp.ts @@ -0,0 +1,11 @@ +import { type TypeOf, string, boolean } from 'zod' + +import { AbstractSchema } from './abstract' + +export const TotpSchema = AbstractSchema.extend({ + secret: string().default(''), + enabled: boolean().default(false), + url: string().url().default(''), +}) + +export type Totp = TypeOf diff --git a/src/modelSchema/user.ts b/src/modelSchema/user.ts new file mode 100644 index 000000000..eb68eee21 --- /dev/null +++ b/src/modelSchema/user.ts @@ -0,0 +1,20 @@ +import { type TypeOf, string } from 'zod' + +import { IdSchema } from './common/id' +import { DateSchema } from './common/date' + +import { AbstractSchema } from './abstract' +import { UserSettingsSchema } from './userSettings' + +export const UserSchema = AbstractSchema.extend({ + id: IdSchema.default(0), + email: string().email().default(''), + username: string().default(''), + name: string().default(''), + settings: UserSettingsSchema.nullable(), + + created: DateSchema.nullable(), + updated: DateSchema.nullable(), +}) + +export type User = TypeOf diff --git a/src/modelSchema/userList.ts b/src/modelSchema/userList.ts new file mode 100644 index 000000000..817302bea --- /dev/null +++ b/src/modelSchema/userList.ts @@ -0,0 +1,11 @@ +import type { TypeOf } from 'zod' + +import { IdSchema } from './common/id' + +import { UserShareBaseSchema } from './userShareBase' + +export const UserListSchema = UserShareBaseSchema.extend({ + listId: IdSchema.default(0), // IList['id'] +}) + +export type IUserList = TypeOf diff --git a/src/modelSchema/userNamespace.ts b/src/modelSchema/userNamespace.ts new file mode 100644 index 000000000..7aa2e6c80 --- /dev/null +++ b/src/modelSchema/userNamespace.ts @@ -0,0 +1,11 @@ +import type { TypeOf } from 'zod' + +import { IdSchema } from './common/id' + +import { UserShareBaseSchema } from './userShareBase' + +export const UserNamespaceSchema = UserShareBaseSchema.extend({ + namespaceId: IdSchema.default(0), // INamespace['id'] +}) + +export type IUserNamespace = TypeOf diff --git a/src/modelSchema/userSettings.ts b/src/modelSchema/userSettings.ts new file mode 100644 index 000000000..2ec48b1cf --- /dev/null +++ b/src/modelSchema/userSettings.ts @@ -0,0 +1,28 @@ +import { type TypeOf, boolean, string, undefined, nativeEnum } from 'zod' + +import { IdSchema } from './common/id' + +import { AbstractSchema } from './abstract' + +const WEEKDAYS = { + MONDAY: 0, + TUESDAY: 1, + WEDNESDAY: 2, + THURSDAY: 3, + FRIDAY: 4, + SATURDAY: 5, + SUNDAY: 6, +} as const + +export const UserSettingsSchema = AbstractSchema.extend({ + name: string().default(''), + emailRemindersEnabled: boolean().default(true), + discoverableByName: boolean().default(false), + discoverableByEmail: boolean().default(false), + overdueTasksRemindersEnabled: boolean().default(true), + defaultListId: IdSchema.or(undefined()), // iListSchema['id'] // FIXME: shouldn't this be `null`? + weekStart: nativeEnum(WEEKDAYS).default(WEEKDAYS.MONDAY), + timezone: string().default(''), +}) + +export type IUserSettings = TypeOf diff --git a/src/modelSchema/userShareBase.ts b/src/modelSchema/userShareBase.ts new file mode 100644 index 000000000..f4b69138a --- /dev/null +++ b/src/modelSchema/userShareBase.ts @@ -0,0 +1,17 @@ +import { type TypeOf, nativeEnum } from 'zod' + +import { DateSchema } from './common/date' +import { IdSchema } from './common/id' + +import { RIGHTS } from '@/constants/rights' +import { AbstractSchema } from './abstract' + +export const UserShareBaseSchema = AbstractSchema.extend({ + userId: IdSchema, // FIXME: default of model is `''` + right: nativeEnum(RIGHTS).default(RIGHTS.READ), + + created: DateSchema.nullable(), + updated: DateSchema.nullable(), +}) + +export type TeamMember = TypeOf diff --git a/src/modelTypes/IAvatar.ts b/src/modelTypes/IAvatar.ts index ab681996a..ab2f5d79f 100644 --- a/src/modelTypes/IAvatar.ts +++ b/src/modelTypes/IAvatar.ts @@ -1,6 +1,7 @@ import type {IAbstract} from './IAbstract' -export type AvatarProvider = 'default' | 'initials' | 'gravatar' | 'marble' | 'upload' +export const AVATAR_PROVIDER = ['default', 'initials', 'gravatar', 'marble', 'upload'] as const +export type AvatarProvider = typeof AVATAR_PROVIDER[number] export interface IAvatar extends IAbstract { avatarProvider: AvatarProvider diff --git a/yarn.lock b/yarn.lock index 2a0b7240d..1e6d389a3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2391,6 +2391,11 @@ resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.2.tgz#fc25ad9943bcac11cceb8168db4f275e0e72e756" integrity sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg== +"@types/validator@^13.7.5": + version "13.7.5" + resolved "https://registry.yarnpkg.com/@types/validator/-/validator-13.7.5.tgz#96ad8c50d441c8c86a639efccc4d8d9d5d9cd5ea" + integrity sha512-9rQHeAqz6Jw3gDhttkmWetoriW5FPbxylv/6h6mXtaj2NKRcOvOmvfcswVdLVpbuy10NrO486K3lCoLgoIhiIA== + "@types/web-bluetooth@^0.0.15": version "0.0.15" resolved "https://registry.yarnpkg.com/@types/web-bluetooth/-/web-bluetooth-0.0.15.tgz#d60330046a6ed8a13b4a53df3813c44942ebdf72" @@ -12412,6 +12417,11 @@ validate-npm-package-name@^4.0.0: dependencies: builtins "^5.0.0" +validator@^13.7.0: + version "13.7.0" + resolved "https://registry.yarnpkg.com/validator/-/validator-13.7.0.tgz#4f9658ba13ba8f3d82ee881d3516489ea85c0857" + integrity sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw== + vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" @@ -13217,3 +13227,8 @@ zip-stream@^4.1.0: archiver-utils "^2.1.0" compress-commons "^4.1.0" readable-stream "^3.6.0" + +zod@3.18.0: + version "3.18.0" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.18.0.tgz#2eed58b3cafb8d9a67aa2fee69279702f584f3bc" + integrity sha512-gwTm8RfUCe8l9rDwN5r2A17DkAa8Ez4Yl4yXqc5VqeGaXaJahzYYXbTwvhroZi0SNBqTwh/bKm2N0mpCzuw4bA==