diff --git a/app/components/Sidebar/components/DocumentLink.tsx b/app/components/Sidebar/components/DocumentLink.tsx index 8a7aac0df..c0519f293 100644 --- a/app/components/Sidebar/components/DocumentLink.tsx +++ b/app/components/Sidebar/components/DocumentLink.tsx @@ -6,8 +6,8 @@ import { useDrag, useDrop } from "react-dnd"; import { useTranslation } from "react-i18next"; import { Link } from "react-router-dom"; import styled from "styled-components"; -import { MAX_TITLE_LENGTH } from "@shared/constants"; import { sortNavigationNodes } from "@shared/utils/collections"; +import { DocumentValidation } from "@shared/validations"; import Collection from "~/models/Collection"; import Document from "~/models/Document"; import Fade from "~/components/Fade"; @@ -319,7 +319,7 @@ function InnerDocumentLink( onSubmit={handleTitleChange} onEditing={handleTitleEditing} canUpdate={canUpdate} - maxLength={MAX_TITLE_LENGTH} + maxLength={DocumentValidation.maxTitleLength} /> } isActive={(match, location: Location<{ starred?: boolean }>) => diff --git a/app/scenes/CollectionEdit.tsx b/app/scenes/CollectionEdit.tsx index 3ddbdadcb..31a235ec7 100644 --- a/app/scenes/CollectionEdit.tsx +++ b/app/scenes/CollectionEdit.tsx @@ -3,7 +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 { CollectionValidation } from "@shared/validations"; import Button from "~/components/Button"; import Flex from "~/components/Flex"; import IconPicker from "~/components/IconPicker"; @@ -94,7 +94,7 @@ const CollectionEdit = ({ collectionId, onSubmit }: Props) => { type="text" label={t("Name")} onChange={handleNameChange} - maxLength={MAX_TITLE_LENGTH} + maxLength={CollectionValidation.maxNameLength} value={name} required autoFocus diff --git a/app/scenes/CollectionNew.tsx b/app/scenes/CollectionNew.tsx index ce20cbbf1..5ea45a095 100644 --- a/app/scenes/CollectionNew.tsx +++ b/app/scenes/CollectionNew.tsx @@ -3,7 +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 { CollectionValidation } from "@shared/validations"; import RootStore from "~/stores/RootStore"; import Collection from "~/models/Collection"; import Button from "~/components/Button"; @@ -128,7 +128,7 @@ class CollectionNew extends React.Component { type="text" label={t("Name")} onChange={this.handleNameChange} - maxLength={MAX_TITLE_LENGTH} + maxLength={CollectionValidation.maxNameLength} value={this.name} required autoFocus diff --git a/app/scenes/Document/components/EditableTitle.tsx b/app/scenes/Document/components/EditableTitle.tsx index d1cffeafc..e70829364 100644 --- a/app/scenes/Document/components/EditableTitle.tsx +++ b/app/scenes/Document/components/EditableTitle.tsx @@ -2,13 +2,13 @@ import { observer } from "mobx-react"; import * as React from "react"; import styled from "styled-components"; import breakpoint from "styled-components-breakpoint"; -import { MAX_TITLE_LENGTH } from "@shared/constants"; import { light } from "@shared/styles/theme"; import { getCurrentDateAsString, getCurrentDateTimeAsString, getCurrentTimeAsString, } from "@shared/utils/date"; +import { DocumentValidation } from "@shared/validations"; import Document from "~/models/Document"; import ContentEditable, { RefHandle } from "~/components/ContentEditable"; import Star, { AnimatedStar } from "~/components/Star"; @@ -132,7 +132,7 @@ const EditableTitle = React.forwardRef( $emojiWidth={emojiWidth} $isStarred={document.isStarred} autoFocus={!value} - maxLength={MAX_TITLE_LENGTH} + maxLength={DocumentValidation.maxTitleLength} readOnly={readOnly} dir="auto" ref={ref} diff --git a/app/stores/DocumentsStore.ts b/app/stores/DocumentsStore.ts index 75bf6846e..efd4c74b1 100644 --- a/app/stores/DocumentsStore.ts +++ b/app/stores/DocumentsStore.ts @@ -2,10 +2,10 @@ import path from "path"; import invariant from "invariant"; import { find, orderBy, filter, compact, omitBy } from "lodash"; import { observable, action, computed, runInAction } from "mobx"; -import { MAX_TITLE_LENGTH } from "@shared/constants"; import { DateFilter } from "@shared/types"; import { subtractDate } from "@shared/utils/date"; import naturalSort from "@shared/utils/naturalSort"; +import { DocumentValidation } from "@shared/validations"; import BaseStore from "~/stores/BaseStore"; import RootStore from "~/stores/RootStore"; import Document from "~/models/Document"; @@ -553,7 +553,7 @@ export default class DocumentsStore extends BaseStore { template: document.template, title: `${document.title.slice( 0, - MAX_TITLE_LENGTH - append.length + DocumentValidation.maxTitleLength - append.length )}${append}`, text: document.text, }); diff --git a/server/commands/documentImporter.ts b/server/commands/documentImporter.ts index b7b8a25d8..974fc0ca8 100644 --- a/server/commands/documentImporter.ts +++ b/server/commands/documentImporter.ts @@ -5,8 +5,8 @@ import mammoth from "mammoth"; import quotedPrintable from "quoted-printable"; import { Transaction } from "sequelize"; import utf8 from "utf8"; -import { MAX_TITLE_LENGTH } from "@shared/constants"; import parseTitle from "@shared/utils/parseTitle"; +import { DocumentValidation } from "@shared/validations"; import { APM } from "@server/logging/tracing"; import { User } from "@server/models"; 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 - title = truncate(title, { length: MAX_TITLE_LENGTH }); + title = truncate(title, { length: DocumentValidation.maxTitleLength }); return { text, diff --git a/server/commands/pinCreator.ts b/server/commands/pinCreator.ts index 7ce38427b..12b957ea1 100644 --- a/server/commands/pinCreator.ts +++ b/server/commands/pinCreator.ts @@ -1,11 +1,10 @@ import fractionalIndex from "fractional-index"; import { Sequelize, Op, WhereOptions } from "sequelize"; +import { PinValidation } from "@shared/validations"; import { sequelize } from "@server/database/sequelize"; import { ValidationError } from "@server/errors"; import { Pin, User, Event } from "@server/models"; -const MAX_PINS = 8; - type Props = { /** The user creating the pin */ user: User; @@ -40,8 +39,10 @@ export default async function pinCreator({ }; const count = await Pin.count({ where }); - if (count >= MAX_PINS) { - throw ValidationError(`You cannot pin more than ${MAX_PINS} documents`); + if (count >= PinValidation.max) { + throw ValidationError( + `You cannot pin more than ${PinValidation.max} documents` + ); } if (!index) { diff --git a/server/models/Collection.ts b/server/models/Collection.ts index 71d5aa2f2..1674cb1a5 100644 --- a/server/models/Collection.ts +++ b/server/models/Collection.ts @@ -21,7 +21,6 @@ import { Length as SimpleLength, } 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 { CollectionValidation } from "@shared/validations"; @@ -144,8 +143,8 @@ class Collection extends ParanoidModel { @NotContainsUrl @Length({ - max: MAX_TITLE_LENGTH, - msg: `name must be ${MAX_TITLE_LENGTH} characters or less`, + max: CollectionValidation.maxNameLength, + msg: `name must be ${CollectionValidation.maxNameLength} characters or less`, }) @Column name: string; diff --git a/server/models/Document.ts b/server/models/Document.ts index 42846cae4..68cdd5565 100644 --- a/server/models/Document.ts +++ b/server/models/Document.ts @@ -35,12 +35,12 @@ import { import MarkdownSerializer from "slate-md-serializer"; import isUUID from "validator/lib/isUUID"; import * as Y from "yjs"; -import { MAX_TITLE_LENGTH } from "@shared/constants"; import { DateFilter } from "@shared/types"; import getTasks from "@shared/utils/getTasks"; import parseTitle from "@shared/utils/parseTitle"; import unescape from "@shared/utils/unescape"; import { SLUG_URL_REGEX } from "@shared/utils/urlHelpers"; +import { DocumentValidation } from "@shared/validations"; import { parser } from "@server/editor"; import slugify from "@server/utils/slugify"; import Backlink from "./Backlink"; @@ -196,8 +196,8 @@ class Document extends ParanoidModel { urlId: string; @Length({ - max: MAX_TITLE_LENGTH, - msg: `Document title must be ${MAX_TITLE_LENGTH} characters or less`, + max: DocumentValidation.maxTitleLength, + msg: `Document title must be ${DocumentValidation.maxTitleLength} characters or less`, }) @Column title: string; diff --git a/server/models/Revision.ts b/server/models/Revision.ts index 572a67d8c..c43183267 100644 --- a/server/models/Revision.ts +++ b/server/models/Revision.ts @@ -10,7 +10,7 @@ import { Length as SimpleLength, } from "sequelize-typescript"; import MarkdownSerializer from "slate-md-serializer"; -import { MAX_TITLE_LENGTH } from "@shared/constants"; +import { DocumentValidation } from "@shared/validations"; import Document from "./Document"; import User from "./User"; import IdModel from "./base/IdModel"; @@ -43,8 +43,8 @@ class Revision extends IdModel { editorVersion: string; @Length({ - max: MAX_TITLE_LENGTH, - msg: `Revision title must be ${MAX_TITLE_LENGTH} characters or less`, + max: DocumentValidation.maxTitleLength, + msg: `Revision title must be ${DocumentValidation.maxTitleLength} characters or less`, }) @Column title: string; diff --git a/server/models/TeamDomain.ts b/server/models/TeamDomain.ts index 6779f2607..08d01e32b 100644 --- a/server/models/TeamDomain.ts +++ b/server/models/TeamDomain.ts @@ -9,7 +9,7 @@ import { BeforeValidate, BeforeCreate, } from "sequelize-typescript"; -import { MAX_TEAM_DOMAINS } from "@shared/constants"; +import { TeamValidation } from "@shared/validations"; import { ValidationError } from "@server/errors"; import Team from "./Team"; import User from "./User"; @@ -59,9 +59,9 @@ class TeamDomain extends IdModel { const count = await this.count({ where: { teamId: model.teamId }, }); - if (count >= MAX_TEAM_DOMAINS) { + if (count >= TeamValidation.maxDomains) { throw ValidationError( - `You have reached the limit of ${MAX_TEAM_DOMAINS} domains` + `You have reached the limit of ${TeamValidation.maxDomains} domains` ); } } diff --git a/shared/constants.ts b/shared/constants.ts index e3f4be290..cc5abc5b2 100644 --- a/shared/constants.ts +++ b/shared/constants.ts @@ -1,7 +1,3 @@ export const USER_PRESENCE_INTERVAL = 5000; export const MAX_AVATAR_DISPLAY = 6; - -export const MAX_TITLE_LENGTH = 100; - -export const MAX_TEAM_DOMAINS = 10; diff --git a/shared/validations.ts b/shared/validations.ts index 184ecb847..b1d2bf7b3 100644 --- a/shared/validations.ts +++ b/shared/validations.ts @@ -1,4 +1,22 @@ export const CollectionValidation = { - /* The maximum length of the collection description */ + /** The maximum length of the collection description */ 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, };