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/server/services/ltiDeepLinking.ts b/server/services/ltiDeepLinking.ts index 09f160605..677b504c9 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,11 @@ export async function index(req: FastifyRequest<{ Querystring: Query }>) { contentItems: [ { type: "ltiResourceLink", + title: book.name, + text: book.description, url: `${ FRONTEND_ORIGIN || `${req.protocol}://${req.hostname}` - }/book?bookId=${found.id}`, + }/book?bookId=${book.id}`, }, ], }); 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..bd3192406 --- /dev/null +++ b/utils/useBookLinkingHandlers.ts @@ -0,0 +1,92 @@ +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"; +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"; +import { pagesPath } from "./$path"; + +/** + * セッションのltiResourceLinkRequestからltiResourceLinkを作成 + * @param session セッション + * @return creatorIdとbookIdを除いたLTIリソースリンク + */ +function getLtiResourceLink( + session?: SessionSchema +): Omit | undefined { + if (session == null) return; + if (session.ltiResourceLinkRequest?.id == null) return; + + 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 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 (linking) { + await update(content.id, ltiResourceLink); + } else { + await destroy(ltiResourceLink); + } + + await revalidateContents(query); + }, + [update, destroy, 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) {