From fd34a6d19c6c9190c583f0594339eab1ef81e39c Mon Sep 17 00:00:00 2001 From: Apoorv Mishra Date: Thu, 29 Feb 2024 11:41:03 +0530 Subject: [PATCH] Refactor and regroup `urlHelpers` utils (#6462) * fix: refactor urlHelpers * fix: move to /plugins/slack/shared * fix: remove .babelrc * fix: remove Outline class * fix: Slack -> SlackUtils * fix: UrlHelper class --- app/actions/definitions/navigation.tsx | 15 ++-- app/components/ErrorBoundary.tsx | 4 +- app/components/Sharing/PublicAccess.tsx | 4 +- build.js | 8 +++ plugins/slack/client/Settings.tsx | 3 +- .../slack/client/components/SlackButton.tsx | 14 +--- plugins/slack/server/auth/slack.ts | 44 ++++++------ plugins/slack/server/slack.ts | 3 +- plugins/slack/shared/SlackUtils.ts | 70 +++++++++++++++++++ server/commands/documentLoader.ts | 4 +- server/emails/templates/components/Footer.tsx | 4 +- server/models/Collection.ts | 4 +- server/models/Document.ts | 4 +- server/models/Share.ts | 4 +- server/routes/api/documents/schema.ts | 6 +- server/routes/api/pins/schema.ts | 4 +- server/routes/api/shares/schema.ts | 11 +-- server/validation.ts | 4 +- shared/utils/UrlHelper.ts | 10 +++ shared/utils/urlHelpers.ts | 53 -------------- 20 files changed, 146 insertions(+), 127 deletions(-) create mode 100644 plugins/slack/shared/SlackUtils.ts create mode 100644 shared/utils/UrlHelper.ts delete mode 100644 shared/utils/urlHelpers.ts diff --git a/app/actions/definitions/navigation.tsx b/app/actions/definitions/navigation.tsx index 844dfd76b8..8c310a2509 100644 --- a/app/actions/definitions/navigation.tsx +++ b/app/actions/definitions/navigation.tsx @@ -14,13 +14,8 @@ import { ShapesIcon, } from "outline-icons"; import * as React from "react"; +import { UrlHelper } from "@shared/utils/UrlHelper"; import { isMac } from "@shared/utils/browser"; -import { - developersUrl, - changelogUrl, - feedbackUrl, - githubIssuesUrl, -} from "@shared/utils/urlHelpers"; import stores from "~/stores"; import SearchQuery from "~/models/SearchQuery"; import KeyboardShortcuts from "~/scenes/KeyboardShortcuts"; @@ -139,7 +134,7 @@ export const openAPIDocumentation = createAction({ section: NavigationSection, iconInContextMenu: false, icon: , - perform: () => window.open(developersUrl()), + perform: () => window.open(UrlHelper.developers), }); export const toggleSidebar = createAction({ @@ -156,14 +151,14 @@ export const openFeedbackUrl = createAction({ section: NavigationSection, iconInContextMenu: false, icon: , - perform: () => window.open(feedbackUrl()), + perform: () => window.open(UrlHelper.contact), }); export const openBugReportUrl = createAction({ name: ({ t }) => t("Report a bug"), analyticsName: "Open bug report", section: NavigationSection, - perform: () => window.open(githubIssuesUrl()), + perform: () => window.open(UrlHelper.github), }); export const openChangelog = createAction({ @@ -172,7 +167,7 @@ export const openChangelog = createAction({ section: NavigationSection, iconInContextMenu: false, icon: , - perform: () => window.open(changelogUrl()), + perform: () => window.open(UrlHelper.changelog), }); export const openKeyboardShortcuts = createAction({ diff --git a/app/components/ErrorBoundary.tsx b/app/components/ErrorBoundary.tsx index 0cc72ffffa..e2a0dcd1c9 100644 --- a/app/components/ErrorBoundary.tsx +++ b/app/components/ErrorBoundary.tsx @@ -4,7 +4,7 @@ import * as React from "react"; import { withTranslation, Trans, WithTranslation } from "react-i18next"; import styled from "styled-components"; import { s } from "@shared/styles"; -import { githubIssuesUrl, feedbackUrl } from "@shared/utils/urlHelpers"; +import { UrlHelper } from "@shared/utils/UrlHelper"; import Button from "~/components/Button"; import CenteredContent from "~/components/CenteredContent"; import PageTitle from "~/components/PageTitle"; @@ -57,7 +57,7 @@ class ErrorBoundary extends React.Component { }; handleReportBug = () => { - window.open(isCloudHosted ? feedbackUrl() : githubIssuesUrl()); + window.open(isCloudHosted ? UrlHelper.contact : UrlHelper.github); }; render() { diff --git a/app/components/Sharing/PublicAccess.tsx b/app/components/Sharing/PublicAccess.tsx index a65d6e4b20..c5d21b2b4a 100644 --- a/app/components/Sharing/PublicAccess.tsx +++ b/app/components/Sharing/PublicAccess.tsx @@ -9,7 +9,7 @@ import { Link } from "react-router-dom"; import { toast } from "sonner"; import styled, { useTheme } from "styled-components"; import { s } from "@shared/styles"; -import { SHARE_URL_SLUG_REGEX } from "@shared/utils/urlHelpers"; +import { UrlHelper } from "@shared/utils/UrlHelper"; import Document from "~/models/Document"; import Share from "~/models/Share"; import Input, { NativeInput } from "~/components/Input"; @@ -78,7 +78,7 @@ function PublicAccess({ document, share, sharedParent }: Props) { const val = ev.target.value; setUrlId(val); - if (val && !SHARE_URL_SLUG_REGEX.test(val)) { + if (val && !UrlHelper.SHARE_URL_SLUG_REGEX.test(val)) { setValidationError( t("Only lowercase letters, digits and dashes allowed") ); diff --git a/build.js b/build.js index a5af24023c..998fa4df91 100755 --- a/build.js +++ b/build.js @@ -53,6 +53,14 @@ async function build() { `yarn babel --extensions .ts,.tsx --quiet -d "./build/plugins/${plugin}/server" "./plugins/${plugin}/server"` ); } + + const hasShared = existsSync(`./plugins/${plugin}/shared`); + + if (hasShared) { + await execAsync( + `yarn babel --extensions .ts,.tsx --quiet -d "./build/plugins/${plugin}/shared" "./plugins/${plugin}/shared"` + ); + } }), ]); diff --git a/plugins/slack/client/Settings.tsx b/plugins/slack/client/Settings.tsx index 65f7c06ea7..15423e95ba 100644 --- a/plugins/slack/client/Settings.tsx +++ b/plugins/slack/client/Settings.tsx @@ -17,6 +17,7 @@ import env from "~/env"; import useCurrentTeam from "~/hooks/useCurrentTeam"; import useQuery from "~/hooks/useQuery"; import useStores from "~/hooks/useStores"; +import { SlackUtils } from "../shared/SlackUtils"; import SlackIcon from "./Icon"; import SlackButton from "./components/SlackButton"; import SlackListItem from "./components/SlackListItem"; @@ -104,7 +105,7 @@ function Slack() { // "users:read", // "users:read.email", ]} - redirectUri={`${env.URL}/auth/slack.commands`} + redirectUri={SlackUtils.commandsUrl()} state={team.id} icon={} /> diff --git a/plugins/slack/client/components/SlackButton.tsx b/plugins/slack/client/components/SlackButton.tsx index 79c157ac8c..255dccc26b 100644 --- a/plugins/slack/client/components/SlackButton.tsx +++ b/plugins/slack/client/components/SlackButton.tsx @@ -1,8 +1,7 @@ import * as React from "react"; import { useTranslation } from "react-i18next"; -import { slackAuth } from "@shared/utils/urlHelpers"; import Button from "~/components/Button"; -import env from "~/env"; +import { SlackUtils } from "../../shared/SlackUtils"; type Props = { scopes?: string[]; @@ -16,16 +15,7 @@ function SlackButton({ state = "", scopes, redirectUri, label, icon }: Props) { const { t } = useTranslation(); const handleClick = () => { - if (!env.SLACK_CLIENT_ID) { - return; - } - - window.location.href = slackAuth( - state, - scopes, - env.SLACK_CLIENT_ID, - redirectUri - ); + window.location.href = SlackUtils.authUrl(state, scopes, redirectUri); }; return ( diff --git a/plugins/slack/server/auth/slack.ts b/plugins/slack/server/auth/slack.ts index 947e3ec471..51d8778b6e 100644 --- a/plugins/slack/server/auth/slack.ts +++ b/plugins/slack/server/auth/slack.ts @@ -4,7 +4,6 @@ import Router from "koa-router"; import { Profile } from "passport"; import { Strategy as SlackStrategy } from "passport-slack-oauth2"; import { IntegrationService, IntegrationType } from "@shared/types"; -import { integrationSettingsPath } from "@shared/utils/routeHelpers"; import accountProvisioner from "@server/commands/accountProvisioner"; import auth from "@server/middlewares/authentication"; import passportMiddleware from "@server/middlewares/passport"; @@ -25,6 +24,7 @@ import { import env from "../env"; import * as Slack from "../slack"; import * as T from "./schema"; +import { SlackUtils } from "plugins/slack/shared/SlackUtils"; type SlackProfile = Profile & { team: { @@ -66,7 +66,7 @@ if (env.SLACK_CLIENT_ID && env.SLACK_CLIENT_SECRET) { { clientID: env.SLACK_CLIENT_ID, clientSecret: env.SLACK_CLIENT_SECRET, - callbackURL: `${env.URL}/auth/slack.callback`, + callbackURL: SlackUtils.callbackUrl(), passReqToCallback: true, // @ts-expect-error StateStore store: new StateStore(), @@ -139,7 +139,7 @@ if (env.SLACK_CLIENT_ID && env.SLACK_CLIENT_SECRET) { const { user } = ctx.state.auth; if (error) { - ctx.redirect(integrationSettingsPath(`slack?error=${error}`)); + ctx.redirect(SlackUtils.errorUrl(error)); return; } @@ -154,23 +154,21 @@ if (env.SLACK_CLIENT_ID && env.SLACK_CLIENT_SECRET) { }); return redirectOnClient( ctx, - `${team.url}/auth/slack.commands?${ctx.request.querystring}` + SlackUtils.commandsUrl({ + baseUrl: team.url, + params: ctx.request.querystring, + }) ); } catch (err) { - return ctx.redirect( - integrationSettingsPath(`slack?error=unauthenticated`) - ); + return ctx.redirect(SlackUtils.errorUrl("unauthenticated")); } } else { - return ctx.redirect( - integrationSettingsPath(`slack?error=unauthenticated`) - ); + return ctx.redirect(SlackUtils.errorUrl("unauthenticated")); } } - const endpoint = `${env.URL}/auth/slack.commands`; // validation middleware ensures that code is non-null at this point - const data = await Slack.oauthAccess(code!, endpoint); + const data = await Slack.oauthAccess(code!, SlackUtils.commandsUrl()); const authentication = await IntegrationAuthentication.create({ service: IntegrationService.Slack, userId: user.id, @@ -188,7 +186,7 @@ if (env.SLACK_CLIENT_ID && env.SLACK_CLIENT_SECRET) { serviceTeamId: data.team_id, }, }); - ctx.redirect(integrationSettingsPath("slack")); + ctx.redirect(SlackUtils.url); } ); @@ -203,7 +201,7 @@ if (env.SLACK_CLIENT_ID && env.SLACK_CLIENT_SECRET) { const { user } = ctx.state.auth; if (error) { - ctx.redirect(integrationSettingsPath(`slack?error=${error}`)); + ctx.redirect(SlackUtils.errorUrl(error)); return; } @@ -221,23 +219,21 @@ if (env.SLACK_CLIENT_ID && env.SLACK_CLIENT_SECRET) { }); return redirectOnClient( ctx, - `${team.url}/auth/slack.post?${ctx.request.querystring}` + SlackUtils.postUrl({ + baseUrl: team.url, + params: ctx.request.querystring, + }) ); } catch (err) { - return ctx.redirect( - integrationSettingsPath(`slack?error=unauthenticated`) - ); + return ctx.redirect(SlackUtils.errorUrl("unauthenticated")); } } else { - return ctx.redirect( - integrationSettingsPath(`slack?error=unauthenticated`) - ); + return ctx.redirect(SlackUtils.errorUrl("unauthenticated")); } } - const endpoint = `${env.URL}/auth/slack.post`; // validation middleware ensures that code is non-null at this point - const data = await Slack.oauthAccess(code!, endpoint); + const data = await Slack.oauthAccess(code!, SlackUtils.postUrl()); const authentication = await IntegrationAuthentication.create({ service: IntegrationService.Slack, userId: user.id, @@ -260,7 +256,7 @@ if (env.SLACK_CLIENT_ID && env.SLACK_CLIENT_SECRET) { channelId: data.incoming_webhook.channel_id, }, }); - ctx.redirect(integrationSettingsPath("slack")); + ctx.redirect(SlackUtils.url); } ); } diff --git a/plugins/slack/server/slack.ts b/plugins/slack/server/slack.ts index 17f70634f0..f035cfb35d 100644 --- a/plugins/slack/server/slack.ts +++ b/plugins/slack/server/slack.ts @@ -1,6 +1,7 @@ import querystring from "querystring"; import { InvalidRequestError } from "@server/errors"; import fetch from "@server/utils/fetch"; +import { SlackUtils } from "../shared/SlackUtils"; import env from "./env"; const SLACK_API_URL = "https://slack.com/api"; @@ -49,7 +50,7 @@ export async function request(endpoint: string, body: Record) { export async function oauthAccess( code: string, - redirect_uri = `${env.URL}/auth/slack.callback` + redirect_uri = SlackUtils.callbackUrl() ) { return request("oauth.access", { client_id: env.SLACK_CLIENT_ID, diff --git a/plugins/slack/shared/SlackUtils.ts b/plugins/slack/shared/SlackUtils.ts new file mode 100644 index 0000000000..16b917fe43 --- /dev/null +++ b/plugins/slack/shared/SlackUtils.ts @@ -0,0 +1,70 @@ +import env from "@shared/env"; +import { integrationSettingsPath } from "@shared/utils/routeHelpers"; + +export class SlackUtils { + private static authBaseUrl = "https://slack.com/oauth/authorize"; + + static commandsUrl( + { baseUrl, params }: { baseUrl: string; params?: string } = { + baseUrl: `${env.URL}`, + params: undefined, + } + ) { + return params + ? `${baseUrl}/auth/slack.commands?${params}` + : `${baseUrl}/auth/slack.commands`; + } + + static callbackUrl( + { baseUrl, params }: { baseUrl: string; params?: string } = { + baseUrl: `${env.URL}`, + params: undefined, + } + ) { + return params + ? `${baseUrl}/auth/slack.callback?${params}` + : `${baseUrl}/auth/slack.callback`; + } + + static postUrl( + { baseUrl, params }: { baseUrl: string; params?: string } = { + baseUrl: `${env.URL}`, + params: undefined, + } + ) { + return params + ? `${baseUrl}/auth/slack.post?${params}` + : `${baseUrl}/auth/slack.post`; + } + + static errorUrl(err: string) { + return integrationSettingsPath(`slack?error=${err}`); + } + + static get url() { + return integrationSettingsPath("slack"); + } + + static authUrl( + state: string, + scopes: string[] = [ + "identity.email", + "identity.basic", + "identity.avatar", + "identity.team", + ], + redirectUri = SlackUtils.callbackUrl() + ): string { + const baseUrl = SlackUtils.authBaseUrl; + const params = { + client_id: env.SLACK_CLIENT_ID, + scope: scopes ? scopes.join(" ") : "", + redirect_uri: redirectUri, + state, + }; + const urlParams = Object.keys(params) + .map((key) => `${key}=${encodeURIComponent(params[key])}`) + .join("&"); + return `${baseUrl}?${urlParams}`; + } +} diff --git a/server/commands/documentLoader.ts b/server/commands/documentLoader.ts index e64d7243ad..fe66197fb9 100644 --- a/server/commands/documentLoader.ts +++ b/server/commands/documentLoader.ts @@ -1,7 +1,7 @@ import invariant from "invariant"; import { Op, WhereOptions } from "sequelize"; import isUUID from "validator/lib/isUUID"; -import { SHARE_URL_SLUG_REGEX } from "@shared/utils/urlHelpers"; +import { UrlHelper } from "@shared/utils/UrlHelper"; import { NotFoundError, InvalidRequestError, @@ -42,7 +42,7 @@ export default async function loadDocument({ } const shareUrlId = - shareId && !isUUID(shareId) && SHARE_URL_SLUG_REGEX.test(shareId) + shareId && !isUUID(shareId) && UrlHelper.SHARE_URL_SLUG_REGEX.test(shareId) ? shareId : undefined; diff --git a/server/emails/templates/components/Footer.tsx b/server/emails/templates/components/Footer.tsx index f4b083c37b..5fd6595ece 100644 --- a/server/emails/templates/components/Footer.tsx +++ b/server/emails/templates/components/Footer.tsx @@ -1,7 +1,7 @@ import { Table, TBody, TR, TD } from "oy-vey"; import * as React from "react"; import theme from "@shared/styles/theme"; -import { twitterUrl } from "@shared/utils/urlHelpers"; +import { UrlHelper } from "@shared/utils/UrlHelper"; import env from "@server/env"; type Props = { @@ -54,7 +54,7 @@ export default ({ unsubscribeUrl, children }: Props) => { {env.APP_NAME} - + Twitter diff --git a/server/models/Collection.ts b/server/models/Collection.ts index a2ae457167..9256721396 100644 --- a/server/models/Collection.ts +++ b/server/models/Collection.ts @@ -34,9 +34,9 @@ import { import isUUID from "validator/lib/isUUID"; import type { CollectionSort } from "@shared/types"; import { CollectionPermission, NavigationNode } from "@shared/types"; +import { UrlHelper } from "@shared/utils/UrlHelper"; import { sortNavigationNodes } from "@shared/utils/collections"; import slugify from "@shared/utils/slugify"; -import { SLUG_URL_REGEX } from "@shared/utils/urlHelpers"; import { CollectionValidation } from "@shared/validations"; import { ValidationError } from "@server/errors"; import Document from "./Document"; @@ -394,7 +394,7 @@ class Collection extends ParanoidModel< }); } - const match = id.match(SLUG_URL_REGEX); + const match = id.match(UrlHelper.SLUG_URL_REGEX); if (match) { return this.findOne({ where: { diff --git a/server/models/Document.ts b/server/models/Document.ts index ef13aa0a61..ca817a253a 100644 --- a/server/models/Document.ts +++ b/server/models/Document.ts @@ -47,9 +47,9 @@ import type { ProsemirrorData, SourceMetadata, } from "@shared/types"; +import { UrlHelper } from "@shared/utils/UrlHelper"; import getTasks from "@shared/utils/getTasks"; import slugify from "@shared/utils/slugify"; -import { SLUG_URL_REGEX } from "@shared/utils/urlHelpers"; import { DocumentValidation } from "@shared/validations"; import { ValidationError } from "@server/errors"; import Backlink from "./Backlink"; @@ -611,7 +611,7 @@ class Document extends ParanoidModel< return document; } - const match = id.match(SLUG_URL_REGEX); + const match = id.match(UrlHelper.SLUG_URL_REGEX); if (match) { const document = await scope.findOne({ where: { diff --git a/server/models/Share.ts b/server/models/Share.ts index 2657756c5f..ee2191dad7 100644 --- a/server/models/Share.ts +++ b/server/models/Share.ts @@ -17,7 +17,7 @@ import { Unique, BeforeUpdate, } from "sequelize-typescript"; -import { SHARE_URL_SLUG_REGEX } from "@shared/utils/urlHelpers"; +import { UrlHelper } from "@shared/utils/UrlHelper"; import env from "@server/env"; import { ValidationError } from "@server/errors"; import Collection from "./Collection"; @@ -96,7 +96,7 @@ class Share extends IdModel< @AllowNull @Is({ - args: SHARE_URL_SLUG_REGEX, + args: UrlHelper.SHARE_URL_SLUG_REGEX, msg: "Must be only alphanumeric and dashes", }) @Column diff --git a/server/routes/api/documents/schema.ts b/server/routes/api/documents/schema.ts index a7aac22aab..e3c05991f4 100644 --- a/server/routes/api/documents/schema.ts +++ b/server/routes/api/documents/schema.ts @@ -4,7 +4,7 @@ import isEmpty from "lodash/isEmpty"; import isUUID from "validator/lib/isUUID"; import { z } from "zod"; import { DocumentPermission, StatusFilter } from "@shared/types"; -import { SHARE_URL_SLUG_REGEX } from "@shared/utils/urlHelpers"; +import { UrlHelper } from "@shared/utils/UrlHelper"; import { BaseSchema } from "@server/routes/api/schema"; const DocumentsSortParamsSchema = z.object({ @@ -115,7 +115,7 @@ export const DocumentsInfoSchema = BaseSchema.extend({ /** Share Id, if available */ shareId: z .string() - .refine((val) => isUUID(val) || SHARE_URL_SLUG_REGEX.test(val)) + .refine((val) => isUUID(val) || UrlHelper.SHARE_URL_SLUG_REGEX.test(val)) .optional(), /** Version of the API to be used */ @@ -173,7 +173,7 @@ export const DocumentsSearchSchema = BaseSchema.extend({ /** Filter results for the team derived from shareId */ shareId: z .string() - .refine((val) => isUUID(val) || SHARE_URL_SLUG_REGEX.test(val)) + .refine((val) => isUUID(val) || UrlHelper.SHARE_URL_SLUG_REGEX.test(val)) .optional(), /** Min words to be shown in the results snippets */ diff --git a/server/routes/api/pins/schema.ts b/server/routes/api/pins/schema.ts index d3592a80f6..e659e72a41 100644 --- a/server/routes/api/pins/schema.ts +++ b/server/routes/api/pins/schema.ts @@ -1,6 +1,6 @@ import isUUID from "validator/lib/isUUID"; import { z } from "zod"; -import { SLUG_URL_REGEX } from "@shared/utils/urlHelpers"; +import { UrlHelper } from "@shared/utils/UrlHelper"; import { BaseSchema } from "../schema"; export const PinsCreateSchema = BaseSchema.extend({ @@ -9,7 +9,7 @@ export const PinsCreateSchema = BaseSchema.extend({ .string({ required_error: "required", }) - .refine((val) => isUUID(val) || SLUG_URL_REGEX.test(val), { + .refine((val) => isUUID(val) || UrlHelper.SLUG_URL_REGEX.test(val), { message: "must be uuid or url slug", }), collectionId: z.string().uuid().nullish(), diff --git a/server/routes/api/shares/schema.ts b/server/routes/api/shares/schema.ts index 8ff0210fc7..6a263f5edd 100644 --- a/server/routes/api/shares/schema.ts +++ b/server/routes/api/shares/schema.ts @@ -1,7 +1,7 @@ import isEmpty from "lodash/isEmpty"; import isUUID from "validator/lib/isUUID"; import { z } from "zod"; -import { SHARE_URL_SLUG_REGEX, SLUG_URL_REGEX } from "@shared/utils/urlHelpers"; +import { UrlHelper } from "@shared/utils/UrlHelper"; import { Share } from "@server/models"; import { BaseSchema } from "../schema"; @@ -13,7 +13,8 @@ export const SharesInfoSchema = BaseSchema.extend({ .string() .optional() .refine( - (val) => (val ? isUUID(val) || SLUG_URL_REGEX.test(val) : true), + (val) => + val ? isUUID(val) || UrlHelper.SLUG_URL_REGEX.test(val) : true, { message: "must be uuid or url slug", } @@ -52,7 +53,7 @@ export const SharesUpdateSchema = BaseSchema.extend({ published: z.boolean().optional(), urlId: z .string() - .regex(SHARE_URL_SLUG_REGEX, { + .regex(UrlHelper.SHARE_URL_SLUG_REGEX, { message: "must contain only alphanumeric and dashes", }) .nullish(), @@ -65,13 +66,13 @@ export const SharesCreateSchema = BaseSchema.extend({ body: z.object({ documentId: z .string() - .refine((val) => isUUID(val) || SLUG_URL_REGEX.test(val), { + .refine((val) => isUUID(val) || UrlHelper.SLUG_URL_REGEX.test(val), { message: "must be uuid or url slug", }), published: z.boolean().default(false), urlId: z .string() - .regex(SHARE_URL_SLUG_REGEX, { + .regex(UrlHelper.SHARE_URL_SLUG_REGEX, { message: "must contain only alphanumeric and dashes", }) .optional(), diff --git a/server/validation.ts b/server/validation.ts index 097bf1281d..eff13375c3 100644 --- a/server/validation.ts +++ b/server/validation.ts @@ -5,10 +5,10 @@ import validator from "validator"; import isIn from "validator/lib/isIn"; import isUUID from "validator/lib/isUUID"; import { CollectionPermission } from "@shared/types"; +import { UrlHelper } from "@shared/utils/UrlHelper"; import { validateColorHex } from "@shared/utils/color"; import { validateIndexCharacters } from "@shared/utils/indexCharacters"; import parseMentionUrl from "@shared/utils/parseMentionUrl"; -import { SLUG_URL_REGEX } from "@shared/utils/urlHelpers"; import { isUrl } from "@shared/utils/urls"; import { ParamRequiredError, ValidationError } from "./errors"; import { Buckets } from "./models/helpers/AttachmentHelper"; @@ -210,7 +210,7 @@ export class ValidateDocumentId { * @returns true if documentId is valid, false otherwise */ public static isValid = (documentId: string) => - isUUID(documentId) || SLUG_URL_REGEX.test(documentId); + isUUID(documentId) || UrlHelper.SLUG_URL_REGEX.test(documentId); public static message = "Must be uuid or url slug"; } diff --git a/shared/utils/UrlHelper.ts b/shared/utils/UrlHelper.ts new file mode 100644 index 0000000000..69d3cc04d2 --- /dev/null +++ b/shared/utils/UrlHelper.ts @@ -0,0 +1,10 @@ +export class UrlHelper { + public static github = "https://www.github.com/outline/outline/issues"; + public static twitter = "https://twitter.com/getoutline"; + public static contact = "https://www.getoutline.com/contact"; + public static developers = "https://www.getoutline.com/developers"; + public static changelog = "https://www.getoutline.com/changelog"; + + public static SLUG_URL_REGEX = /^(?:[0-9a-zA-Z-_~]*-)?([a-zA-Z0-9]{10,15})$/; + public static SHARE_URL_SLUG_REGEX = /^[0-9a-z-]+$/; +} diff --git a/shared/utils/urlHelpers.ts b/shared/utils/urlHelpers.ts deleted file mode 100644 index 5d97822fe8..0000000000 --- a/shared/utils/urlHelpers.ts +++ /dev/null @@ -1,53 +0,0 @@ -import env from "../env"; - -export function slackAuth( - state: string, - scopes: string[] = [ - "identity.email", - "identity.basic", - "identity.avatar", - "identity.team", - ], - clientId: string, - redirectUri = `${env.URL}/auth/slack.callback` -): string { - const baseUrl = "https://slack.com/oauth/authorize"; - const params = { - client_id: clientId, - scope: scopes ? scopes.join(" ") : "", - redirect_uri: redirectUri, - state, - }; - const urlParams = Object.keys(params) - .map((key) => `${key}=${encodeURIComponent(params[key])}`) - .join("&"); - return `${baseUrl}?${urlParams}`; -} - -export function githubUrl(): string { - return "https://www.github.com/outline"; -} - -export function githubIssuesUrl(): string { - return "https://www.github.com/outline/outline/issues"; -} - -export function twitterUrl(): string { - return "https://twitter.com/getoutline"; -} - -export function feedbackUrl(): string { - return "https://www.getoutline.com/contact"; -} - -export function developersUrl(): string { - return "https://www.getoutline.com/developers"; -} - -export function changelogUrl(): string { - return "https://www.getoutline.com/changelog"; -} - -export const SLUG_URL_REGEX = /^(?:[0-9a-zA-Z-_~]*-)?([a-zA-Z0-9]{10,15})$/; - -export const SHARE_URL_SLUG_REGEX = /^[0-9a-z-]+$/;