mirror of
https://github.com/outline/outline.git
synced 2025-12-21 10:39:41 -06:00
fix: Improved model validation for Collection (#3749)
* fix: Added improved model validation for Collection attributes * sp * fix: Enforce title length in UI
This commit is contained in:
@@ -3,6 +3,7 @@ import { observer } from "mobx-react";
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Trans, useTranslation } from "react-i18next";
|
import { Trans, useTranslation } from "react-i18next";
|
||||||
|
import { MAX_TITLE_LENGTH } from "@shared/constants";
|
||||||
import Button from "~/components/Button";
|
import Button from "~/components/Button";
|
||||||
import Flex from "~/components/Flex";
|
import Flex from "~/components/Flex";
|
||||||
import IconPicker from "~/components/IconPicker";
|
import IconPicker from "~/components/IconPicker";
|
||||||
@@ -93,6 +94,7 @@ const CollectionEdit = ({ collectionId, onSubmit }: Props) => {
|
|||||||
type="text"
|
type="text"
|
||||||
label={t("Name")}
|
label={t("Name")}
|
||||||
onChange={handleNameChange}
|
onChange={handleNameChange}
|
||||||
|
maxLength={MAX_TITLE_LENGTH}
|
||||||
value={name}
|
value={name}
|
||||||
required
|
required
|
||||||
autoFocus
|
autoFocus
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { observable } from "mobx";
|
|||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { withTranslation, Trans, WithTranslation } from "react-i18next";
|
import { withTranslation, Trans, WithTranslation } from "react-i18next";
|
||||||
|
import { MAX_TITLE_LENGTH } from "@shared/constants";
|
||||||
import RootStore from "~/stores/RootStore";
|
import RootStore from "~/stores/RootStore";
|
||||||
import Collection from "~/models/Collection";
|
import Collection from "~/models/Collection";
|
||||||
import Button from "~/components/Button";
|
import Button from "~/components/Button";
|
||||||
@@ -127,6 +128,7 @@ class CollectionNew extends React.Component<Props> {
|
|||||||
type="text"
|
type="text"
|
||||||
label={t("Name")}
|
label={t("Name")}
|
||||||
onChange={this.handleNameChange}
|
onChange={this.handleNameChange}
|
||||||
|
maxLength={MAX_TITLE_LENGTH}
|
||||||
value={this.name}
|
value={this.name}
|
||||||
required
|
required
|
||||||
autoFocus
|
autoFocus
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import {
|
|||||||
DataType,
|
DataType,
|
||||||
} from "sequelize-typescript";
|
} from "sequelize-typescript";
|
||||||
import isUUID from "validator/lib/isUUID";
|
import isUUID from "validator/lib/isUUID";
|
||||||
|
import { MAX_TITLE_LENGTH } from "@shared/constants";
|
||||||
import { sortNavigationNodes } from "@shared/utils/collections";
|
import { sortNavigationNodes } from "@shared/utils/collections";
|
||||||
import { SLUG_URL_REGEX } from "@shared/utils/urlHelpers";
|
import { SLUG_URL_REGEX } from "@shared/utils/urlHelpers";
|
||||||
import slugify from "@server/utils/slugify";
|
import slugify from "@server/utils/slugify";
|
||||||
@@ -33,6 +34,8 @@ import Team from "./Team";
|
|||||||
import User from "./User";
|
import User from "./User";
|
||||||
import ParanoidModel from "./base/ParanoidModel";
|
import ParanoidModel from "./base/ParanoidModel";
|
||||||
import Fix from "./decorators/Fix";
|
import Fix from "./decorators/Fix";
|
||||||
|
import IsHexColor from "./validators/IsHexColor";
|
||||||
|
import Length from "./validators/Length";
|
||||||
import NotContainsUrl from "./validators/NotContainsUrl";
|
import NotContainsUrl from "./validators/NotContainsUrl";
|
||||||
|
|
||||||
// without this indirection, the app crashes on starup
|
// without this indirection, the app crashes on starup
|
||||||
@@ -133,18 +136,35 @@ class Collection extends ParanoidModel {
|
|||||||
urlId: string;
|
urlId: string;
|
||||||
|
|
||||||
@NotContainsUrl
|
@NotContainsUrl
|
||||||
|
@Length({
|
||||||
|
max: MAX_TITLE_LENGTH,
|
||||||
|
msg: `Collection name must be less than ${MAX_TITLE_LENGTH} characters`,
|
||||||
|
})
|
||||||
@Column
|
@Column
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
|
@Length({
|
||||||
|
max: 1000,
|
||||||
|
msg: `Collection description must be less than 1000 characters`,
|
||||||
|
})
|
||||||
@Column
|
@Column
|
||||||
description: string;
|
description: string;
|
||||||
|
|
||||||
|
@Length({
|
||||||
|
max: 50,
|
||||||
|
msg: `Collection icon must be less than 50 characters`,
|
||||||
|
})
|
||||||
@Column
|
@Column
|
||||||
icon: string | null;
|
icon: string | null;
|
||||||
|
|
||||||
|
@IsHexColor
|
||||||
@Column
|
@Column
|
||||||
color: string | null;
|
color: string | null;
|
||||||
|
|
||||||
|
@Length({
|
||||||
|
max: 50,
|
||||||
|
msg: `Collection index must be less than 50 characters`,
|
||||||
|
})
|
||||||
@Column
|
@Column
|
||||||
index: string | null;
|
index: string | null;
|
||||||
|
|
||||||
|
|||||||
@@ -188,7 +188,6 @@ class Document extends ParanoidModel {
|
|||||||
urlId: string;
|
urlId: string;
|
||||||
|
|
||||||
@Length({
|
@Length({
|
||||||
min: 0,
|
|
||||||
max: MAX_TITLE_LENGTH,
|
max: MAX_TITLE_LENGTH,
|
||||||
msg: `Document title must be less than ${MAX_TITLE_LENGTH} characters`,
|
msg: `Document title must be less than ${MAX_TITLE_LENGTH} characters`,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -84,21 +84,17 @@ export enum UserFlag {
|
|||||||
@Fix
|
@Fix
|
||||||
class User extends ParanoidModel {
|
class User extends ParanoidModel {
|
||||||
@IsEmail
|
@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
|
@Column
|
||||||
email: string | null;
|
email: string | null;
|
||||||
|
|
||||||
@NotContainsUrl
|
@NotContainsUrl
|
||||||
@Length({
|
@Length({ max: 255, msg: "User username must be less than 255 characters" })
|
||||||
min: 0,
|
|
||||||
max: 255,
|
|
||||||
msg: "username must be less than 255 characters",
|
|
||||||
})
|
|
||||||
@Column
|
@Column
|
||||||
username: string | null;
|
username: string | null;
|
||||||
|
|
||||||
@NotContainsUrl
|
@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
|
@Column
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
|
|||||||
@@ -22,13 +22,13 @@ import Length from "./validators/Length";
|
|||||||
@Fix
|
@Fix
|
||||||
class WebhookSubscription extends ParanoidModel {
|
class WebhookSubscription extends ParanoidModel {
|
||||||
@NotEmpty
|
@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
|
@Column
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
@IsUrl
|
@IsUrl
|
||||||
@NotEmpty
|
@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
|
@Column
|
||||||
url: string;
|
url: string;
|
||||||
|
|
||||||
|
|||||||
17
server/models/validators/IsHexColor.ts
Normal file
17
server/models/validators/IsHexColor.ts
Normal file
@@ -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");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -7,11 +7,11 @@ import { addAttributeOptions } from "sequelize-typescript";
|
|||||||
*/
|
*/
|
||||||
export default function Length({
|
export default function Length({
|
||||||
msg,
|
msg,
|
||||||
min,
|
min = 0,
|
||||||
max,
|
max,
|
||||||
}: {
|
}: {
|
||||||
msg?: string;
|
msg?: string;
|
||||||
min: number;
|
min?: number;
|
||||||
max: number;
|
max: number;
|
||||||
}): (target: any, propertyName: string) => void {
|
}): (target: any, propertyName: string) => void {
|
||||||
return (target: any, propertyName: string) =>
|
return (target: any, propertyName: string) =>
|
||||||
|
|||||||
Reference in New Issue
Block a user