Refactor validations

This commit is contained in:
Tom Moor
2022-07-24 13:40:04 +01:00
parent 870d9ed41e
commit ec35af4bc5
13 changed files with 47 additions and 33 deletions

View File

@@ -6,8 +6,8 @@ import { useDrag, useDrop } from "react-dnd";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import styled from "styled-components"; import styled from "styled-components";
import { MAX_TITLE_LENGTH } from "@shared/constants";
import { sortNavigationNodes } from "@shared/utils/collections"; import { sortNavigationNodes } from "@shared/utils/collections";
import { DocumentValidation } from "@shared/validations";
import Collection from "~/models/Collection"; import Collection from "~/models/Collection";
import Document from "~/models/Document"; import Document from "~/models/Document";
import Fade from "~/components/Fade"; import Fade from "~/components/Fade";
@@ -319,7 +319,7 @@ function InnerDocumentLink(
onSubmit={handleTitleChange} onSubmit={handleTitleChange}
onEditing={handleTitleEditing} onEditing={handleTitleEditing}
canUpdate={canUpdate} canUpdate={canUpdate}
maxLength={MAX_TITLE_LENGTH} maxLength={DocumentValidation.maxTitleLength}
/> />
} }
isActive={(match, location: Location<{ starred?: boolean }>) => isActive={(match, location: Location<{ starred?: boolean }>) =>

View File

@@ -3,7 +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 { CollectionValidation } from "@shared/validations";
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";
@@ -94,7 +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} maxLength={CollectionValidation.maxNameLength}
value={name} value={name}
required required
autoFocus autoFocus

View File

@@ -3,7 +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 { CollectionValidation } from "@shared/validations";
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";
@@ -128,7 +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} maxLength={CollectionValidation.maxNameLength}
value={this.name} value={this.name}
required required
autoFocus autoFocus

View File

@@ -2,13 +2,13 @@ import { observer } from "mobx-react";
import * as React from "react"; import * as React from "react";
import styled from "styled-components"; import styled from "styled-components";
import breakpoint from "styled-components-breakpoint"; import breakpoint from "styled-components-breakpoint";
import { MAX_TITLE_LENGTH } from "@shared/constants";
import { light } from "@shared/styles/theme"; import { light } from "@shared/styles/theme";
import { import {
getCurrentDateAsString, getCurrentDateAsString,
getCurrentDateTimeAsString, getCurrentDateTimeAsString,
getCurrentTimeAsString, getCurrentTimeAsString,
} from "@shared/utils/date"; } from "@shared/utils/date";
import { DocumentValidation } from "@shared/validations";
import Document from "~/models/Document"; import Document from "~/models/Document";
import ContentEditable, { RefHandle } from "~/components/ContentEditable"; import ContentEditable, { RefHandle } from "~/components/ContentEditable";
import Star, { AnimatedStar } from "~/components/Star"; import Star, { AnimatedStar } from "~/components/Star";
@@ -132,7 +132,7 @@ const EditableTitle = React.forwardRef(
$emojiWidth={emojiWidth} $emojiWidth={emojiWidth}
$isStarred={document.isStarred} $isStarred={document.isStarred}
autoFocus={!value} autoFocus={!value}
maxLength={MAX_TITLE_LENGTH} maxLength={DocumentValidation.maxTitleLength}
readOnly={readOnly} readOnly={readOnly}
dir="auto" dir="auto"
ref={ref} ref={ref}

View File

@@ -2,10 +2,10 @@ import path from "path";
import invariant from "invariant"; import invariant from "invariant";
import { find, orderBy, filter, compact, omitBy } from "lodash"; import { find, orderBy, filter, compact, omitBy } from "lodash";
import { observable, action, computed, runInAction } from "mobx"; import { observable, action, computed, runInAction } from "mobx";
import { MAX_TITLE_LENGTH } from "@shared/constants";
import { DateFilter } from "@shared/types"; import { DateFilter } from "@shared/types";
import { subtractDate } from "@shared/utils/date"; import { subtractDate } from "@shared/utils/date";
import naturalSort from "@shared/utils/naturalSort"; import naturalSort from "@shared/utils/naturalSort";
import { DocumentValidation } from "@shared/validations";
import BaseStore from "~/stores/BaseStore"; import BaseStore from "~/stores/BaseStore";
import RootStore from "~/stores/RootStore"; import RootStore from "~/stores/RootStore";
import Document from "~/models/Document"; import Document from "~/models/Document";
@@ -553,7 +553,7 @@ export default class DocumentsStore extends BaseStore<Document> {
template: document.template, template: document.template,
title: `${document.title.slice( title: `${document.title.slice(
0, 0,
MAX_TITLE_LENGTH - append.length DocumentValidation.maxTitleLength - append.length
)}${append}`, )}${append}`,
text: document.text, text: document.text,
}); });

View File

@@ -5,8 +5,8 @@ import mammoth from "mammoth";
import quotedPrintable from "quoted-printable"; import quotedPrintable from "quoted-printable";
import { Transaction } from "sequelize"; import { Transaction } from "sequelize";
import utf8 from "utf8"; import utf8 from "utf8";
import { MAX_TITLE_LENGTH } from "@shared/constants";
import parseTitle from "@shared/utils/parseTitle"; import parseTitle from "@shared/utils/parseTitle";
import { DocumentValidation } from "@shared/validations";
import { APM } from "@server/logging/tracing"; import { APM } from "@server/logging/tracing";
import { User } from "@server/models"; import { User } from "@server/models";
import dataURItoBuffer from "@server/utils/dataURItoBuffer"; import dataURItoBuffer from "@server/utils/dataURItoBuffer";
@@ -221,7 +221,7 @@ async function documentImporter({
} }
// It's better to truncate particularly long titles than fail the import // It's better to truncate particularly long titles than fail the import
title = truncate(title, { length: MAX_TITLE_LENGTH }); title = truncate(title, { length: DocumentValidation.maxTitleLength });
return { return {
text, text,

View File

@@ -1,11 +1,10 @@
import fractionalIndex from "fractional-index"; import fractionalIndex from "fractional-index";
import { Sequelize, Op, WhereOptions } from "sequelize"; import { Sequelize, Op, WhereOptions } from "sequelize";
import { PinValidation } from "@shared/validations";
import { sequelize } from "@server/database/sequelize"; import { sequelize } from "@server/database/sequelize";
import { ValidationError } from "@server/errors"; import { ValidationError } from "@server/errors";
import { Pin, User, Event } from "@server/models"; import { Pin, User, Event } from "@server/models";
const MAX_PINS = 8;
type Props = { type Props = {
/** The user creating the pin */ /** The user creating the pin */
user: User; user: User;
@@ -40,8 +39,10 @@ export default async function pinCreator({
}; };
const count = await Pin.count({ where }); const count = await Pin.count({ where });
if (count >= MAX_PINS) { if (count >= PinValidation.max) {
throw ValidationError(`You cannot pin more than ${MAX_PINS} documents`); throw ValidationError(
`You cannot pin more than ${PinValidation.max} documents`
);
} }
if (!index) { if (!index) {

View File

@@ -21,7 +21,6 @@ import {
Length as SimpleLength, Length as SimpleLength,
} 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 { CollectionValidation } from "@shared/validations"; import { CollectionValidation } from "@shared/validations";
@@ -144,8 +143,8 @@ class Collection extends ParanoidModel {
@NotContainsUrl @NotContainsUrl
@Length({ @Length({
max: MAX_TITLE_LENGTH, max: CollectionValidation.maxNameLength,
msg: `name must be ${MAX_TITLE_LENGTH} characters or less`, msg: `name must be ${CollectionValidation.maxNameLength} characters or less`,
}) })
@Column @Column
name: string; name: string;

View File

@@ -35,12 +35,12 @@ import {
import MarkdownSerializer from "slate-md-serializer"; import MarkdownSerializer from "slate-md-serializer";
import isUUID from "validator/lib/isUUID"; import isUUID from "validator/lib/isUUID";
import * as Y from "yjs"; import * as Y from "yjs";
import { MAX_TITLE_LENGTH } from "@shared/constants";
import { DateFilter } from "@shared/types"; import { DateFilter } from "@shared/types";
import getTasks from "@shared/utils/getTasks"; import getTasks from "@shared/utils/getTasks";
import parseTitle from "@shared/utils/parseTitle"; import parseTitle from "@shared/utils/parseTitle";
import unescape from "@shared/utils/unescape"; import unescape from "@shared/utils/unescape";
import { SLUG_URL_REGEX } from "@shared/utils/urlHelpers"; import { SLUG_URL_REGEX } from "@shared/utils/urlHelpers";
import { DocumentValidation } from "@shared/validations";
import { parser } from "@server/editor"; import { parser } from "@server/editor";
import slugify from "@server/utils/slugify"; import slugify from "@server/utils/slugify";
import Backlink from "./Backlink"; import Backlink from "./Backlink";
@@ -196,8 +196,8 @@ class Document extends ParanoidModel {
urlId: string; urlId: string;
@Length({ @Length({
max: MAX_TITLE_LENGTH, max: DocumentValidation.maxTitleLength,
msg: `Document title must be ${MAX_TITLE_LENGTH} characters or less`, msg: `Document title must be ${DocumentValidation.maxTitleLength} characters or less`,
}) })
@Column @Column
title: string; title: string;

View File

@@ -10,7 +10,7 @@ import {
Length as SimpleLength, Length as SimpleLength,
} from "sequelize-typescript"; } from "sequelize-typescript";
import MarkdownSerializer from "slate-md-serializer"; import MarkdownSerializer from "slate-md-serializer";
import { MAX_TITLE_LENGTH } from "@shared/constants"; import { DocumentValidation } from "@shared/validations";
import Document from "./Document"; import Document from "./Document";
import User from "./User"; import User from "./User";
import IdModel from "./base/IdModel"; import IdModel from "./base/IdModel";
@@ -43,8 +43,8 @@ class Revision extends IdModel {
editorVersion: string; editorVersion: string;
@Length({ @Length({
max: MAX_TITLE_LENGTH, max: DocumentValidation.maxTitleLength,
msg: `Revision title must be ${MAX_TITLE_LENGTH} characters or less`, msg: `Revision title must be ${DocumentValidation.maxTitleLength} characters or less`,
}) })
@Column @Column
title: string; title: string;

View File

@@ -9,7 +9,7 @@ import {
BeforeValidate, BeforeValidate,
BeforeCreate, BeforeCreate,
} from "sequelize-typescript"; } from "sequelize-typescript";
import { MAX_TEAM_DOMAINS } from "@shared/constants"; import { TeamValidation } from "@shared/validations";
import { ValidationError } from "@server/errors"; import { ValidationError } from "@server/errors";
import Team from "./Team"; import Team from "./Team";
import User from "./User"; import User from "./User";
@@ -59,9 +59,9 @@ class TeamDomain extends IdModel {
const count = await this.count({ const count = await this.count({
where: { teamId: model.teamId }, where: { teamId: model.teamId },
}); });
if (count >= MAX_TEAM_DOMAINS) { if (count >= TeamValidation.maxDomains) {
throw ValidationError( throw ValidationError(
`You have reached the limit of ${MAX_TEAM_DOMAINS} domains` `You have reached the limit of ${TeamValidation.maxDomains} domains`
); );
} }
} }

View File

@@ -1,7 +1,3 @@
export const USER_PRESENCE_INTERVAL = 5000; export const USER_PRESENCE_INTERVAL = 5000;
export const MAX_AVATAR_DISPLAY = 6; export const MAX_AVATAR_DISPLAY = 6;
export const MAX_TITLE_LENGTH = 100;
export const MAX_TEAM_DOMAINS = 10;

View File

@@ -1,4 +1,22 @@
export const CollectionValidation = { export const CollectionValidation = {
/* The maximum length of the collection description */ /** The maximum length of the collection description */
maxDescriptionLength: 1000, maxDescriptionLength: 1000,
/** The maximum length of the collection name */
maxNameLength: 100,
};
export const DocumentValidation = {
/** The maximum length of the document title */
maxTitleLength: 100,
};
export const PinValidation = {
/** The maximum number of pinned documents on an individual collection or home screen */
max: 8,
};
export const TeamValidation = {
/** The maximum number of domains per team */
maxDomains: 10,
}; };