Compare commits

..

41 Commits

Author SHA1 Message Date
Philipinho b43de81013 cleanup 2024-07-07 16:36:14 +01:00
Philipinho 6659adc7fe v0.2.4 2024-07-07 16:31:28 +01:00
Philip Okugbe 24adff9679 Merge pull request #74 from docmost/remove-redundant-page-slug_id-index
remove redundant page slug_id index
2024-07-07 16:29:21 +01:00
Philipinho e960b8c1a9 create migration 2024-07-07 16:27:43 +01:00
Philipinho 1958067110 Revert "remove redundant slug_id index"
This reverts commit 3e519ebcd8.
2024-07-07 16:16:13 +01:00
Philipinho 3e519ebcd8 remove redundant slug_id index 2024-07-07 16:07:43 +01:00
Philip Okugbe 07cd650205 Merge pull request #73 from docmost/fix/case-insensitive-email
make emails case-insensitive
2024-07-07 15:51:14 +01:00
Philipinho 949d782a28 make emails case-insensitive 2024-07-07 15:49:43 +01:00
Philipinho 295d4325bf update tiptap hocuspocus 2024-07-07 15:36:36 +01:00
Philip Okugbe 40a40bb3c7 Merge pull request #71 from docmost/aws_env_option
Add support for AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables
2024-07-07 09:49:31 +01:00
Philipinho bc1579b022 fix condition check 2024-07-07 09:48:43 +01:00
Philip Okugbe 85b3073681 Merge pull request #57 from will2hew/will/url-setup
CORS setup and Vite devServer proxy
2024-07-07 09:42:51 +01:00
Philipinho 4af3a54649 Allow AWS SDK use AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY 2024-07-07 09:24:19 +01:00
Philip Okugbe c4c169b17a Merge pull request #64 from docmost/feat/health
Add health check
2024-07-06 01:06:17 +01:00
Philipinho a0536d852f cleanup indicators 2024-07-05 23:05:19 +01:00
Philipinho f12f93b373 increase startup db retry limit 2024-07-05 19:00:55 +01:00
Philipinho 35dcd5f254 refactor health module 2024-07-05 18:59:26 +01:00
Philipinho 9496ec9b57 prevent database error from crashing server 2024-07-05 18:59:16 +01:00
Will H 5ace7616d0 cors setup and dev server proxy 2024-07-05 16:25:00 +12:00
Will H ce6a05ab66 cr - add redis check and logging 2024-07-05 13:40:51 +12:00
Philipinho 7383673636 v0.2.3 2024-07-05 00:49:08 +01:00
Philip Okugbe 3e7b2495c5 Merge pull request #51 from docmost/private-attachments
make page attachments private
2024-07-05 00:47:51 +01:00
Philip Okugbe 0fc8edeb52 Merge pull request #55 from docmost/fix/bug-fixes
Fix: missing tree, editor font and responsive recent pages
2024-07-05 00:45:32 +01:00
Philipinho bbf865b2f6 cleanup debug log 2024-07-05 00:41:30 +01:00
Philipinho 0c622a0dc1 use sane font-weight 2024-07-05 00:33:59 +01:00
Philipinho f52cd011a4 remove unused imports 2024-07-05 00:33:12 +01:00
Philipinho a4d53468c3 fix tree state 2024-07-05 00:30:56 +01:00
Will H 66773dfaca Add health check and dev script 2024-07-05 10:10:08 +12:00
Philipinho cc93abfb7e make pages table responsive 2024-07-04 21:13:43 +01:00
Philipinho 13f26f9c31 make page attachments private 2024-07-04 16:01:35 +01:00
Philipinho a4ec2dac6c v0.2.2 2024-07-03 13:01:00 +01:00
Philip Okugbe 681d7c789c merge: fix/media-in-firefox
fix media visibility in firefox
2024-07-03 11:57:03 +01:00
Philipinho 9f583174a9 fix media visibility in firefox 2024-07-03 11:53:09 +01:00
Philip Okugbe 05633082c5 feat/fuzzy-match-menu
added fuzzy matching logic for menu search
2024-07-03 11:25:21 +01:00
Philip Okugbe 491fbad4ac feat/full-width
add full page width user preference
2024-07-03 11:24:32 +01:00
Philipinho e824aeced7 Add width option to page menu 2024-07-03 11:23:42 +01:00
Philipinho 8f056d1071 add full page width preference 2024-07-03 11:00:42 +01:00
SurajJadhav7 99cf6dab62 added fuzzy matching logic for menu search 2024-07-01 16:49:36 +05:30
Philip Okugbe d1ae117f76 v0.2.1
client: fix APP_URL usage
2024-07-01 10:25:36 +01:00
Philipinho eea4e62c2e v0.2.1 2024-07-01 10:09:28 +01:00
Philipinho d429384d22 client: fix APP_URL 2024-07-01 10:08:12 +01:00
43 changed files with 650 additions and 189 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "client", "name": "client",
"private": true, "private": true,
"version": "0.2.0", "version": "0.2.4",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "tsc && vite build", "build": "tsc && vite build",
@@ -1,4 +1,11 @@
import { Text, Group, UnstyledButton, Badge, Table } from "@mantine/core"; import {
Text,
Group,
UnstyledButton,
Badge,
Table,
ScrollArea,
} from "@mantine/core";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import PageListSkeleton from "@/components/ui/page-list-skeleton.tsx"; import PageListSkeleton from "@/components/ui/page-list-skeleton.tsx";
import { buildPageUrl } from "@/features/page/page.utils.ts"; import { buildPageUrl } from "@/features/page/page.utils.ts";
@@ -22,46 +29,48 @@ export default function RecentChanges({ spaceId }: Props) {
} }
return pages && pages.items.length > 0 ? ( return pages && pages.items.length > 0 ? (
<Table highlightOnHover verticalSpacing="sm"> <ScrollArea>
<Table.Tbody> <Table highlightOnHover verticalSpacing="sm">
{pages.items.map((page) => ( <Table.Tbody>
<Table.Tr key={page.id}> {pages.items.map((page) => (
<Table.Td> <Table.Tr key={page.id}>
<UnstyledButton
component={Link}
to={buildPageUrl(page?.space.slug, page.slugId, page.title)}
>
<Group wrap="nowrap">
{page.icon || <IconFileDescription size={18} />}
<Text fw={500} size="md" lineClamp={1}>
{page.title || "Untitled"}
</Text>
</Group>
</UnstyledButton>
</Table.Td>
{!spaceId && (
<Table.Td> <Table.Td>
<Badge <UnstyledButton
color="blue"
variant="light"
component={Link} component={Link}
to={getSpaceUrl(page?.space.slug)} to={buildPageUrl(page?.space.slug, page.slugId, page.title)}
style={{ cursor: "pointer" }}
> >
{page?.space.name} <Group wrap="nowrap">
</Badge> {page.icon || <IconFileDescription size={18} />}
<Text fw={500} size="md" lineClamp={1}>
{page.title || "Untitled"}
</Text>
</Group>
</UnstyledButton>
</Table.Td> </Table.Td>
)} {!spaceId && (
<Table.Td> <Table.Td>
<Text c="dimmed" size="xs" fw={500}> <Badge
{formattedDate(page.updatedAt)} color="blue"
</Text> variant="light"
</Table.Td> component={Link}
</Table.Tr> to={getSpaceUrl(page?.space.slug)}
))} style={{ cursor: "pointer" }}
</Table.Tbody> >
</Table> {page?.space.name}
</Badge>
</Table.Td>
)}
<Table.Td>
<Text c="dimmed" size="xs" fw={500}>
{formattedDate(page.updatedAt)}
</Text>
</Table.Td>
</Table.Tr>
))}
</Table.Tbody>
</Table>
</ScrollArea>
) : ( ) : (
<Text size="md" ta="center"> <Text size="md" ta="center">
No pages yet No pages yet
@@ -1,5 +1,6 @@
import { Avatar, Group, Menu, rem, UnstyledButton, Text } from "@mantine/core"; import { Group, Menu, UnstyledButton, Text } from "@mantine/core";
import { import {
IconBrush,
IconChevronDown, IconChevronDown,
IconLogout, IconLogout,
IconSettings, IconSettings,
@@ -38,10 +39,7 @@ export default function TopMenu() {
<Text fw={500} size="sm" lh={1} mr={3}> <Text fw={500} size="sm" lh={1} mr={3}>
{workspace.name} {workspace.name}
</Text> </Text>
<IconChevronDown <IconChevronDown size={16} />
style={{ width: rem(12), height: rem(12) }}
stroke={1.5}
/>
</Group> </Group>
</UnstyledButton> </UnstyledButton>
</Menu.Target> </Menu.Target>
@@ -51,12 +49,7 @@ export default function TopMenu() {
<Menu.Item <Menu.Item
component={Link} component={Link}
to={APP_ROUTE.SETTINGS.WORKSPACE.GENERAL} to={APP_ROUTE.SETTINGS.WORKSPACE.GENERAL}
leftSection={ leftSection={<IconSettings size={16} />}
<IconSettings
style={{ width: rem(16), height: rem(16) }}
stroke={1.5}
/>
}
> >
Workspace settings Workspace settings
</Menu.Item> </Menu.Item>
@@ -64,12 +57,7 @@ export default function TopMenu() {
<Menu.Item <Menu.Item
component={Link} component={Link}
to={APP_ROUTE.SETTINGS.WORKSPACE.MEMBERS} to={APP_ROUTE.SETTINGS.WORKSPACE.MEMBERS}
leftSection={ leftSection={<IconUsers size={16} />}
<IconUsers
style={{ width: rem(16), height: rem(16) }}
stroke={1.5}
/>
}
> >
Manage members Manage members
</Menu.Item> </Menu.Item>
@@ -98,27 +86,22 @@ export default function TopMenu() {
<Menu.Item <Menu.Item
component={Link} component={Link}
to={APP_ROUTE.SETTINGS.ACCOUNT.PROFILE} to={APP_ROUTE.SETTINGS.ACCOUNT.PROFILE}
leftSection={ leftSection={<IconUserCircle size={16} />}
<IconUserCircle
style={{ width: rem(16), height: rem(16) }}
stroke={1.5}
/>
}
> >
My profile My profile
</Menu.Item> </Menu.Item>
<Menu.Item
component={Link}
to={APP_ROUTE.SETTINGS.ACCOUNT.PREFERENCES}
leftSection={<IconBrush size={16} />}
>
My preferences
</Menu.Item>
<Menu.Divider /> <Menu.Divider />
<Menu.Item <Menu.Item onClick={logout} leftSection={<IconLogout size={16} />}>
onClick={logout}
leftSection={
<IconLogout
style={{ width: rem(16), height: rem(16) }}
stroke={1.5}
/>
}
>
Logout Logout
</Menu.Item> </Menu.Item>
</Menu.Dropdown> </Menu.Dropdown>
@@ -4,11 +4,7 @@ import { useNavigate } from "react-router-dom";
import { useAtom } from "jotai"; import { useAtom } from "jotai";
import { authTokensAtom } from "@/features/auth/atoms/auth-tokens-atom"; import { authTokensAtom } from "@/features/auth/atoms/auth-tokens-atom";
import { currentUserAtom } from "@/features/user/atoms/current-user-atom"; import { currentUserAtom } from "@/features/user/atoms/current-user-atom";
import { import { ILogin, ISetupWorkspace } from "@/features/auth/types/auth.types";
ILogin,
IRegister,
ISetupWorkspace,
} from "@/features/auth/types/auth.types";
import { notifications } from "@mantine/notifications"; import { notifications } from "@mantine/notifications";
import { IAcceptInvite } from "@/features/workspace/types/workspace.types.ts"; import { IAcceptInvite } from "@/features/workspace/types/workspace.types.ts";
import { acceptInvitation } from "@/features/workspace/services/workspace-service.ts"; import { acceptInvitation } from "@/features/workspace/services/workspace-service.ts";
@@ -49,7 +45,6 @@ export default function useAuth() {
const res = await acceptInvitation(data); const res = await acceptInvitation(data);
setIsLoading(false); setIsLoading(false);
console.log(res);
setAuthToken(res.tokens); setAuthToken(res.tokens);
navigate(APP_ROUTE.HOME); navigate(APP_ROUTE.HOME);
@@ -2,32 +2,28 @@ import { NodeViewProps, NodeViewWrapper } from "@tiptap/react";
import { useMemo } from "react"; import { useMemo } from "react";
import { Image } from "@mantine/core"; import { Image } from "@mantine/core";
import { getFileUrl } from "@/lib/config.ts"; import { getFileUrl } from "@/lib/config.ts";
import clsx from "clsx";
export default function ImageView(props: NodeViewProps) { export default function ImageView(props: NodeViewProps) {
const { node, selected } = props; const { node, selected } = props;
const { src, width, align, title } = node.attrs; const { src, width, align, title } = node.attrs;
const flexJustifyContent = useMemo(() => { const alignClass = useMemo(() => {
if (align === "center") return "center"; if (align === "left") return "alignLeft";
if (align === "right") return "flex-end"; if (align === "right") return "alignRight";
return "flex-start"; if (align === "center") return "alignCenter";
return "alignCenter";
}, [align]); }, [align]);
return ( return (
<NodeViewWrapper <NodeViewWrapper>
style={{
position: "relative",
display: "flex",
justifyContent: flexJustifyContent,
}}
>
<Image <Image
radius="md" radius="md"
fit="contain" fit="contain"
w={width} w={width}
src={getFileUrl(src)} src={getFileUrl(src)}
alt={title} alt={title}
className={selected ? "ProseMirror-selectednode" : ""} className={clsx(selected ? "ProseMirror-selectednode" : "", alignClass)}
/> />
</NodeViewWrapper> </NodeViewWrapper>
); );
@@ -264,10 +264,20 @@ export const getSuggestionItems = ({
const search = query.toLowerCase(); const search = query.toLowerCase();
const filteredGroups: SlashMenuGroupedItemsType = {}; const filteredGroups: SlashMenuGroupedItemsType = {};
const fuzzyMatch = (query, target) => {
let queryIndex = 0;
target = target.toLowerCase();
for (let char of target) {
if (query[queryIndex] === char) queryIndex++;
if (queryIndex === query.length) return true;
}
return false;
};
for (const [group, items] of Object.entries(CommandGroups)) { for (const [group, items] of Object.entries(CommandGroups)) {
const filteredItems = items.filter((item) => { const filteredItems = items.filter((item) => {
return ( return (
item.title.toLowerCase().includes(search) || fuzzyMatch(search, item.title) ||
item.description.toLowerCase().includes(search) || item.description.toLowerCase().includes(search) ||
(item.searchTerms && (item.searchTerms &&
item.searchTerms.some((term: string) => term.includes(search))) item.searchTerms.some((term: string) => term.includes(search)))
@@ -1,31 +1,27 @@
import { NodeViewProps, NodeViewWrapper } from "@tiptap/react"; import { NodeViewProps, NodeViewWrapper } from "@tiptap/react";
import { useMemo } from "react"; import { useMemo } from "react";
import { getFileUrl } from "@/lib/config.ts"; import { getFileUrl } from "@/lib/config.ts";
import clsx from "clsx";
export default function VideoView(props: NodeViewProps) { export default function VideoView(props: NodeViewProps) {
const { node, selected } = props; const { node, selected } = props;
const { src, width, align } = node.attrs; const { src, width, align } = node.attrs;
const flexJustifyContent = useMemo(() => { const alignClass = useMemo(() => {
if (align === "center") return "center"; if (align === "left") return "alignLeft";
if (align === "right") return "flex-end"; if (align === "right") return "alignRight";
return "flex-start"; if (align === "center") return "alignCenter";
return "alignCenter";
}, [align]); }, [align]);
return ( return (
<NodeViewWrapper <NodeViewWrapper>
style={{
position: "relative",
display: "flex",
justifyContent: flexJustifyContent,
}}
>
<video <video
preload="metadata" preload="metadata"
width={width} width={width}
controls controls
src={getFileUrl(src)} src={getFileUrl(src)}
className={selected ? "ProseMirror-selectednode" : ""} className={clsx(selected ? "ProseMirror-selectednode" : "", alignClass)}
/> />
</NodeViewWrapper> </NodeViewWrapper>
); );
@@ -2,6 +2,9 @@ import classes from "@/features/editor/styles/editor.module.css";
import React from "react"; import React from "react";
import { TitleEditor } from "@/features/editor/title-editor"; import { TitleEditor } from "@/features/editor/title-editor";
import PageEditor from "@/features/editor/page-editor"; import PageEditor from "@/features/editor/page-editor";
import { Container } from "@mantine/core";
import { useAtom } from "jotai/index";
import { userAtom } from "@/features/user/atoms/current-user-atom.ts";
const MemoizedTitleEditor = React.memo(TitleEditor); const MemoizedTitleEditor = React.memo(TitleEditor);
const MemoizedPageEditor = React.memo(PageEditor); const MemoizedPageEditor = React.memo(PageEditor);
@@ -21,8 +24,16 @@ export function FullEditor({
spaceSlug, spaceSlug,
editable, editable,
}: FullEditorProps) { }: FullEditorProps) {
const [user] = useAtom(userAtom);
const fullPageWidth = user.settings?.preferences?.fullPageWidth;
return ( return (
<div className={classes.editor}> <Container
fluid={fullPageWidth}
{...(fullPageWidth && { mx: 80 })}
size={850}
className={classes.editor}
>
<MemoizedTitleEditor <MemoizedTitleEditor
pageId={pageId} pageId={pageId}
slugId={slugId} slugId={slugId}
@@ -31,6 +42,6 @@ export function FullEditor({
editable={editable} editable={editable}
/> />
<MemoizedPageEditor pageId={pageId} editable={editable} /> <MemoizedPageEditor pageId={pageId} editable={editable} />
</div> </Container>
); );
} }
@@ -9,7 +9,7 @@
); );
font-size: var(--mantine-font-size-md); font-size: var(--mantine-font-size-md);
line-height: var(--mantine-line-height-xl); line-height: var(--mantine-line-height-xl);
font-weight: 415; font-weight: 400;
width: 100%; width: 100%;
> * + * { > * + * {
@@ -125,6 +125,21 @@
cursor: ew-resize; cursor: ew-resize;
cursor: col-resize; cursor: col-resize;
} }
.alignLeft {
margin-left: 0;
margin-right: auto;
}
.alignRight {
margin-right: 0;
margin-left: auto;
}
.alignCenter {
margin-left: auto;
margin-right: auto;
}
} }
.ProseMirror-icon { .ProseMirror-icon {
@@ -1,5 +1,4 @@
.editor { .editor {
max-width: 800px;
height: 100%; height: 100%;
padding: 8px 20px; padding: 8px 20px;
margin: 64px auto; margin: 64px auto;
@@ -4,7 +4,7 @@
iframe { iframe {
display: block; display: block;
outline: 0px solid transparent; outline: 0 solid transparent;
border-radius: var(--mantine-radius-md); border-radius: var(--mantine-radius-md);
width: 100%; width: 100%;
} }
@@ -1,5 +1,6 @@
import { ActionIcon, Menu, Tooltip } from "@mantine/core"; import { ActionIcon, Group, Menu, Tooltip } from "@mantine/core";
import { import {
IconArrowsHorizontal,
IconDots, IconDots,
IconHistory, IconHistory,
IconLink, IconLink,
@@ -19,7 +20,7 @@ import { getAppUrl } from "@/lib/config.ts";
import { extractPageSlugId } from "@/lib"; import { extractPageSlugId } from "@/lib";
import { treeApiAtom } from "@/features/page/tree/atoms/tree-api-atom.ts"; import { treeApiAtom } from "@/features/page/tree/atoms/tree-api-atom.ts";
import { useDeletePageModal } from "@/features/page/hooks/use-delete-page-modal.tsx"; import { useDeletePageModal } from "@/features/page/hooks/use-delete-page-modal.tsx";
import { boolean } from "zod"; import { PageWidthToggle } from "@/features/user/components/page-width-pref.tsx";
interface PageHeaderMenuProps { interface PageHeaderMenuProps {
readOnly?: boolean; readOnly?: boolean;
@@ -96,6 +97,13 @@ function PageActionMenu({ readOnly }: PageActionMenuProps) {
Copy link Copy link
</Menu.Item> </Menu.Item>
<Menu.Divider /> <Menu.Divider />
<Menu.Item leftSection={<IconArrowsHorizontal size={16} stroke={2} />}>
<Group wrap="nowrap">
<PageWidthToggle label="Full width" />
</Group>
</Menu.Item>
<Menu.Item <Menu.Item
leftSection={<IconHistory size={16} stroke={2} />} leftSection={<IconHistory size={16} stroke={2} />}
onClick={openHistoryModal} onClick={openHistoryModal}
@@ -88,6 +88,7 @@ export default function SpaceTree({ spaceId, readOnly }: SpaceTreeProps) {
if (pagesData?.pages && !hasNextPage) { if (pagesData?.pages && !hasNextPage) {
const allItems = pagesData.pages.flatMap((page) => page.items); const allItems = pagesData.pages.flatMap((page) => page.items);
const treeData = buildTree(allItems); const treeData = buildTree(allItems);
if (data.length < 1 || data?.[0].spaceId !== spaceId) { if (data.length < 1 || data?.[0].spaceId !== spaceId) {
//Thoughts //Thoughts
// don't reset if there is data in state // don't reset if there is data in state
@@ -106,7 +107,7 @@ export default function SpaceTree({ spaceId, readOnly }: SpaceTreeProps) {
const fetchData = async () => { const fetchData = async () => {
if (isDataLoaded.current && currentPage) { if (isDataLoaded.current && currentPage) {
// check if pageId node is present in the tree // check if pageId node is present in the tree
const node = dfs(treeApiRef.current.root, currentPage.id); const node = dfs(treeApiRef.current?.root, currentPage.id);
if (node) { if (node) {
// if node is found, no need to traverse its ancestors // if node is found, no need to traverse its ancestors
return; return;
@@ -52,6 +52,8 @@ export function useTreeMutation<T>(spaceId: string) {
slugId: createdPage.slugId, slugId: createdPage.slugId,
name: "", name: "",
position: createdPage.position, position: createdPage.position,
spaceId: createdPage.spaceId,
parentPageId: createdPage.parentPageId,
children: [], children: [],
} as any; } as any;
@@ -1,17 +1,10 @@
import { Group, Center, Text } from "@mantine/core"; import { Group, Center, Text } from "@mantine/core";
import { Spotlight } from "@mantine/spotlight"; import { Spotlight } from "@mantine/spotlight";
import { import { IconFileDescription, IconSearch } from "@tabler/icons-react";
IconFileDescription,
IconHome,
IconSearch,
IconSettings,
} from "@tabler/icons-react";
import React, { useState } from "react"; import React, { useState } from "react";
import { useNavigate, useParams } from "react-router-dom"; import { useNavigate, useParams } from "react-router-dom";
import { useDebouncedValue } from "@mantine/hooks"; import { useDebouncedValue } from "@mantine/hooks";
import { usePageSearchQuery } from "@/features/search/queries/search-query"; import { usePageSearchQuery } from "@/features/search/queries/search-query";
import { useGetSpaceBySlugQuery } from "@/features/space/queries/space-query.ts";
import { useRecentChangesQuery } from "@/features/page/queries/page-query.ts";
import { buildPageUrl } from "@/features/page/page.utils.ts"; import { buildPageUrl } from "@/features/page/page.utils.ts";
interface SearchSpotlightProps { interface SearchSpotlightProps {
@@ -1,5 +1,16 @@
import { atomWithStorage } from "jotai/utils"; import { atomWithStorage } from "jotai/utils";
import { ICurrentUser } from "@/features/user/types/user.types"; import { ICurrentUser } from "@/features/user/types/user.types";
import { focusAtom } from "jotai-optics";
export const currentUserAtom = atomWithStorage<ICurrentUser | null>("currentUser", null); export const currentUserAtom = atomWithStorage<ICurrentUser | null>(
"currentUser",
null,
);
export const userAtom = focusAtom(currentUserAtom, (optic) =>
optic.prop("user"),
);
export const workspaceAtom = focusAtom(currentUserAtom, (optic) =>
optic.prop("workspace"),
);
@@ -0,0 +1,49 @@
import { Group, Text, Switch, MantineSize } from "@mantine/core";
import { useAtom } from "jotai/index";
import { userAtom } from "@/features/user/atoms/current-user-atom.ts";
import { updateUser } from "@/features/user/services/user-service.ts";
import React, { useState } from "react";
export default function PageWidthPref() {
return (
<Group justify="space-between" wrap="nowrap" gap="xl">
<div>
<Text size="md">Full page width</Text>
<Text size="sm" c="dimmed">
Choose your preferred page width.
</Text>
</div>
<PageWidthToggle />
</Group>
);
}
interface PageWidthToggleProps {
size?: MantineSize;
label?: string;
}
export function PageWidthToggle({ size, label }: PageWidthToggleProps) {
const [user, setUser] = useAtom(userAtom);
const [checked, setChecked] = useState(
user.settings?.preferences?.fullPageWidth,
);
const handleChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
const value = event.currentTarget.checked;
const updatedUser = await updateUser({ fullPageWidth: value });
setChecked(value);
setUser(updatedUser);
};
return (
<Switch
size={size}
label={label}
labelPosition="left"
defaultChecked={checked}
onChange={handleChange}
aria-label="Toggle full page width"
/>
);
}
@@ -7,7 +7,7 @@ export interface IUser {
emailVerifiedAt: Date; emailVerifiedAt: Date;
avatarUrl: string; avatarUrl: string;
timezone: string; timezone: string;
settings: any; settings: IUserSettings;
invitedById: string; invitedById: string;
lastLoginAt: string; lastLoginAt: string;
lastActiveAt: Date; lastActiveAt: Date;
@@ -17,9 +17,16 @@ export interface IUser {
workspaceId: string; workspaceId: string;
deactivatedAt: Date; deactivatedAt: Date;
deletedAt: Date; deletedAt: Date;
fullPageWidth: boolean; // used for update
} }
export interface ICurrentUser { export interface ICurrentUser {
user: IUser; user: IUser;
workspace: IWorkspace; workspace: IWorkspace;
} }
export interface IUserSettings {
preferences: {
fullPageWidth: boolean;
};
}
+4 -4
View File
@@ -1,10 +1,10 @@
import axios, { AxiosInstance } from "axios"; import axios, { AxiosInstance } from "axios";
import Cookies from "js-cookie"; import Cookies from "js-cookie";
import Routes from "@/lib/app-route.ts"; import Routes from "@/lib/app-route.ts";
import { getBackendUrl } from "@/lib/config.ts";
const api: AxiosInstance = axios.create({ const api: AxiosInstance = axios.create({
baseURL: getBackendUrl(), baseURL: "/api",
withCredentials: true,
}); });
api.interceptors.request.use( api.interceptors.request.use(
@@ -26,7 +26,7 @@ api.interceptors.request.use(
}, },
(error) => { (error) => {
return Promise.reject(error); return Promise.reject(error);
}, }
); );
api.interceptors.response.use( api.interceptors.response.use(
@@ -67,7 +67,7 @@ api.interceptors.response.use(
} }
} }
return Promise.reject(error); return Promise.reject(error);
}, }
); );
function redirectToLogin() { function redirectToLogin() {
+3 -5
View File
@@ -7,13 +7,11 @@ declare global {
export function getAppUrl(): string { export function getAppUrl(): string {
let appUrl = window.CONFIG?.APP_URL || process.env.APP_URL; let appUrl = window.CONFIG?.APP_URL || process.env.APP_URL;
if (!appUrl) { if (import.meta.env.DEV) {
appUrl = import.meta.env.DEV return appUrl || "http://localhost:3000";
? "http://localhost:3000"
: window.location.protocol + "//" + window.location.host;
} }
return appUrl; return `${window.location.protocol}//${window.location.host}`;
} }
export function getBackendUrl(): string { export function getBackendUrl(): string {
@@ -1,11 +1,15 @@
import SettingsTitle from "@/components/settings/settings-title.tsx"; import SettingsTitle from "@/components/settings/settings-title.tsx";
import AccountTheme from "@/features/user/components/account-theme.tsx"; import AccountTheme from "@/features/user/components/account-theme.tsx";
import PageWidthPref from "@/features/user/components/page-width-pref.tsx";
import { Divider } from "@mantine/core";
export default function AccountPreferences() { export default function AccountPreferences() {
return ( return (
<> <>
<SettingsTitle title="Preferences" /> <SettingsTitle title="Preferences" />
<AccountTheme /> <AccountTheme />
<Divider my={"md"} />
<PageWidthPref />
</> </>
); );
} }
+8
View File
@@ -19,5 +19,13 @@ export default defineConfig(({ mode }) => {
"@": "/src", "@": "/src",
}, },
}, },
server: {
proxy: {
"/api": {
target: APP_URL,
changeOrigin: true,
},
},
},
}; };
}); });
+3 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "server", "name": "server",
"version": "0.2.0", "version": "0.2.4",
"description": "", "description": "",
"author": "", "author": "",
"private": true, "private": true,
@@ -31,6 +31,7 @@
"@aws-sdk/client-s3": "^3.600.0", "@aws-sdk/client-s3": "^3.600.0",
"@aws-sdk/s3-request-presigner": "^3.600.0", "@aws-sdk/s3-request-presigner": "^3.600.0",
"@casl/ability": "^6.7.1", "@casl/ability": "^6.7.1",
"@fastify/cookie": "^9.3.1",
"@fastify/multipart": "^8.3.0", "@fastify/multipart": "^8.3.0",
"@fastify/static": "^7.0.4", "@fastify/static": "^7.0.4",
"@nestjs/bullmq": "^10.1.1", "@nestjs/bullmq": "^10.1.1",
@@ -43,6 +44,7 @@
"@nestjs/passport": "^10.0.3", "@nestjs/passport": "^10.0.3",
"@nestjs/platform-fastify": "^10.3.9", "@nestjs/platform-fastify": "^10.3.9",
"@nestjs/platform-socket.io": "^10.3.9", "@nestjs/platform-socket.io": "^10.3.9",
"@nestjs/terminus": "^10.2.3",
"@nestjs/websockets": "^10.3.9", "@nestjs/websockets": "^10.3.9",
"@react-email/components": "0.0.19", "@react-email/components": "0.0.19",
"@react-email/render": "^0.0.15", "@react-email/render": "^0.0.15",
+2
View File
@@ -11,6 +11,7 @@ import { MailModule } from './integrations/mail/mail.module';
import { QueueModule } from './integrations/queue/queue.module'; import { QueueModule } from './integrations/queue/queue.module';
import { StaticModule } from './integrations/static/static.module'; import { StaticModule } from './integrations/static/static.module';
import { EventEmitterModule } from '@nestjs/event-emitter'; import { EventEmitterModule } from '@nestjs/event-emitter';
import { HealthModule } from './integrations/health/health.module';
@Module({ @Module({
imports: [ imports: [
@@ -21,6 +22,7 @@ import { EventEmitterModule } from '@nestjs/event-emitter';
WsModule, WsModule,
QueueModule, QueueModule,
StaticModule, StaticModule,
HealthModule,
StorageModule.forRootAsync({ StorageModule.forRootAsync({
imports: [EnvironmentModule], imports: [EnvironmentModule],
}), }),
@@ -16,7 +16,15 @@ export class TransformHttpResponseInterceptor<T>
intercept( intercept(
context: ExecutionContext, context: ExecutionContext,
next: CallHandler<T>, next: CallHandler<T>,
): Observable<Response<T>> { ): Observable<Response<T> | any> {
const request = context.switchToHttp().getRequest();
const path = request.url;
// Skip interceptor for the /api/health path
if (path === '/api/health') {
return next.handle();
}
return next.handle().pipe( return next.handle().pipe(
map((data) => { map((data) => {
const status = context.switchToHttp().getResponse().statusCode; const status = context.switchToHttp().getResponse().statusCode;
@@ -45,7 +45,6 @@ import {
import WorkspaceAbilityFactory from '../casl/abilities/workspace-ability.factory'; import WorkspaceAbilityFactory from '../casl/abilities/workspace-ability.factory';
import { PageRepo } from '@docmost/db/repos/page/page.repo'; import { PageRepo } from '@docmost/db/repos/page/page.repo';
import { AttachmentRepo } from '@docmost/db/repos/attachment/attachment.repo'; import { AttachmentRepo } from '@docmost/db/repos/attachment/attachment.repo';
import { Public } from '../../common/decorators/public.decorator';
import { validate as isValidUUID } from 'uuid'; import { validate as isValidUUID } from 'uuid';
@Controller() @Controller()
@@ -129,12 +128,11 @@ export class AttachmentController {
} }
} }
@Public()
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@Get('/files/:fileId/:fileName') @Get('/files/:fileId/:fileName')
async getFile( async getFile(
@Res() res: FastifyReply, @Res() res: FastifyReply,
//@AuthUser() user: User, @AuthUser() user: User,
@AuthWorkspace() workspace: Workspace, @AuthWorkspace() workspace: Workspace,
@Param('fileId') fileId: string, @Param('fileId') fileId: string,
@Param('fileName') fileName?: string, @Param('fileName') fileName?: string,
@@ -144,18 +142,29 @@ export class AttachmentController {
} }
const attachment = await this.attachmentRepo.findById(fileId); const attachment = await this.attachmentRepo.findById(fileId);
if (attachment.workspaceId !== workspace.id) { if (
!attachment ||
attachment.workspaceId !== workspace.id ||
!attachment.pageId ||
!attachment.spaceId
) {
throw new NotFoundException(); throw new NotFoundException();
} }
if (!attachment || !attachment.pageId) { const spaceAbility = await this.spaceAbility.createForUser(
throw new NotFoundException('File record not found'); user,
attachment.spaceId,
);
if (spaceAbility.cannot(SpaceCaslAction.Read, SpaceCaslSubject.Page)) {
throw new ForbiddenException();
} }
try { try {
const fileStream = await this.storageService.read(attachment.filePath); const fileStream = await this.storageService.read(attachment.filePath);
res.headers({ res.headers({
'Content-Type': getMimeType(attachment.filePath), 'Content-Type': getMimeType(attachment.filePath),
'Cache-Control': 'public, max-age=3600',
}); });
return res.send(fileStream); return res.send(fileStream);
} catch (err) { } catch (err) {
@@ -268,6 +277,7 @@ export class AttachmentController {
const fileStream = await this.storageService.read(filePath); const fileStream = await this.storageService.read(filePath);
res.headers({ res.headers({
'Content-Type': getMimeType(filePath), 'Content-Type': getMimeType(filePath),
'Cache-Control': 'public, max-age=86400',
}); });
return res.send(fileStream); return res.send(fileStream);
} catch (err) { } catch (err) {
@@ -4,11 +4,12 @@ import {
UnauthorizedException, UnauthorizedException,
} from '@nestjs/common'; } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport'; import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt'; import { Strategy } from 'passport-jwt';
import { EnvironmentService } from '../../../integrations/environment/environment.service'; import { EnvironmentService } from '../../../integrations/environment/environment.service';
import { JwtPayload, JwtType } from '../dto/jwt-payload'; import { JwtPayload, JwtType } from '../dto/jwt-payload';
import { WorkspaceRepo } from '@docmost/db/repos/workspace/workspace.repo'; import { WorkspaceRepo } from '@docmost/db/repos/workspace/workspace.repo';
import { UserRepo } from '@docmost/db/repos/user/user.repo'; import { UserRepo } from '@docmost/db/repos/user/user.repo';
import { FastifyRequest } from 'fastify';
@Injectable() @Injectable()
export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') { export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
@@ -18,7 +19,15 @@ export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
private readonly environmentService: EnvironmentService, private readonly environmentService: EnvironmentService,
) { ) {
super({ super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), jwtFromRequest: (req: FastifyRequest) => {
let accessToken = null;
try {
accessToken = JSON.parse(req.cookies?.authTokens)?.accessToken;
} catch {}
return accessToken || this.extractTokenFromHeader(req);
},
ignoreExpiration: false, ignoreExpiration: false,
secretOrKey: environmentService.getAppSecret(), secretOrKey: environmentService.getAppSecret(),
passReqToCallback: true, passReqToCallback: true,
@@ -50,4 +59,9 @@ export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
return { user, workspace }; return { user, workspace };
} }
private extractTokenFromHeader(request: FastifyRequest): string | undefined {
const [type, token] = request.headers.authorization?.split(' ') ?? [];
return type === 'Bearer' ? token : undefined;
}
} }
+4 -1
View File
@@ -34,7 +34,10 @@ export class CoreModule implements NestModule {
configure(consumer: MiddlewareConsumer) { configure(consumer: MiddlewareConsumer) {
consumer consumer
.apply(DomainMiddleware) .apply(DomainMiddleware)
.exclude({ path: 'auth/setup', method: RequestMethod.POST }) .exclude(
{ path: 'auth/setup', method: RequestMethod.POST },
{ path: 'health', method: RequestMethod.GET },
)
.forRoutes('*'); .forRoutes('*');
} }
} }
@@ -1,6 +1,6 @@
import { OmitType, PartialType } from '@nestjs/mapped-types'; import { OmitType, PartialType } from '@nestjs/mapped-types';
import { CreateUserDto } from '../../auth/dto/create-user.dto'; import { CreateUserDto } from '../../auth/dto/create-user.dto';
import { IsOptional, IsString } from 'class-validator'; import { IsBoolean, IsOptional, IsString } from 'class-validator';
export class UpdateUserDto extends PartialType( export class UpdateUserDto extends PartialType(
OmitType(CreateUserDto, ['password'] as const), OmitType(CreateUserDto, ['password'] as const),
@@ -8,4 +8,8 @@ export class UpdateUserDto extends PartialType(
@IsOptional() @IsOptional()
@IsString() @IsString()
avatarUrl: string; avatarUrl: string;
@IsOptional()
@IsBoolean()
fullPageWidth: boolean;
} }
+17
View File
@@ -20,10 +20,19 @@ export class UserService {
workspaceId: string, workspaceId: string,
) { ) {
const user = await this.userRepo.findById(userId, workspaceId); const user = await this.userRepo.findById(userId, workspaceId);
if (!user) { if (!user) {
throw new NotFoundException('User not found'); throw new NotFoundException('User not found');
} }
// preference update
if (typeof updateUserDto.fullPageWidth !== 'undefined') {
return this.updateUserPageWidthPreference(
userId,
updateUserDto.fullPageWidth,
);
}
if (updateUserDto.name) { if (updateUserDto.name) {
user.name = updateUserDto.name; user.name = updateUserDto.name;
} }
@@ -42,4 +51,12 @@ export class UserService {
await this.userRepo.updateUser(updateUserDto, userId, workspaceId); await this.userRepo.updateUser(updateUserDto, userId, workspaceId);
return user; return user;
} }
async updateUserPageWidthPreference(userId: string, fullPageWidth: boolean) {
return this.userRepo.updatePreference(
userId,
'fullPageWidth',
fullPageWidth,
);
}
} }
+3 -1
View File
@@ -36,6 +36,8 @@ types.setTypeParser(types.builtins.INT8, (val) => Number(val));
dialect: new PostgresDialect({ dialect: new PostgresDialect({
pool: new Pool({ pool: new Pool({
connectionString: environmentService.getDatabaseURL(), connectionString: environmentService.getDatabaseURL(),
}).on('error', (err) => {
console.error('Database error:', err.message);
}), }),
}), }),
plugins: [new CamelCasePlugin()], plugins: [new CamelCasePlugin()],
@@ -102,7 +104,7 @@ export class DatabaseModule implements OnModuleDestroy, OnApplicationBootstrap {
} }
async establishConnection() { async establishConnection() {
const retryAttempts = 10; const retryAttempts = 15;
const retryDelay = 3000; const retryDelay = 3000;
this.logger.log('Establishing database connection'); this.logger.log('Establishing database connection');
@@ -0,0 +1,7 @@
import { type Kysely } from 'kysely';
export async function up(db: Kysely<any>): Promise<void> {
await db.schema.dropIndex('pages_slug_id_idx').ifExists().execute();
}
export async function down(db: Kysely<any>): Promise<void> {}
@@ -6,15 +6,12 @@ import { hashPassword } from '../../../common/helpers';
import { dbOrTx } from '@docmost/db/utils'; import { dbOrTx } from '@docmost/db/utils';
import { import {
InsertableUser, InsertableUser,
Space,
UpdatableUser, UpdatableUser,
User, User,
} from '@docmost/db/types/entity.types'; } from '@docmost/db/types/entity.types';
import { PaginationOptions } from '../../pagination/pagination-options'; import { PaginationOptions } from '../../pagination/pagination-options';
import { import { executeWithPagination } from '@docmost/db/pagination/pagination';
executeWithPagination, import { sql } from 'kysely';
PaginationResult,
} from '@docmost/db/pagination/pagination';
@Injectable() @Injectable()
export class UserRepo { export class UserRepo {
@@ -65,7 +62,7 @@ export class UserRepo {
.selectFrom('users') .selectFrom('users')
.select(this.baseFields) .select(this.baseFields)
.$if(includePassword, (qb) => qb.select('password')) .$if(includePassword, (qb) => qb.select('password'))
.where('email', '=', email) .where(sql`LOWER(email)`, '=', sql`LOWER(${email})`)
.where('workspaceId', '=', workspaceId) .where('workspaceId', '=', workspaceId)
.executeTakeFirst(); .executeTakeFirst();
} }
@@ -157,6 +154,24 @@ export class UserRepo {
return result; return result;
} }
async updatePreference(
userId: string,
prefKey: string,
prefValue: string | boolean,
) {
return await this.db
.updateTable('users')
.set({
settings: sql`COALESCE(settings, '{}'::jsonb)
|| jsonb_build_object('preferences', COALESCE(settings->'preferences', '{}'::jsonb)
|| jsonb_build_object('${sql.raw(prefKey)}', ${sql.lit(prefValue)}))`,
updatedAt: new Date(),
})
.where('id', '=', userId)
.returning(this.baseFields)
.executeTakeFirst();
}
/* /*
async getSpaceIds( async getSpaceIds(
workspaceId: string, workspaceId: string,
@@ -0,0 +1,22 @@
import { Controller, Get } from '@nestjs/common';
import { HealthCheck, HealthCheckService } from '@nestjs/terminus';
import { PostgresHealthIndicator } from './postgres.health';
import { RedisHealthIndicator } from './redis.health';
@Controller('health')
export class HealthController {
constructor(
private health: HealthCheckService,
private postgres: PostgresHealthIndicator,
private redis: RedisHealthIndicator,
) {}
@Get()
@HealthCheck()
async check() {
return this.health.check([
() => this.postgres.pingCheck('database'),
() => this.redis.pingCheck('redis'),
]);
}
}
@@ -0,0 +1,13 @@
import { Global, Module } from '@nestjs/common';
import { HealthController } from './health.controller';
import { TerminusModule } from '@nestjs/terminus';
import { PostgresHealthIndicator } from './postgres.health';
import { RedisHealthIndicator } from './redis.health';
@Global()
@Module({
controllers: [HealthController],
providers: [PostgresHealthIndicator, RedisHealthIndicator],
imports: [TerminusModule],
})
export class HealthModule {}
@@ -0,0 +1,31 @@
import { InjectKysely } from 'nestjs-kysely';
import {
HealthCheckError,
HealthIndicator,
HealthIndicatorResult,
} from '@nestjs/terminus';
import { Injectable, Logger } from '@nestjs/common';
import { sql } from 'kysely';
import { KyselyDB } from '@docmost/db/types/kysely.types';
@Injectable()
export class PostgresHealthIndicator extends HealthIndicator {
private readonly logger = new Logger(PostgresHealthIndicator.name);
constructor(@InjectKysely() private readonly db: KyselyDB) {
super();
}
async pingCheck(key: string): Promise<HealthIndicatorResult> {
try {
await sql`SELECT 1=1`.execute(this.db);
return this.getStatus(key, true);
} catch (e) {
this.logger.error(JSON.stringify(e));
throw new HealthCheckError(
`${key} is not available`,
this.getStatus(key, false),
);
}
}
}
@@ -0,0 +1,34 @@
import {
HealthCheckError,
HealthIndicator,
HealthIndicatorResult,
} from '@nestjs/terminus';
import { Injectable, Logger } from '@nestjs/common';
import { EnvironmentService } from '../environment/environment.service';
import { Redis } from 'ioredis';
@Injectable()
export class RedisHealthIndicator extends HealthIndicator {
private readonly logger = new Logger(RedisHealthIndicator.name);
constructor(private environmentService: EnvironmentService) {
super();
}
async pingCheck(key: string): Promise<HealthIndicatorResult> {
try {
const redis = new Redis(this.environmentService.getRedisUrl(), {
maxRetriesPerRequest: 15,
});
await redis.ping();
return this.getStatus(key, true);
} catch (e) {
this.logger.error(e);
throw new HealthCheckError(
`${key} is not available`,
this.getStatus(key, false),
);
}
}
}
@@ -34,9 +34,6 @@ export class S3Driver implements StorageDriver {
}); });
await this.s3Client.send(command); await this.s3Client.send(command);
// we can get the path from location
console.log(`File uploaded successfully: ${filePath}`);
} catch (err) { } catch (err) {
throw new Error(`Failed to upload file: ${(err as Error).message}`); throw new Error(`Failed to upload file: ${(err as Error).message}`);
} }
@@ -41,20 +41,34 @@ export const storageDriverConfigProvider = {
}; };
case StorageOption.S3: case StorageOption.S3:
return { const s3Config = {
driver, driver,
config: { config: {
region: environmentService.getAwsS3Region(), region: environmentService.getAwsS3Region(),
endpoint: environmentService.getAwsS3Endpoint(), endpoint: environmentService.getAwsS3Endpoint(),
bucket: environmentService.getAwsS3Bucket(), bucket: environmentService.getAwsS3Bucket(),
baseUrl: environmentService.getAwsS3Url(), baseUrl: environmentService.getAwsS3Url(),
credentials: { credentials: undefined,
accessKeyId: environmentService.getAwsS3AccessKeyId(),
secretAccessKey: environmentService.getAwsS3SecretAccessKey(),
},
}, },
}; };
/**
* This makes use of AWS_S3_ACCESS_KEY_ID and AWS_S3_SECRET_ACCESS_KEY if present,
* If not present, it makes it lenient for the AWS SDK to use
* AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY if they are present in the environment
*/
if (
environmentService.getAwsS3AccessKeyId() ||
environmentService.getAwsS3SecretAccessKey()
) {
s3Config.config.credentials = {
accessKeyId: environmentService.getAwsS3AccessKeyId(),
secretAccessKey: environmentService.getAwsS3SecretAccessKey(),
};
}
return s3Config;
default: default:
throw new Error(`Unknown storage driver: ${driver}`); throw new Error(`Unknown storage driver: ${driver}`);
} }
@@ -1,15 +1,17 @@
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable, Logger } from '@nestjs/common';
import { STORAGE_DRIVER_TOKEN } from './constants/storage.constants'; import { STORAGE_DRIVER_TOKEN } from './constants/storage.constants';
import { StorageDriver } from './interfaces'; import { StorageDriver } from './interfaces';
@Injectable() @Injectable()
export class StorageService { export class StorageService {
private readonly logger = new Logger(StorageService.name);
constructor( constructor(
@Inject(STORAGE_DRIVER_TOKEN) private storageDriver: StorageDriver, @Inject(STORAGE_DRIVER_TOKEN) private storageDriver: StorageDriver,
) {} ) {}
async upload(filePath: string, fileContent: Buffer | any) { async upload(filePath: string, fileContent: Buffer | any) {
await this.storageDriver.upload(filePath, fileContent); await this.storageDriver.upload(filePath, fileContent);
this.logger.debug(`File uploaded successfully. Path: ${filePath}`);
} }
async read(filePath: string): Promise<Buffer> { async read(filePath: string): Promise<Buffer> {
+5 -1
View File
@@ -9,6 +9,7 @@ import { TransformHttpResponseInterceptor } from './common/interceptors/http-res
import fastifyMultipart from '@fastify/multipart'; import fastifyMultipart from '@fastify/multipart';
import { WsRedisIoAdapter } from './ws/adapter/ws-redis.adapter'; import { WsRedisIoAdapter } from './ws/adapter/ws-redis.adapter';
import { InternalLogFilter } from './common/logger/internal-log-filter'; import { InternalLogFilter } from './common/logger/internal-log-filter';
import fastifyCookie from '@fastify/cookie';
async function bootstrap() { async function bootstrap() {
const app = await NestFactory.create<NestFastifyApplication>( const app = await NestFactory.create<NestFastifyApplication>(
@@ -31,6 +32,7 @@ async function bootstrap() {
app.useWebSocketAdapter(redisIoAdapter); app.useWebSocketAdapter(redisIoAdapter);
await app.register(fastifyMultipart as any); await app.register(fastifyMultipart as any);
await app.register(fastifyCookie as any);
app app
.getHttpAdapter() .getHttpAdapter()
@@ -38,7 +40,8 @@ async function bootstrap() {
.addHook('preHandler', function (req, reply, done) { .addHook('preHandler', function (req, reply, done) {
if ( if (
req.originalUrl.startsWith('/api') && req.originalUrl.startsWith('/api') &&
!req.originalUrl.startsWith('/api/auth/setup') !req.originalUrl.startsWith('/api/auth/setup') &&
!req.originalUrl.startsWith('/api/health')
) { ) {
if (!req.raw?.['workspaceId']) { if (!req.raw?.['workspaceId']) {
throw new NotFoundException('Workspace not found'); throw new NotFoundException('Workspace not found');
@@ -56,6 +59,7 @@ async function bootstrap() {
transform: true, transform: true,
}), }),
); );
app.enableCors(); app.enableCors();
app.useGlobalInterceptors(new TransformHttpResponseInterceptor()); app.useGlobalInterceptors(new TransformHttpResponseInterceptor());
+8 -6
View File
@@ -1,7 +1,7 @@
{ {
"name": "docmost", "name": "docmost",
"homepage": "https://docmost.com", "homepage": "https://docmost.com",
"version": "0.2.0", "version": "0.2.4",
"private": true, "private": true,
"scripts": { "scripts": {
"build": "nx run-many -t build", "build": "nx run-many -t build",
@@ -12,14 +12,15 @@
"client:dev": "nx run client:dev", "client:dev": "nx run client:dev",
"server:dev": "nx run server:start:dev", "server:dev": "nx run server:start:dev",
"server:start": "nx run server:start:prod", "server:start": "nx run server:start:prod",
"email:dev": "nx run @docmost/transactional:dev" "email:dev": "nx run @docmost/transactional:dev",
"dev": "pnpm concurrently -n \"frontend,backend\" -c \"cyan,green\" \"pnpm run client:dev\" \"pnpm run server:dev\""
}, },
"dependencies": { "dependencies": {
"@docmost/editor-ext": "workspace:*", "@docmost/editor-ext": "workspace:*",
"@hocuspocus/extension-redis": "^2.13.2", "@hocuspocus/extension-redis": "^2.13.5",
"@hocuspocus/provider": "^2.13.2", "@hocuspocus/provider": "^2.13.5",
"@hocuspocus/server": "^2.13.2", "@hocuspocus/server": "^2.13.5",
"@hocuspocus/transformer": "^2.13.2", "@hocuspocus/transformer": "^2.13.5",
"@sindresorhus/slugify": "^2.2.1", "@sindresorhus/slugify": "^2.2.1",
"@tiptap/core": "^2.4.0", "@tiptap/core": "^2.4.0",
"@tiptap/extension-code-block": "^2.4.0", "@tiptap/extension-code-block": "^2.4.0",
@@ -66,6 +67,7 @@
"@nx/js": "19.3.2", "@nx/js": "19.3.2",
"@types/uuid": "^10.0.0", "@types/uuid": "^10.0.0",
"nx": "19.3.2", "nx": "19.3.2",
"concurrently": "^8.2.2",
"tsx": "^4.15.7" "tsx": "^4.15.7"
}, },
"workspaces": { "workspaces": {
+189 -26
View File
@@ -12,17 +12,17 @@ importers:
specifier: workspace:* specifier: workspace:*
version: link:packages/editor-ext version: link:packages/editor-ext
'@hocuspocus/extension-redis': '@hocuspocus/extension-redis':
specifier: ^2.13.2 specifier: ^2.13.5
version: 2.13.2(y-protocols@1.0.6(yjs@13.6.18))(yjs@13.6.18) version: 2.13.5(y-protocols@1.0.6(yjs@13.6.18))(yjs@13.6.18)
'@hocuspocus/provider': '@hocuspocus/provider':
specifier: ^2.13.2 specifier: ^2.13.5
version: 2.13.2(y-protocols@1.0.6(yjs@13.6.18))(yjs@13.6.18) version: 2.13.5(y-protocols@1.0.6(yjs@13.6.18))(yjs@13.6.18)
'@hocuspocus/server': '@hocuspocus/server':
specifier: ^2.13.2 specifier: ^2.13.5
version: 2.13.2(y-protocols@1.0.6(yjs@13.6.18))(yjs@13.6.18) version: 2.13.5(y-protocols@1.0.6(yjs@13.6.18))(yjs@13.6.18)
'@hocuspocus/transformer': '@hocuspocus/transformer':
specifier: ^2.13.2 specifier: ^2.13.5
version: 2.13.2(@tiptap/pm@2.4.0)(y-prosemirror@1.2.3(prosemirror-model@1.19.4)(prosemirror-state@1.4.3)(prosemirror-view@1.32.7)(y-protocols@1.0.6(yjs@13.6.18))(yjs@13.6.18))(yjs@13.6.18) version: 2.13.5(@tiptap/pm@2.4.0)(y-prosemirror@1.2.3(prosemirror-model@1.19.4)(prosemirror-state@1.4.3)(prosemirror-view@1.32.7)(y-protocols@1.0.6(yjs@13.6.18))(yjs@13.6.18))(yjs@13.6.18)
'@sindresorhus/slugify': '@sindresorhus/slugify':
specifier: ^2.2.1 specifier: ^2.2.1
version: 2.2.1 version: 2.2.1
@@ -153,6 +153,9 @@ importers:
'@types/uuid': '@types/uuid':
specifier: ^10.0.0 specifier: ^10.0.0
version: 10.0.0 version: 10.0.0
concurrently:
specifier: ^8.2.2
version: 8.2.2
nx: nx:
specifier: 19.3.2 specifier: 19.3.2
version: 19.3.2(@swc/core@1.5.25(@swc/helpers@0.5.11)) version: 19.3.2(@swc/core@1.5.25(@swc/helpers@0.5.11))
@@ -331,6 +334,9 @@ importers:
'@casl/ability': '@casl/ability':
specifier: ^6.7.1 specifier: ^6.7.1
version: 6.7.1 version: 6.7.1
'@fastify/cookie':
specifier: ^9.3.1
version: 9.3.1
'@fastify/multipart': '@fastify/multipart':
specifier: ^8.3.0 specifier: ^8.3.0
version: 8.3.0 version: 8.3.0
@@ -367,6 +373,9 @@ importers:
'@nestjs/platform-socket.io': '@nestjs/platform-socket.io':
specifier: ^10.3.9 specifier: ^10.3.9
version: 10.3.9(@nestjs/common@10.3.9(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/websockets@10.3.9)(rxjs@7.8.1) version: 10.3.9(@nestjs/common@10.3.9(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/websockets@10.3.9)(rxjs@7.8.1)
'@nestjs/terminus':
specifier: ^10.2.3
version: 10.2.3(@nestjs/common@10.3.9(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.3.9(@nestjs/common@10.3.9(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/websockets@10.3.9)(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)(rxjs@7.8.1)
'@nestjs/websockets': '@nestjs/websockets':
specifier: ^10.3.9 specifier: ^10.3.9
version: 10.3.9(@nestjs/common@10.3.9(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.3.9)(@nestjs/platform-socket.io@10.3.9)(reflect-metadata@0.2.2)(rxjs@7.8.1) version: 10.3.9(@nestjs/common@10.3.9(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.3.9)(@nestjs/platform-socket.io@10.3.9)(reflect-metadata@0.2.2)(rxjs@7.8.1)
@@ -1875,6 +1884,9 @@ packages:
resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==} resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==}
engines: {node: '>=14'} engines: {node: '>=14'}
'@fastify/cookie@9.3.1':
resolution: {integrity: sha512-h1NAEhB266+ZbZ0e9qUE6NnNR07i7DnNXWG9VbbZ8uC6O/hxHpl+Zoe5sw1yfdZ2U6XhToUGDnzQtWJdCaPwfg==}
'@fastify/cors@9.0.1': '@fastify/cors@9.0.1':
resolution: {integrity: sha512-YY9Ho3ovI+QHIL2hW+9X4XqQjXLjJqsU+sMV/xFsxZkE8p3GNnYVFpoOxF7SsP5ZL76gwvbo3V9L+FIekBGU4Q==} resolution: {integrity: sha512-YY9Ho3ovI+QHIL2hW+9X4XqQjXLjJqsU+sMV/xFsxZkE8p3GNnYVFpoOxF7SsP5ZL76gwvbo3V9L+FIekBGU4Q==}
@@ -1926,29 +1938,29 @@ packages:
'@floating-ui/utils@0.2.1': '@floating-ui/utils@0.2.1':
resolution: {integrity: sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==} resolution: {integrity: sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==}
'@hocuspocus/common@2.13.2': '@hocuspocus/common@2.13.5':
resolution: {integrity: sha512-NMsXx/Dl9xu1KlhNbGzLYLOjKtUOVmU+zA/br+EA4DhNDEtGWvHAmqY5r9SBTr2mGtGJagt+GEqZydSzBUFj4g==} resolution: {integrity: sha512-8D9FzhZFlt0WsgXw5yT2zwSxi6z9d4V2vUz6co2vo3Cj+Y2bvGZsdDiTvU/MerGcCLME5k/w6PwLPojLYH/4pg==}
'@hocuspocus/extension-redis@2.13.2': '@hocuspocus/extension-redis@2.13.5':
resolution: {integrity: sha512-QZJKMcMDl3KqDKrwJ65a47aB6scNcmhUN3AqoaeDy8gE3Z3RHo8GCGNcrZCy7MT/+U4KzgRl2rbdJLHciGsAew==} resolution: {integrity: sha512-2wShUgOVXOUd2jd+ncmK3Wd/PUUwrIWNp17XZg/YD6L6kNSNvqPKo0vPmaT4uCekhVO6qPYX3fsLgX2j2hMyUQ==}
peerDependencies: peerDependencies:
y-protocols: ^1.0.6 y-protocols: ^1.0.6
yjs: ^13.6.8 yjs: ^13.6.8
'@hocuspocus/provider@2.13.2': '@hocuspocus/provider@2.13.5':
resolution: {integrity: sha512-Pi+b8gcXHomSDRzohbmVW4dwo5OIqEBoLVrsuEG/pCCS13gBAeUCKCxfrl0q5XCNhK1nu3/j6UW9lxuExqiY+g==} resolution: {integrity: sha512-G3S0OiFSYkmbOwnbhV7FyJs4OBqB/+1YT9c44Ujux1RKowGm5H8+0p3FUHfXwd/3v9V0jE+E1FnFKoGonJSQwA==}
peerDependencies: peerDependencies:
y-protocols: ^1.0.6 y-protocols: ^1.0.6
yjs: ^13.6.8 yjs: ^13.6.8
'@hocuspocus/server@2.13.2': '@hocuspocus/server@2.13.5':
resolution: {integrity: sha512-crUuMJaK8Ql6zC9x+N0kwKlHTZ2pXV7hG2yOQj5ieGxnQtX4uExU77yS9vji9R7w10YcPnkDwvwPMgeyWhwRfQ==} resolution: {integrity: sha512-gDYax5ruaj30mMtFjq5+o5USXQD31hDOxBVU8eTAzezS6hpVllaP7HmM8iRda+UmQoeybHxzqsWAf74JCmYDug==}
peerDependencies: peerDependencies:
y-protocols: ^1.0.6 y-protocols: ^1.0.6
yjs: ^13.6.8 yjs: ^13.6.8
'@hocuspocus/transformer@2.13.2': '@hocuspocus/transformer@2.13.5':
resolution: {integrity: sha512-F6dCg3cCNB1QPXtkNvlYuVESTy5VJglNF6AbUAx1yh5zXRz9CMzxZs7tjVCdarZtjYmyu/dtYctub7uyA2RefA==} resolution: {integrity: sha512-G5QWvV4K7lussF4lSWPFjtqSTZjiq1tkU4WEn1u8OdKQUF0GtfNcnrGWMlCeJdz5Ucpp4qJHWt4TTPI1Lpw/9Q==}
peerDependencies: peerDependencies:
'@tiptap/pm': ^2.1.12 '@tiptap/pm': ^2.1.12
y-prosemirror: ^1.2.1 y-prosemirror: ^1.2.1
@@ -2283,6 +2295,54 @@ packages:
peerDependencies: peerDependencies:
typescript: '>=4.8.2' typescript: '>=4.8.2'
'@nestjs/terminus@10.2.3':
resolution: {integrity: sha512-iX7gXtAooePcyQqFt57aDke5MzgdkBeYgF5YsFNNFwOiAFdIQEhfv3PR0G+HlH9F6D7nBCDZt9U87Pks/qHijg==}
peerDependencies:
'@grpc/grpc-js': '*'
'@grpc/proto-loader': '*'
'@mikro-orm/core': '*'
'@mikro-orm/nestjs': '*'
'@nestjs/axios': ^1.0.0 || ^2.0.0 || ^3.0.0
'@nestjs/common': ^9.0.0 || ^10.0.0
'@nestjs/core': ^9.0.0 || ^10.0.0
'@nestjs/microservices': ^9.0.0 || ^10.0.0
'@nestjs/mongoose': ^9.0.0 || ^10.0.0
'@nestjs/sequelize': ^9.0.0 || ^10.0.0
'@nestjs/typeorm': ^9.0.0 || ^10.0.0
'@prisma/client': '*'
mongoose: '*'
reflect-metadata: 0.1.x || 0.2.x
rxjs: 7.x
sequelize: '*'
typeorm: '*'
peerDependenciesMeta:
'@grpc/grpc-js':
optional: true
'@grpc/proto-loader':
optional: true
'@mikro-orm/core':
optional: true
'@mikro-orm/nestjs':
optional: true
'@nestjs/axios':
optional: true
'@nestjs/microservices':
optional: true
'@nestjs/mongoose':
optional: true
'@nestjs/sequelize':
optional: true
'@nestjs/typeorm':
optional: true
'@prisma/client':
optional: true
mongoose:
optional: true
sequelize:
optional: true
typeorm:
optional: true
'@nestjs/testing@10.3.9': '@nestjs/testing@10.3.9':
resolution: {integrity: sha512-z24SdpZIRtYyM5s2vnu7rbBosXJY/KcAP7oJlwgFa/h/z/wg8gzyoKy5lhibH//OZNO+pYKajV5wczxuy5WeAg==} resolution: {integrity: sha512-z24SdpZIRtYyM5s2vnu7rbBosXJY/KcAP7oJlwgFa/h/z/wg8gzyoKy5lhibH//OZNO+pYKajV5wczxuy5WeAg==}
peerDependencies: peerDependencies:
@@ -4158,6 +4218,9 @@ packages:
ajv@8.12.0: ajv@8.12.0:
resolution: {integrity: sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==} resolution: {integrity: sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==}
ansi-align@3.0.1:
resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==}
ansi-colors@4.1.3: ansi-colors@4.1.3:
resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==}
engines: {node: '>=6'} engines: {node: '>=6'}
@@ -4343,6 +4406,10 @@ packages:
bowser@2.11.0: bowser@2.11.0:
resolution: {integrity: sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==} resolution: {integrity: sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==}
boxen@5.1.2:
resolution: {integrity: sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==}
engines: {node: '>=10'}
brace-expansion@1.1.11: brace-expansion@1.1.11:
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
@@ -4437,6 +4504,10 @@ packages:
chardet@0.7.0: chardet@0.7.0:
resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==}
check-disk-space@3.4.0:
resolution: {integrity: sha512-drVkSqfwA+TvuEhFipiR1OC9boEGZL5RrWvVsOthdcvQNXyCCuKkEiTOTXZ7qxSf/GLwq4GvzfrQD/Wz325hgw==}
engines: {node: '>=16'}
chokidar@3.5.3: chokidar@3.5.3:
resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==}
engines: {node: '>= 8.10.0'} engines: {node: '>= 8.10.0'}
@@ -4466,6 +4537,10 @@ packages:
class-validator@0.14.1: class-validator@0.14.1:
resolution: {integrity: sha512-2VEG9JICxIqTpoK1eMzZqaV+u/EiwEJkMGzTrZf6sU/fwsnOITVgYJ8yojSy6CaXtO9V0Cc6ZQZ8h8m4UBuLwQ==} resolution: {integrity: sha512-2VEG9JICxIqTpoK1eMzZqaV+u/EiwEJkMGzTrZf6sU/fwsnOITVgYJ8yojSy6CaXtO9V0Cc6ZQZ8h8m4UBuLwQ==}
cli-boxes@2.2.1:
resolution: {integrity: sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==}
engines: {node: '>=6'}
cli-cursor@3.1.0: cli-cursor@3.1.0:
resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==}
engines: {node: '>=8'} engines: {node: '>=8'}
@@ -4578,6 +4653,11 @@ packages:
concat-map@0.0.1: concat-map@0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
concurrently@8.2.2:
resolution: {integrity: sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==}
engines: {node: ^14.13.0 || >=16.0.0}
hasBin: true
config-chain@1.1.13: config-chain@1.1.13:
resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==} resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==}
@@ -4594,6 +4674,10 @@ packages:
convert-source-map@2.0.0: convert-source-map@2.0.0:
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
cookie-signature@1.2.1:
resolution: {integrity: sha512-78KWk9T26NhzXtuL26cIJ8/qNHANyJ/ZYrmEXFzUmhZdjpBv+DlWlOANRTGBt48YcyslsLrj0bMLFTmXvLRCOw==}
engines: {node: '>=6.6.0'}
cookie@0.4.2: cookie@0.4.2:
resolution: {integrity: sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==} resolution: {integrity: sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==}
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
@@ -4676,6 +4760,10 @@ packages:
dash-get@1.0.2: dash-get@1.0.2:
resolution: {integrity: sha512-4FbVrHDwfOASx7uQVxeiCTo7ggSdYZbqs8lH+WU6ViypPlDbe9y6IP5VVUDQBv9DcnyaiPT5XT0UWHgJ64zLeQ==} resolution: {integrity: sha512-4FbVrHDwfOASx7uQVxeiCTo7ggSdYZbqs8lH+WU6ViypPlDbe9y6IP5VVUDQBv9DcnyaiPT5XT0UWHgJ64zLeQ==}
date-fns@2.30.0:
resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==}
engines: {node: '>=0.11'}
date-fns@3.6.0: date-fns@3.6.0:
resolution: {integrity: sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==} resolution: {integrity: sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==}
@@ -7181,6 +7269,9 @@ packages:
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
engines: {node: '>=8'} engines: {node: '>=8'}
shell-quote@1.8.1:
resolution: {integrity: sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==}
shelljs.exec@1.1.8: shelljs.exec@1.1.8:
resolution: {integrity: sha512-vFILCw+lzUtiwBAHV8/Ex8JsFjelFMdhONIsgKNLgTzeRckp2AOYRQtHJE/9LhNvdMmE27AGtzWx0+DHpwIwSw==} resolution: {integrity: sha512-vFILCw+lzUtiwBAHV8/Ex8JsFjelFMdhONIsgKNLgTzeRckp2AOYRQtHJE/9LhNvdMmE27AGtzWx0+DHpwIwSw==}
engines: {node: '>= 4.0.0'} engines: {node: '>= 4.0.0'}
@@ -7265,6 +7356,9 @@ packages:
resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==} resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==}
engines: {node: '>= 8'} engines: {node: '>= 8'}
spawn-command@0.0.2:
resolution: {integrity: sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==}
split2@4.2.0: split2@4.2.0:
resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==}
engines: {node: '>= 10.x'} engines: {node: '>= 10.x'}
@@ -7598,6 +7692,10 @@ packages:
resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==}
engines: {node: '>=4'} engines: {node: '>=4'}
type-fest@0.20.2:
resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==}
engines: {node: '>=10'}
type-fest@0.21.3: type-fest@0.21.3:
resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==}
engines: {node: '>=10'} engines: {node: '>=10'}
@@ -7840,6 +7938,10 @@ packages:
wide-align@1.1.5: wide-align@1.1.5:
resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==}
widest-line@3.1.0:
resolution: {integrity: sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==}
engines: {node: '>=8'}
wrap-ansi@6.2.0: wrap-ansi@6.2.0:
resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==}
engines: {node: '>=8'} engines: {node: '>=8'}
@@ -9789,6 +9891,11 @@ snapshots:
'@fastify/busboy@2.1.1': {} '@fastify/busboy@2.1.1': {}
'@fastify/cookie@9.3.1':
dependencies:
cookie-signature: 1.2.1
fastify-plugin: 4.5.1
'@fastify/cors@9.0.1': '@fastify/cors@9.0.1':
dependencies: dependencies:
fastify-plugin: 4.5.1 fastify-plugin: 4.5.1
@@ -9869,13 +9976,13 @@ snapshots:
'@floating-ui/utils@0.2.1': {} '@floating-ui/utils@0.2.1': {}
'@hocuspocus/common@2.13.2': '@hocuspocus/common@2.13.5':
dependencies: dependencies:
lib0: 0.2.93 lib0: 0.2.93
'@hocuspocus/extension-redis@2.13.2(y-protocols@1.0.6(yjs@13.6.18))(yjs@13.6.18)': '@hocuspocus/extension-redis@2.13.5(y-protocols@1.0.6(yjs@13.6.18))(yjs@13.6.18)':
dependencies: dependencies:
'@hocuspocus/server': 2.13.2(y-protocols@1.0.6(yjs@13.6.18))(yjs@13.6.18) '@hocuspocus/server': 2.13.5(y-protocols@1.0.6(yjs@13.6.18))(yjs@13.6.18)
ioredis: 4.28.5 ioredis: 4.28.5
kleur: 4.1.5 kleur: 4.1.5
lodash.debounce: 4.0.8 lodash.debounce: 4.0.8
@@ -9888,9 +9995,9 @@ snapshots:
- supports-color - supports-color
- utf-8-validate - utf-8-validate
'@hocuspocus/provider@2.13.2(y-protocols@1.0.6(yjs@13.6.18))(yjs@13.6.18)': '@hocuspocus/provider@2.13.5(y-protocols@1.0.6(yjs@13.6.18))(yjs@13.6.18)':
dependencies: dependencies:
'@hocuspocus/common': 2.13.2 '@hocuspocus/common': 2.13.5
'@lifeomic/attempt': 3.0.3 '@lifeomic/attempt': 3.0.3
lib0: 0.2.93 lib0: 0.2.93
ws: 8.17.1 ws: 8.17.1
@@ -9900,9 +10007,9 @@ snapshots:
- bufferutil - bufferutil
- utf-8-validate - utf-8-validate
'@hocuspocus/server@2.13.2(y-protocols@1.0.6(yjs@13.6.18))(yjs@13.6.18)': '@hocuspocus/server@2.13.5(y-protocols@1.0.6(yjs@13.6.18))(yjs@13.6.18)':
dependencies: dependencies:
'@hocuspocus/common': 2.13.2 '@hocuspocus/common': 2.13.5
async-lock: 1.4.1 async-lock: 1.4.1
kleur: 4.1.5 kleur: 4.1.5
lib0: 0.2.93 lib0: 0.2.93
@@ -9914,7 +10021,7 @@ snapshots:
- bufferutil - bufferutil
- utf-8-validate - utf-8-validate
'@hocuspocus/transformer@2.13.2(@tiptap/pm@2.4.0)(y-prosemirror@1.2.3(prosemirror-model@1.19.4)(prosemirror-state@1.4.3)(prosemirror-view@1.32.7)(y-protocols@1.0.6(yjs@13.6.18))(yjs@13.6.18))(yjs@13.6.18)': '@hocuspocus/transformer@2.13.5(@tiptap/pm@2.4.0)(y-prosemirror@1.2.3(prosemirror-model@1.19.4)(prosemirror-state@1.4.3)(prosemirror-view@1.32.7)(y-protocols@1.0.6(yjs@13.6.18))(yjs@13.6.18))(yjs@13.6.18)':
dependencies: dependencies:
'@tiptap/core': 2.4.0(@tiptap/pm@2.4.0) '@tiptap/core': 2.4.0(@tiptap/pm@2.4.0)
'@tiptap/pm': 2.4.0 '@tiptap/pm': 2.4.0
@@ -10389,6 +10496,15 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- chokidar - chokidar
'@nestjs/terminus@10.2.3(@nestjs/common@10.3.9(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.3.9(@nestjs/common@10.3.9(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/websockets@10.3.9)(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)(rxjs@7.8.1)':
dependencies:
'@nestjs/common': 10.3.9(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1)
'@nestjs/core': 10.3.9(@nestjs/common@10.3.9(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/websockets@10.3.9)(reflect-metadata@0.2.2)(rxjs@7.8.1)
boxen: 5.1.2
check-disk-space: 3.4.0
reflect-metadata: 0.2.2
rxjs: 7.8.1
'@nestjs/testing@10.3.9(@nestjs/common@10.3.9(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.3.9(@nestjs/common@10.3.9(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/websockets@10.3.9)(reflect-metadata@0.2.2)(rxjs@7.8.1))': '@nestjs/testing@10.3.9(@nestjs/common@10.3.9(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.3.9(@nestjs/common@10.3.9(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/websockets@10.3.9)(reflect-metadata@0.2.2)(rxjs@7.8.1))':
dependencies: dependencies:
'@nestjs/common': 10.3.9(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) '@nestjs/common': 10.3.9(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1)
@@ -12451,6 +12567,10 @@ snapshots:
require-from-string: 2.0.2 require-from-string: 2.0.2
uri-js: 4.4.1 uri-js: 4.4.1
ansi-align@3.0.1:
dependencies:
string-width: 4.2.3
ansi-colors@4.1.3: {} ansi-colors@4.1.3: {}
ansi-escapes@4.3.2: ansi-escapes@4.3.2:
@@ -12706,6 +12826,17 @@ snapshots:
bowser@2.11.0: {} bowser@2.11.0: {}
boxen@5.1.2:
dependencies:
ansi-align: 3.0.1
camelcase: 6.3.0
chalk: 4.1.2
cli-boxes: 2.2.1
string-width: 4.2.3
type-fest: 0.20.2
widest-line: 3.1.0
wrap-ansi: 7.0.0
brace-expansion@1.1.11: brace-expansion@1.1.11:
dependencies: dependencies:
balanced-match: 1.0.2 balanced-match: 1.0.2
@@ -12807,6 +12938,8 @@ snapshots:
chardet@0.7.0: {} chardet@0.7.0: {}
check-disk-space@3.4.0: {}
chokidar@3.5.3: chokidar@3.5.3:
dependencies: dependencies:
anymatch: 3.1.3 anymatch: 3.1.3
@@ -12847,6 +12980,8 @@ snapshots:
libphonenumber-js: 1.10.58 libphonenumber-js: 1.10.58
validator: 13.12.0 validator: 13.12.0
cli-boxes@2.2.1: {}
cli-cursor@3.1.0: cli-cursor@3.1.0:
dependencies: dependencies:
restore-cursor: 3.1.0 restore-cursor: 3.1.0
@@ -12932,6 +13067,18 @@ snapshots:
concat-map@0.0.1: {} concat-map@0.0.1: {}
concurrently@8.2.2:
dependencies:
chalk: 4.1.2
date-fns: 2.30.0
lodash: 4.17.21
rxjs: 7.8.1
shell-quote: 1.8.1
spawn-command: 0.0.2
supports-color: 8.1.1
tree-kill: 1.2.2
yargs: 17.7.2
config-chain@1.1.13: config-chain@1.1.13:
dependencies: dependencies:
ini: 1.3.8 ini: 1.3.8
@@ -12947,6 +13094,8 @@ snapshots:
convert-source-map@2.0.0: {} convert-source-map@2.0.0: {}
cookie-signature@1.2.1: {}
cookie@0.4.2: {} cookie@0.4.2: {}
cookie@0.6.0: {} cookie@0.6.0: {}
@@ -13036,6 +13185,10 @@ snapshots:
dash-get@1.0.2: {} dash-get@1.0.2: {}
date-fns@2.30.0:
dependencies:
'@babel/runtime': 7.23.7
date-fns@3.6.0: {} date-fns@3.6.0: {}
debounce@2.0.0: {} debounce@2.0.0: {}
@@ -15946,6 +16099,8 @@ snapshots:
shebang-regex@3.0.0: {} shebang-regex@3.0.0: {}
shell-quote@1.8.1: {}
shelljs.exec@1.1.8: {} shelljs.exec@1.1.8: {}
shelljs@0.8.5: shelljs@0.8.5:
@@ -16067,6 +16222,8 @@ snapshots:
source-map@0.7.4: {} source-map@0.7.4: {}
spawn-command@0.0.2: {}
split2@4.2.0: {} split2@4.2.0: {}
sprintf-js@1.0.3: {} sprintf-js@1.0.3: {}
@@ -16434,6 +16591,8 @@ snapshots:
type-detect@4.0.8: {} type-detect@4.0.8: {}
type-fest@0.20.2: {}
type-fest@0.21.3: {} type-fest@0.21.3: {}
type-fest@0.7.1: {} type-fest@0.7.1: {}
@@ -16651,6 +16810,10 @@ snapshots:
dependencies: dependencies:
string-width: 4.2.3 string-width: 4.2.3
widest-line@3.1.0:
dependencies:
string-width: 4.2.3
wrap-ansi@6.2.0: wrap-ansi@6.2.0:
dependencies: dependencies:
ansi-styles: 4.3.0 ansi-styles: 4.3.0