From 7e9f06bb8e92f60e7db52284d8d37d8a6f3b8184 Mon Sep 17 00:00:00 2001 From: Kohei Watanabe Date: Wed, 12 Jul 2023 14:12:46 +0900 Subject: [PATCH 1/4] =?UTF-8?q?refactor:=20useBookLinkingHandlers()=20?= =?UTF-8?q?=E3=81=AB=E3=83=AA=E3=83=B3=E3=82=AF=E6=8F=90=E4=BE=9B=E5=91=A8?= =?UTF-8?q?=E3=82=8A=E3=81=AE=E3=83=AD=E3=82=B8=E3=83=83=E3=82=AF=E3=82=92?= =?UTF-8?q?=E9=9B=86=E7=B4=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pages/book/edit/index.tsx | 6 ++-- pages/books/index.tsx | 30 ++--------------- utils/getLtiResourceLink.ts | 32 ------------------ utils/useBookLinkHandler.ts | 20 ----------- utils/useBookLinkingHandlers.ts | 59 +++++++++++++++++++++++++++++++++ utils/useBookNewHandlers.ts | 8 ++--- 6 files changed, 69 insertions(+), 86 deletions(-) delete mode 100644 utils/getLtiResourceLink.ts delete mode 100644 utils/useBookLinkHandler.ts create mode 100644 utils/useBookLinkingHandlers.ts diff --git a/pages/book/edit/index.tsx b/pages/book/edit/index.tsx index 4e70a60c3..1a3686af7 100644 --- a/pages/book/edit/index.tsx +++ b/pages/book/edit/index.tsx @@ -10,7 +10,7 @@ import Placeholder from "$templates/Placeholder"; import BookNotFoundProblem from "$templates/BookNotFoundProblem"; import { destroyBook, updateBook, useBook } from "$utils/book"; import { pagesPath } from "$utils/$path"; -import useBookLinkHandler from "$utils/useBookLinkHandler"; +import useBookLinkingHandlers from "$utils/useBookLinkingHandlers"; import useAuthorsHandler from "$utils/useAuthorsHandler"; export type Query = { @@ -23,7 +23,7 @@ function Edit({ bookId, context }: Query) { const { session, isContentEditable } = useSessionAtom(); const { book, error } = useBook(bookId, isContentEditable); const router = useRouter(); - const handleBookLink = useBookLinkHandler(); + const { onBookLinking } = useBookLinkingHandlers(); const { handleAuthorsUpdate, handleAuthorSubmit } = useAuthorsHandler( book && { type: "book", ...book } ); @@ -43,7 +43,7 @@ function Edit({ bookId, context }: Query) { ...props }: BookPropsWithSubmitOptions) { await updateBook({ id: bookId, ...props }); - if (submitWithLink) await handleBookLink({ id: bookId }); + if (submitWithLink) await onBookLinking({ id: bookId }); return back(); } async function handleDelete({ id }: Pick) { diff --git a/pages/books/index.tsx b/pages/books/index.tsx index aed7c0e73..dc6003848 100644 --- a/pages/books/index.tsx +++ b/pages/books/index.tsx @@ -8,14 +8,8 @@ import BookPreviewDialog from "$organisms/BookPreviewDialog"; import useBooks from "$utils/useBooks"; import useLinkedBook from "$utils/useLinkedBook"; import { pagesPath } from "$utils/$path"; -import { - updateLtiResourceLink, - destroyLtiResourceLink, -} from "$utils/ltiResourceLink"; -import getLtiResourceLink from "$utils/getLtiResourceLink"; import useDialogProps from "$utils/useDialogProps"; -import { useSearchAtom } from "$store/search"; -import { revalidateContents } from "utils/useContents"; +import useBookLinkingHandlers from "$utils/useBookLinkingHandlers"; const Books = ( props: Omit< @@ -26,14 +20,13 @@ const Books = ( function Index() { const router = useRouter(); - const { session, isContentEditable } = useSessionAtom(); + const { isContentEditable } = useSessionAtom(); const { linkedBook } = useLinkedBook(); const { data: previewContent, dispatch: onContentPreviewClick, ...dialogProps } = useDialogProps(); - const { query } = useSearchAtom(); const onContentEditClick = (book: Pick) => { const action = isContentEditable(book) ? "edit" : "generate"; return router.push( @@ -52,24 +45,7 @@ function Index() { pagesPath.books.import.$url({ query: { context: "books" } }) ); }; - const onContentLinkClick = async ( - content: ContentSchema, - checked: boolean - ) => { - const book = content as BookSchema; - const ltiResourceLink = getLtiResourceLink(session); - if (ltiResourceLink == null) return; - const bookId = book.id; - if (checked) { - await updateLtiResourceLink({ ...ltiResourceLink, bookId }); - } else { - const link = book.ltiResourceLinks.find( - ({ consumerId }) => consumerId === session?.oauthClient.id - ); - if (link) await destroyLtiResourceLink(link); - } - await revalidateContents(query); - }; + const { onBookLinking: onContentLinkClick } = useBookLinkingHandlers(); const handleLinkedBookClick = (book: Pick) => router.push(pagesPath.book.$url({ query: { bookId: book.id } })); const handlers = { diff --git a/utils/getLtiResourceLink.ts b/utils/getLtiResourceLink.ts deleted file mode 100644 index 2d94a280c..000000000 --- a/utils/getLtiResourceLink.ts +++ /dev/null @@ -1,32 +0,0 @@ -import type { SessionSchema } from "$server/models/session"; -import type { - LtiResourceLinkSchema, - LtiResourceLinkProps, -} from "$server/models/ltiResourceLink"; - -/** - * セッションからltiResourceLinkを作成 - * @param session セッション - * @return creatorIdとbookIdを除いたLTIリソースリンク - */ -function getLtiResourceLink( - session?: SessionSchema -): Omit< - LtiResourceLinkSchema & LtiResourceLinkProps, - "creatorId" | "bookId" -> | null { - if (session == null) return null; - - const ltiResourceLink = { - consumerId: session.oauthClient.id, - id: session?.ltiResourceLinkRequest?.id ?? "", - title: session?.ltiResourceLinkRequest?.title ?? "", - contextId: session.ltiContext.id, - contextTitle: session.ltiContext.title ?? "", - contextLabel: session.ltiContext.label ?? "", - }; - - return ltiResourceLink; -} - -export default getLtiResourceLink; diff --git a/utils/useBookLinkHandler.ts b/utils/useBookLinkHandler.ts deleted file mode 100644 index de5727b3b..000000000 --- a/utils/useBookLinkHandler.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { useCallback } from "react"; -import type { BookSchema } from "$server/models/book"; -import { useSessionAtom } from "$store/session"; -import { updateLtiResourceLink } from "$utils/ltiResourceLink"; -import getLtiResourceLink from "$utils/getLtiResourceLink"; - -function useBookLinkHandler() { - const { session } = useSessionAtom(); - const handler = useCallback( - async ({ id: bookId }: Pick) => { - const ltiResourceLink = getLtiResourceLink(session); - if (ltiResourceLink == null) return; - await updateLtiResourceLink({ ...ltiResourceLink, bookId }); - }, - [session] - ); - return handler; -} - -export default useBookLinkHandler; diff --git a/utils/useBookLinkingHandlers.ts b/utils/useBookLinkingHandlers.ts new file mode 100644 index 000000000..94a81d2b1 --- /dev/null +++ b/utils/useBookLinkingHandlers.ts @@ -0,0 +1,59 @@ +import { useCallback, useMemo } from "react"; +import type { ContentSchema } from "$server/models/content"; +import type { BookSchema } from "$server/models/book"; +import type { SessionSchema } from "$server/models/session"; +import type { LtiResourceLinkSchema } from "$server/models/ltiResourceLink"; +import { useSessionAtom } from "$store/session"; +import { useSearchAtom } from "$store/search"; +import { + destroyLtiResourceLink, + updateLtiResourceLink, +} from "$utils/ltiResourceLink"; +import { revalidateContents } from "./useContents"; + +/** + * セッションのltiResourceLinkRequestからltiResourceLinkを作成 + * @param session セッション + * @return creatorIdとbookIdを除いたLTIリソースリンク + */ +function getLtiResourceLink( + session?: SessionSchema +): Omit | null { + if (session == null) return null; + if (session.ltiResourceLinkRequest?.id == null) return null; + + const ltiResourceLink = { + consumerId: session.oauthClient.id, + id: session.ltiResourceLinkRequest.id, + title: session.ltiResourceLinkRequest.title ?? "", + contextId: session.ltiContext.id, + contextTitle: session.ltiContext.title ?? "", + contextLabel: session.ltiContext.label ?? "", + }; + + return ltiResourceLink; +} + +function useBookLinkingHandlers() { + const { session } = useSessionAtom(); + const ltiResourceLink = useMemo(() => getLtiResourceLink(session), [session]); + const { query } = useSearchAtom(); + const onBookLinking = useCallback( + async (content: Pick | ContentSchema, linking = true) => { + if ("type" in content && content.type !== "book") return; + if (ltiResourceLink == null) return; + + if (linking) { + await updateLtiResourceLink({ ...ltiResourceLink, bookId: content.id }); + } else { + await destroyLtiResourceLink(ltiResourceLink); + } + + await revalidateContents(query); + }, + [ltiResourceLink, query] + ); + return { onBookLinking }; +} + +export default useBookLinkingHandlers; diff --git a/utils/useBookNewHandlers.ts b/utils/useBookNewHandlers.ts index e16a72efd..a48323053 100644 --- a/utils/useBookNewHandlers.ts +++ b/utils/useBookNewHandlers.ts @@ -4,7 +4,7 @@ import type { BookSchema } from "$server/models/book"; import type { BookPropsWithSubmitOptions } from "$types/bookPropsWithSubmitOptions"; import { pagesPath } from "./$path"; import { createBook } from "./book"; -import useBookLinkHandler from "./useBookLinkHandler"; +import useBookLinkingHandlers from "./useBookLinkingHandlers"; import useAuthorsHandler from "$utils/useAuthorsHandler"; import { updateBookAuthors } from "./bookAuthors"; @@ -13,7 +13,7 @@ function useBookNewHandlers( bookId?: BookSchema["id"] ) { const router = useRouter(); - const handleBookLink = useBookLinkHandler(); + const { onBookLinking } = useBookLinkingHandlers(); const { handleAuthorsUpdate, handleAuthorSubmit } = useAuthorsHandler(); const handleSubmit = useCallback( async ({ @@ -32,7 +32,7 @@ function useBookNewHandlers( ...authors, ], }); - if (submitWithLink) await handleBookLink({ id: book.id }); + if (submitWithLink) await onBookLinking({ id: book.id }); await router.replace( pagesPath.book.edit.$url({ query: { @@ -42,7 +42,7 @@ function useBookNewHandlers( }) ); }, - [router, context, handleBookLink] + [router, context, onBookLinking] ); const handleCancel = useCallback(() => { switch (context) { From b34a36c2d9c00abfee08b9e6a33329c71496a87b Mon Sep 17 00:00:00 2001 From: Kohei Watanabe Date: Wed, 12 Jul 2023 14:46:14 +0900 Subject: [PATCH 2/4] =?UTF-8?q?feat:=20Deep=20Linking=20=E3=81=A7=E3=81=AE?= =?UTF-8?q?=E3=83=96=E3=83=83=E3=82=AF=E6=8F=90=E4=BE=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- utils/useBookLinkingHandlers.ts | 47 ++++++++++++++++++++++++++++----- 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/utils/useBookLinkingHandlers.ts b/utils/useBookLinkingHandlers.ts index 94a81d2b1..bd3192406 100644 --- a/utils/useBookLinkingHandlers.ts +++ b/utils/useBookLinkingHandlers.ts @@ -1,4 +1,5 @@ import { useCallback, useMemo } from "react"; +import { useRouter } from "next/router"; import type { ContentSchema } from "$server/models/content"; import type { BookSchema } from "$server/models/book"; import type { SessionSchema } from "$server/models/session"; @@ -10,6 +11,7 @@ import { updateLtiResourceLink, } from "$utils/ltiResourceLink"; import { revalidateContents } from "./useContents"; +import { pagesPath } from "./$path"; /** * セッションのltiResourceLinkRequestからltiResourceLinkを作成 @@ -18,9 +20,9 @@ import { revalidateContents } from "./useContents"; */ function getLtiResourceLink( session?: SessionSchema -): Omit | null { - if (session == null) return null; - if (session.ltiResourceLinkRequest?.id == null) return null; +): Omit | undefined { + if (session == null) return; + if (session.ltiResourceLinkRequest?.id == null) return; const ltiResourceLink = { consumerId: session.oauthClient.id, @@ -35,24 +37,55 @@ function getLtiResourceLink( } function useBookLinkingHandlers() { + const router = useRouter(); const { session } = useSessionAtom(); const ltiResourceLink = useMemo(() => getLtiResourceLink(session), [session]); const { query } = useSearchAtom(); + + const update = useCallback( + async ( + bookId: BookSchema["id"], + ltiResourceLink?: Omit + ) => { + if (session?.ltiMessageType === "LtiDeepLinkingRequest") { + await router.push(pagesPath.book.linking.$url({ query: { bookId } })); + return; + } + + if (ltiResourceLink) { + await updateLtiResourceLink({ ...ltiResourceLink, bookId }); + return; + } + }, + [router, session] + ); + + const destroy = useCallback( + async ( + ltiResourceLink?: Omit + ) => { + if (ltiResourceLink == null) return; + + await destroyLtiResourceLink(ltiResourceLink); + }, + [] + ); + const onBookLinking = useCallback( async (content: Pick | ContentSchema, linking = true) => { if ("type" in content && content.type !== "book") return; - if (ltiResourceLink == null) return; if (linking) { - await updateLtiResourceLink({ ...ltiResourceLink, bookId: content.id }); + await update(content.id, ltiResourceLink); } else { - await destroyLtiResourceLink(ltiResourceLink); + await destroy(ltiResourceLink); } await revalidateContents(query); }, - [ltiResourceLink, query] + [update, destroy, ltiResourceLink, query] ); + return { onBookLinking }; } From ec6f0385f1e6fbb24caf70a9d6cca06ea4c8c6bb Mon Sep 17 00:00:00 2001 From: Kohei Watanabe Date: Wed, 12 Jul 2023 14:54:50 +0900 Subject: [PATCH 3/4] =?UTF-8?q?feat:=20Deep=20Linking=20=E3=83=87=E3=83=95?= =?UTF-8?q?=E3=82=A9=E3=83=AB=E3=83=88=E3=81=AE=E6=B4=BB=E5=8B=95=E5=90=8D?= =?UTF-8?q?=E3=81=A8=E3=81=97=E3=81=A6=E3=83=96=E3=83=83=E3=82=AF=E5=90=8D?= =?UTF-8?q?=E3=82=92=E8=A8=AD=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/services/ltiDeepLinking.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/server/services/ltiDeepLinking.ts b/server/services/ltiDeepLinking.ts index 09f160605..c3218d2bd 100644 --- a/server/services/ltiDeepLinking.ts +++ b/server/services/ltiDeepLinking.ts @@ -3,7 +3,7 @@ import { outdent } from "outdent"; import authUser from "$server/auth/authUser"; import authInstructor from "$server/auth/authInstructor"; import type { FromSchema, JSONSchema } from "json-schema-to-ts"; -import bookExists from "$server/utils/book/bookExists"; +import findBook from "$server/utils/book/findBook"; import { FRONTEND_ORIGIN } from "$server/utils/env"; import { createPrivateKey } from "$server/utils/ltiv1p3/jwk"; import findClient from "$server/utils/ltiv1p3/findClient"; @@ -60,9 +60,9 @@ export async function index(req: FastifyRequest<{ Querystring: Query }>) { if (!privateKey) return { status: 403 }; - const found = await bookExists(req.query.book_id); + const book = await findBook(req.query.book_id, req.session.user.id); - if (!found) return { status: 404 }; + if (!book) return { status: 404 }; const jwt = await getDlResponseJwt(client, { privateKey, @@ -71,9 +71,10 @@ export async function index(req: FastifyRequest<{ Querystring: Query }>) { contentItems: [ { type: "ltiResourceLink", + title: book.name, url: `${ FRONTEND_ORIGIN || `${req.protocol}://${req.hostname}` - }/book?bookId=${found.id}`, + }/book?bookId=${book.id}`, }, ], }); From 0cf1bf8afe032e777f8e8fddf2de533ddf3b3332 Mon Sep 17 00:00:00 2001 From: Kohei Watanabe Date: Wed, 12 Jul 2023 16:23:00 +0900 Subject: [PATCH 4/4] =?UTF-8?q?feat:=20Deep=20Linking=20=E3=83=87=E3=83=95?= =?UTF-8?q?=E3=82=A9=E3=83=AB=E3=83=88=E3=81=AE=E8=AA=AC=E6=98=8E=E3=81=A8?= =?UTF-8?q?=E3=81=97=E3=81=A6=E3=83=96=E3=83=83=E3=82=AF=E8=AA=AC=E6=98=8E?= =?UTF-8?q?=E3=82=92=E8=A8=AD=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/services/ltiDeepLinking.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/server/services/ltiDeepLinking.ts b/server/services/ltiDeepLinking.ts index c3218d2bd..677b504c9 100644 --- a/server/services/ltiDeepLinking.ts +++ b/server/services/ltiDeepLinking.ts @@ -72,6 +72,7 @@ export async function index(req: FastifyRequest<{ Querystring: Query }>) { { type: "ltiResourceLink", title: book.name, + text: book.description, url: `${ FRONTEND_ORIGIN || `${req.protocol}://${req.hostname}` }/book?bookId=${book.id}`,