Skip to content

Commit

Permalink
Add retrieve a Document
Browse files Browse the repository at this point in the history
  • Loading branch information
devleejb committed Jan 22, 2024
1 parent 2cf0078 commit db0e8c2
Show file tree
Hide file tree
Showing 15 changed files with 100 additions and 64 deletions.
23 changes: 22 additions & 1 deletion backend/src/documents/documents.controller.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Controller, Get, Query } from "@nestjs/common";
import { Controller, Get, Param, Query, Req } from "@nestjs/common";
import { DocumentsService } from "./documents.service";
import { Public } from "src/utils/decorators/auth.decorator";
import {
ApiFoundResponse,
ApiNotFoundResponse,
ApiOkResponse,
ApiOperation,
Expand All @@ -11,6 +12,8 @@ import {
} from "@nestjs/swagger";
import { FindDocumentFromSharingTokenResponse } from "./types/find-document-from-sharing-token-response.type";
import { HttpExceptionResponse } from "src/utils/types/http-exception-response.type";
import { FindDocumentResponse } from "./types/find-document-response.type";
import { AuthroizedRequest } from "src/utils/types/req.type";

@ApiTags("Documents")
@Controller("documents")
Expand Down Expand Up @@ -38,4 +41,22 @@ export class DocumentsController {
): Promise<FindDocumentFromSharingTokenResponse> {
return this.documentsService.findDocumentFromSharingToken(token);
}

@Get(":document_slug")
@ApiOperation({
summary: "Retrieve a Document in the Workspace",
description: "If the user has the access permissions, return a document.",
})
@ApiFoundResponse({ type: FindDocumentResponse })
@ApiNotFoundResponse({
type: HttpExceptionResponse,
description:
"The workspace or document does not exist, or the user lacks the appropriate permissions.",
})
async findOne(
@Req() req: AuthroizedRequest,
@Param("document_slug") documentSlug: string
): Promise<FindDocumentResponse> {
return this.documentsService.findOneBySlug(req.user.id, documentSlug);
}
}
21 changes: 21 additions & 0 deletions backend/src/documents/documents.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,25 @@ export class DocumentsService {
role,
};
}

async findOneBySlug(userId: string, documentSlug: string) {
try {
const document = await this.prismaService.document.findFirstOrThrow({
where: {
slug: documentSlug,
},
});

await this.prismaService.userWorkspace.findFirstOrThrow({
where: {
userId,
workspaceId: document.workspaceId,
},
});

return document;
} catch (e) {
throw new NotFoundException();
}
}
}
3 changes: 3 additions & 0 deletions backend/src/documents/types/find-document-response.type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { WorkspaceDocumentDomain } from "../../workspace-documents/types/workspace-document-domain.type";

export class FindDocumentResponse extends WorkspaceDocumentDomain {}

This file was deleted.

20 changes: 0 additions & 20 deletions backend/src/workspace-documents/workspace-documents.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import { AuthroizedRequest } from "src/utils/types/req.type";
import { CreateWorkspaceDocumentDto } from "./dto/create-workspace-document.dto";
import { CreateWorkspaceDocumentResponse } from "./types/create-workspace-document-response.type";
import { HttpExceptionResponse } from "src/utils/types/http-exception-response.type";
import { FindWorkspaceDocumentResponse } from "./types/find-workspace-document-response.type";
import { FindWorkspaceDocumentsResponse } from "./types/find-workspace-documents-response.type";
import { CreateWorkspaceDocumentShareTokenResponse } from "./types/create-workspace-document-share-token-response.type";
import { CreateWorkspaceDocumentShareTokenDto } from "./dto/create-workspace-document-share-token.dto";
Expand All @@ -36,25 +35,6 @@ import { CreateWorkspaceDocumentShareTokenDto } from "./dto/create-workspace-doc
export class WorkspaceDocumentsController {
constructor(private workspaceDocumentsService: WorkspaceDocumentsService) {}

@Get(":document_slug")
@ApiOperation({
summary: "Retrieve a Document in the Workspace",
description: "If the user has the access permissions, return a document.",
})
@ApiFoundResponse({ type: FindWorkspaceDocumentResponse })
@ApiNotFoundResponse({
type: HttpExceptionResponse,
description:
"The workspace or document does not exist, or the user lacks the appropriate permissions.",
})
async findOne(
@Req() req: AuthroizedRequest,
@Param("workspace_id") workspaceId: string,
@Param("document_slug") documentSlug: string
): Promise<FindWorkspaceDocumentResponse> {
return this.workspaceDocumentsService.findOneBySlug(req.user.id, workspaceId, documentSlug);
}

@Get("")
@ApiOperation({
summary: "Retrieve the Documents in Workspace",
Expand Down
19 changes: 0 additions & 19 deletions backend/src/workspace-documents/workspace-documents.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,25 +47,6 @@ export class WorkspaceDocumentsService {
});
}

async findOneBySlug(userId: string, workspaceId: string, documentSlug: string) {
try {
await this.prismaService.userWorkspace.findFirstOrThrow({
where: {
userId,
workspaceId,
},
});

return this.prismaService.document.findFirstOrThrow({
where: {
slug: documentSlug,
},
});
} catch (e) {
throw new NotFoundException();
}
}

async findMany(
userId: string,
workspaceId: string,
Expand Down
8 changes: 7 additions & 1 deletion frontend/src/components/cards/DocumentCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,23 @@ import moment from "moment";
import { Card, CardActionArea, CardContent, Stack, Typography } from "@mui/material";
import AccessTimeIcon from "@mui/icons-material/AccessTime";
import { Document } from "../../hooks/api/types/document.d";
import { useNavigate } from "react-router-dom";

interface DocumentCardProps {
document: Document;
}

function DocumentCard(props: DocumentCardProps) {
const { document } = props;
const navigate = useNavigate();

const handleToDocument = () => {
navigate(`/document/${document.slug}`);
};

return (
<Card sx={{ width: "100%" }}>
<CardActionArea>
<CardActionArea onClick={handleToDocument}>
<CardContent>
<Typography variant="h5" component="div" noWrap>
{document.title}
Expand Down
6 changes: 2 additions & 4 deletions frontend/src/components/editor/Preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,14 @@ import { selectEditor } from "../../store/editorSlice";
import { CircularProgress } from "@mui/material";
import { useEffect, useState } from "react";
import "./editor.css";
import { useParams } from "react-router-dom";

function Preview() {
const params = useParams();
const currentTheme = useCurrentTheme();
const editorStore = useSelector(selectEditor);
const [content, setContent] = useState("");

useEffect(() => {
if (!editorStore.doc || !params.documentId) return;
if (!editorStore.doc) return;

setContent(editorStore.doc?.getRoot().content?.toString() || "");

Expand All @@ -26,7 +24,7 @@ function Preview() {
unsubsribe();
setContent("");
};
}, [editorStore.doc, params.documentId]);
}, [editorStore.doc]);

if (!editorStore?.doc) return <CircularProgress sx={{ marginX: "auto", mt: 4 }} />;

Expand Down
10 changes: 6 additions & 4 deletions frontend/src/components/popovers/WorkspaceListPopover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,11 @@ function WorkspaceListPopover(props: WorkspaceListPopoverProps) {
const { data: workspacePageList, hasNextPage, fetchNextPage } = useGetWorkspaceListQuery();
const { mutateAsync: createWorkspace } = useCreateWorkspaceMutation();
const workspaceList = useMemo(() => {
return workspacePageList?.pages.reduce((prev: Array<Workspace>, page) => {
return prev.concat(page.workspaces);
}, [] as Array<Workspace>);
return (
workspacePageList?.pages.reduce((prev: Array<Workspace>, page) => {
return prev.concat(page.workspaces);
}, [] as Array<Workspace>) ?? []
);
}, [workspacePageList?.pages]);
const [createWorkspaceModalOpen, setCreateWorkspaceModalOpen] = useState(false);

Expand Down Expand Up @@ -91,7 +93,7 @@ function WorkspaceListPopover(props: WorkspaceListPopoverProps) {
}
useWindow={false}
>
{workspaceList?.map((workspace) => (
{workspaceList.map((workspace) => (
<MenuItem
key={workspace.id}
onClick={() => handleMoveToSelectedWorkspace(workspace.slug)}
Expand Down
23 changes: 23 additions & 0 deletions frontend/src/hooks/api/document.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { useQuery } from "@tanstack/react-query";
import axios from "axios";
import { GetDocumentResponse } from "./types/document";

export const generateGetDocumentQueryKey = (documentSlug: string) => {
return ["documents", documentSlug];
};

export const useGetDocumentQuery = (documentSlug: string) => {
const query = useQuery({
queryKey: generateGetDocumentQueryKey(documentSlug || ""),
enabled: Boolean(documentSlug),
queryFn: async () => {
const res = await axios.get<GetDocumentResponse>(`/documents/${documentSlug}`);
return res.data;
},
meta: {
errorMessage: "This is a non-existent or unauthorized Workspace.",
},
});

return query;
};
2 changes: 2 additions & 0 deletions frontend/src/hooks/api/types/document.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ export class Document {
createdAt: Date;
updatedAt: Date;
}

export class GetDocumentResponse extends Document {}
4 changes: 2 additions & 2 deletions frontend/src/hooks/api/workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import {
GetWorkspaceResponse,
} from "./types/workspace";

export const generateGetWorkspaceQueryKey = (workspaceId: string) => {
return ["workspaces", workspaceId];
export const generateGetWorkspaceQueryKey = (workspaceSlug: string) => {
return ["workspaces", workspaceSlug];
};

export const generateGetWorkspaceListQueryKey = () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,26 +14,28 @@ import Resizable from "react-resizable-layout";
import { useWindowWidth } from "@react-hook/window-size";
import Preview from "../../components/editor/Preview";
import { useParams } from "react-router-dom";
import { useGetDocumentQuery } from "../../hooks/api/document";

function EditorIndex() {
const params = useParams();
const dispatch = useDispatch();
const windowWidth = useWindowWidth();
const editorStore = useSelector(selectEditor);
const params = useParams();
const { data: document } = useGetDocumentQuery(params.documentSlug || "");

useEffect(() => {
let client: yorkie.Client;
let doc: yorkie.Document<YorkieCodeMirrorDocType, YorkieCodeMirrorPresenceType>;

if (!params.documentId) return;
if (!document?.yorkieDocumentId) return;

const initializeYorkie = async () => {
client = new yorkie.Client(import.meta.env.VITE_YORKIE_API_ADDR, {
apiKey: import.meta.env.VITE_YORKIE_API_KEY,
});
await client.activate();

doc = new yorkie.Document(params.documentId as string);
doc = new yorkie.Document(document?.yorkieDocumentId as string);

await client.attach(doc, {
initialPresence: {
Expand All @@ -56,7 +58,7 @@ function EditorIndex() {

cleanUp();
};
}, [dispatch, params.documentId]);
}, [dispatch, document?.yorkieDocumentId]);

return (
<Box height="calc(100% - 64px)">
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/pages/workspace/Index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ function WorkspaceIndex() {
spacing={{ xs: 2, md: 3 }}
columns={{ xs: 4, sm: 8, md: 12, lg: 12 }}
>
{documentList.map((document, idx) => (
<Grid key={idx} item xs={4} sm={4} md={4} lg={3}>
{documentList.map((document) => (
<Grid key={document.id} item xs={4} sm={4} md={4} lg={3}>
<DocumentCard document={document} />
</Grid>
))}
Expand Down
8 changes: 4 additions & 4 deletions frontend/src/routes.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import EditorLayout from "./components/layouts/EditorLayout";
import EditorIndex from "./pages/editor/Index";
import EditorIndex from "./pages/document/Index";
import MainLayout from "./components/layouts/MainLayout";
import Index from "./pages/Index";
import CallbackIndex from "./pages/auth/callback/Index";
Expand Down Expand Up @@ -38,12 +38,12 @@ const codePairRoutes = [
],
},
{
path: ":documentId",
accessType: AccessType.PUBLIC,
path: "document",
accessType: AccessType.PRIVATE,
element: <EditorLayout />,
children: [
{
path: "",
path: ":documentSlug",
element: <EditorIndex />,
},
],
Expand Down

0 comments on commit db0e8c2

Please sign in to comment.