diff --git a/app/scenes/CollectionEdit.tsx b/app/scenes/CollectionEdit.tsx index 47cc6895fb..3ddbdadcb6 100644 --- a/app/scenes/CollectionEdit.tsx +++ b/app/scenes/CollectionEdit.tsx @@ -3,6 +3,7 @@ import { observer } from "mobx-react"; import { useState } from "react"; import * as React from "react"; import { Trans, useTranslation } from "react-i18next"; +import { MAX_TITLE_LENGTH } from "@shared/constants"; import Button from "~/components/Button"; import Flex from "~/components/Flex"; import IconPicker from "~/components/IconPicker"; @@ -93,6 +94,7 @@ const CollectionEdit = ({ collectionId, onSubmit }: Props) => { type="text" label={t("Name")} onChange={handleNameChange} + maxLength={MAX_TITLE_LENGTH} value={name} required autoFocus diff --git a/app/scenes/CollectionNew.tsx b/app/scenes/CollectionNew.tsx index bbe7289e38..ce20cbbf10 100644 --- a/app/scenes/CollectionNew.tsx +++ b/app/scenes/CollectionNew.tsx @@ -3,6 +3,7 @@ import { observable } from "mobx"; import { observer } from "mobx-react"; import * as React from "react"; import { withTranslation, Trans, WithTranslation } from "react-i18next"; +import { MAX_TITLE_LENGTH } from "@shared/constants"; import RootStore from "~/stores/RootStore"; import Collection from "~/models/Collection"; import Button from "~/components/Button"; @@ -127,6 +128,7 @@ class CollectionNew extends React.Component { type="text" label={t("Name")} onChange={this.handleNameChange} + maxLength={MAX_TITLE_LENGTH} value={this.name} required autoFocus diff --git a/server/models/Collection.ts b/server/models/Collection.ts index 595f6d29f1..a8339ef305 100644 --- a/server/models/Collection.ts +++ b/server/models/Collection.ts @@ -20,6 +20,7 @@ import { DataType, } from "sequelize-typescript"; import isUUID from "validator/lib/isUUID"; +import { MAX_TITLE_LENGTH } from "@shared/constants"; import { sortNavigationNodes } from "@shared/utils/collections"; import { SLUG_URL_REGEX } from "@shared/utils/urlHelpers"; import slugify from "@server/utils/slugify"; @@ -33,6 +34,8 @@ import Team from "./Team"; import User from "./User"; import ParanoidModel from "./base/ParanoidModel"; import Fix from "./decorators/Fix"; +import IsHexColor from "./validators/IsHexColor"; +import Length from "./validators/Length"; import NotContainsUrl from "./validators/NotContainsUrl"; // without this indirection, the app crashes on starup @@ -133,18 +136,35 @@ class Collection extends ParanoidModel { urlId: string; @NotContainsUrl + @Length({ + max: MAX_TITLE_LENGTH, + msg: `Collection name must be less than ${MAX_TITLE_LENGTH} characters`, + }) @Column name: string; + @Length({ + max: 1000, + msg: `Collection description must be less than 1000 characters`, + }) @Column description: string; + @Length({ + max: 50, + msg: `Collection icon must be less than 50 characters`, + }) @Column icon: string | null; + @IsHexColor @Column color: string | null; + @Length({ + max: 50, + msg: `Collection index must be less than 50 characters`, + }) @Column index: string | null; diff --git a/server/models/Document.ts b/server/models/Document.ts index a75f15ee68..fddfea5df5 100644 --- a/server/models/Document.ts +++ b/server/models/Document.ts @@ -188,7 +188,6 @@ class Document extends ParanoidModel { urlId: string; @Length({ - min: 0, max: MAX_TITLE_LENGTH, msg: `Document title must be less than ${MAX_TITLE_LENGTH} characters`, }) diff --git a/server/models/User.ts b/server/models/User.ts index 727483200f..3fd7490ef5 100644 --- a/server/models/User.ts +++ b/server/models/User.ts @@ -84,21 +84,17 @@ export enum UserFlag { @Fix class User extends ParanoidModel { @IsEmail - @Length({ min: 0, max: 255, msg: "email must be less than 255 characters" }) + @Length({ max: 255, msg: "User email must be less than 255 characters" }) @Column email: string | null; @NotContainsUrl - @Length({ - min: 0, - max: 255, - msg: "username must be less than 255 characters", - }) + @Length({ max: 255, msg: "User username must be less than 255 characters" }) @Column username: string | null; @NotContainsUrl - @Length({ min: 0, max: 255, msg: "name must be less than 255 characters" }) + @Length({ max: 255, msg: "User name must be less than 255 characters" }) @Column name: string; diff --git a/server/models/WebhookSubscription.ts b/server/models/WebhookSubscription.ts index f212dc49a9..84fb1e9809 100644 --- a/server/models/WebhookSubscription.ts +++ b/server/models/WebhookSubscription.ts @@ -22,13 +22,13 @@ import Length from "./validators/Length"; @Fix class WebhookSubscription extends ParanoidModel { @NotEmpty - @Length({ min: 0, max: 255, msg: "Must be less than 255 characters" }) + @Length({ max: 255, msg: "Webhook name be less than 255 characters" }) @Column name: string; @IsUrl @NotEmpty - @Length({ min: 0, max: 255, msg: "Must be less than 255 characters" }) + @Length({ max: 255, msg: "Webhook url be less than 255 characters" }) @Column url: string; diff --git a/server/models/validators/IsHexColor.ts b/server/models/validators/IsHexColor.ts new file mode 100644 index 0000000000..0a2b2eb58e --- /dev/null +++ b/server/models/validators/IsHexColor.ts @@ -0,0 +1,17 @@ +import { isHexColor } from "class-validator"; +import { addAttributeOptions } from "sequelize-typescript"; + +/** + * A decorator that validates that a string is a valid hex color code. + */ +export default function IsHexColor(target: any, propertyName: string) { + return addAttributeOptions(target, propertyName, { + validate: { + validDomain(value: string) { + if (!isHexColor(value)) { + throw new Error("Must be a valid hex color code"); + } + }, + }, + }); +} diff --git a/server/models/validators/Length.ts b/server/models/validators/Length.ts index a4773ae0cb..a36ff66521 100644 --- a/server/models/validators/Length.ts +++ b/server/models/validators/Length.ts @@ -7,11 +7,11 @@ import { addAttributeOptions } from "sequelize-typescript"; */ export default function Length({ msg, - min, + min = 0, max, }: { msg?: string; - min: number; + min?: number; max: number; }): (target: any, propertyName: string) => void { return (target: any, propertyName: string) =>