From 502305d01a9d93b56c7790a26dd8093e66508637 Mon Sep 17 00:00:00 2001 From: "nageuna922@gmail.com" Date: Sun, 8 Dec 2024 15:37:54 +0900 Subject: [PATCH 1/7] feat: document-search --- .../search/components/header-in-document.tsx | 56 +++-- .../search/components/recent-searches.tsx | 75 ++++++- .../search/components/search-item/index.tsx | 3 +- src/features/search/config/index.tsx | 1 + .../search/screen/search-in-document.tsx | 194 ++++++++++++++---- src/features/search/utils/index.tsx | 114 ++++++++++ src/requests/document/hooks.ts | 12 +- src/requests/document/index.tsx | 23 +++ src/shared/lib/tanstack-query/query-keys.ts | 6 + 9 files changed, 415 insertions(+), 69 deletions(-) create mode 100644 src/features/search/config/index.tsx create mode 100644 src/features/search/utils/index.tsx diff --git a/src/features/search/components/header-in-document.tsx b/src/features/search/components/header-in-document.tsx index 4fb3f922..84af43ce 100644 --- a/src/features/search/components/header-in-document.tsx +++ b/src/features/search/components/header-in-document.tsx @@ -1,34 +1,58 @@ +'use client' + import Icon from '@/shared/components/custom/icon' import { Input } from '@/shared/components/ui/input' import { useRouter } from 'next/navigation' -import { RefObject, useState } from 'react' +import { RefObject } from 'react' +import { ControllerRenderProps, useFormContext } from 'react-hook-form' interface Props { - searchHeaderRef: RefObject + field: ControllerRenderProps< + { + keyword: string + }, + 'keyword' + > + searchInputRef: RefObject isSearchFocused: boolean setIsSearchFocused: (value: boolean) => void + onDeleteKeyword: () => void } -const HeaderInDocument = ({ searchHeaderRef, isSearchFocused, setIsSearchFocused }: Props) => { +const HeaderInDocument = ({ + field, + searchInputRef, + isSearchFocused, + setIsSearchFocused, + onDeleteKeyword, +}: Props) => { const router = useRouter() - const [keyword, setKeyword] = useState('') + const { register } = useFormContext() + + const handleCancel = () => { + if (isSearchFocused) { + setIsSearchFocused(false) + return + } else { + router.push('/document') + } + } return ( -
+
setKeyword(e.target.value)} + {...register} + ref={searchInputRef} onFocus={() => setIsSearchFocused(true)} + value={field.value} + onChange={field.onChange} placeholder="노트명, 노트, 퀴즈 검색" className="h-[40px] placeholder:text-text-placeholder-01" variant={'round'} left={} right={ -
-
diff --git a/src/features/search/components/recent-searches.tsx b/src/features/search/components/recent-searches.tsx index 3927bd94..061f66be 100644 --- a/src/features/search/components/recent-searches.tsx +++ b/src/features/search/components/recent-searches.tsx @@ -1,21 +1,78 @@ +'use client' + import Icon from '@/shared/components/custom/icon' import Text from '@/shared/components/ui/text' +import { getRecentSearches } from '../utils' +import { RefObject, useEffect, useState } from 'react' +import { RECENT_SEARCHES } from '../config' + +interface Props { + containerRef: RefObject + onUpdateKeyword: (keyword: string) => void + // recentSearches: string[] + // handleDelete: (keyword: string) => void + // handleClearAll: () => void +} + +const RecentSearches = ({ + containerRef, + onUpdateKeyword, +}: // recentSearches, +// handleDelete, +// handleClearAll, +Props) => { + const [recentSearches, setRecentSearches] = useState([]) + + useEffect(() => { + const storageSearches = getRecentSearches() + setRecentSearches(storageSearches) + }, []) + + useEffect(() => { + // eslint-disable-next-line no-console + console.log('recentSearches changed: ', recentSearches) + }, [recentSearches]) + + const handleDelete = (keyword: string) => { + const updatedSearches = recentSearches.filter((search) => search !== keyword) + setRecentSearches(updatedSearches) + localStorage.setItem(RECENT_SEARCHES, JSON.stringify(updatedSearches)) + } + + const handleClearAll = () => { + setRecentSearches([]) + localStorage.removeItem(RECENT_SEARCHES) + } -const RecentSearches = () => { return ( -
-
+
+
최근 검색어 - +
-
- {Array.from({ length: 5 }).map((_, idx) => ( -
- 최근 검색어 {idx} -
diff --git a/src/features/search/components/search-item/index.tsx b/src/features/search/components/search-item/index.tsx index bf7e6594..9843a2ff 100644 --- a/src/features/search/components/search-item/index.tsx +++ b/src/features/search/components/search-item/index.tsx @@ -7,7 +7,7 @@ import DocumentTypeIcon from '@/features/document/components/document-type-icon' interface Props { createType: Document.ItemInList['documentType'] documentTitle: string - matchingSentence: string + matchingSentence: React.ReactNode resultType: 'document' | 'quiz' relativeDirectory: string lastItem?: boolean @@ -37,7 +37,6 @@ const SearchItem = ({ {documentTitle}
- {/* todo: 키워드와 일치하는 부분 색상 accent표시 하는 로직 필요 */} {matchingSentence}
diff --git a/src/features/search/config/index.tsx b/src/features/search/config/index.tsx new file mode 100644 index 00000000..fe751f86 --- /dev/null +++ b/src/features/search/config/index.tsx @@ -0,0 +1 @@ +export const RECENT_SEARCHES = 'recentSearches' diff --git a/src/features/search/screen/search-in-document.tsx b/src/features/search/screen/search-in-document.tsx index 2e50fce6..8b43d4ef 100644 --- a/src/features/search/screen/search-in-document.tsx +++ b/src/features/search/screen/search-in-document.tsx @@ -5,16 +5,52 @@ import SearchList from '../components/search-list' import SearchItem from '../components/search-item' import RecentSearches from '../components/recent-searches' import HeaderInDocument from '../components/header-in-document' +import * as z from 'zod' +import { zodResolver } from '@hookform/resolvers/zod' +import { useForm } from 'react-hook-form' +import { + extractPlainText, + getRecentSearches, + highlightAndTrimText, + saveRecentSearches, +} from '../utils' +import Text from '@/shared/components/ui/text' +import { useRouter, useSearchParams } from 'next/navigation' +import { useQuery } from '@tanstack/react-query' +import { queries } from '@/shared/lib/tanstack-query/query-keys' +import Loading from '@/shared/components/custom/loading' +import { Form, FormField } from '@/shared/components/ui/form' + +const searchSchema = z.object({ + keyword: z.string().trim().nonempty('검색어를 입력해주세요.'), +}) +type SearchFormValues = z.infer const SearchInDocument = () => { + const router = useRouter() + const keyword = useSearchParams().get('keyword') ?? '' + const [isSearchFocused, setIsSearchFocused] = useState(false) - const searchHeaderRef = useRef(null) + + const searchInputRef = useRef(null) const searchContainerRef = useRef(null) + const { data, isPending } = useQuery(queries.document.search({ keyword })) + const searchResults = [...(data?.documents ?? []), ...(data?.quizzes ?? [])] as Partial< + SearchedDocument & SearchedQuiz + >[] + + const form = useForm({ + resolver: zodResolver(searchSchema), + defaultValues: { + keyword: keyword ?? '', + }, + }) + useEffect(() => { const handleClickOutside = (e: MouseEvent) => { if ( - !searchHeaderRef.current?.contains(e.target as Node) && + !searchInputRef.current?.contains(e.target as Node) && !searchContainerRef.current?.contains(e.target as Node) ) { setIsSearchFocused(false) @@ -27,45 +63,127 @@ const SearchInDocument = () => { } }, []) + const handleUpdateKeyword = (keyword: string) => { + form.setValue('keyword', keyword) + searchInputRef.current?.focus() + } + + const handleDeleteKeyword = () => { + form.setValue('keyword', '') + router.push('/document/search') + } + + const onSubmit = (data: SearchFormValues) => { + const keyword = data.keyword + + const prevSearches = getRecentSearches() + saveRecentSearches(prevSearches, keyword) + + searchInputRef.current?.blur() + setIsSearchFocused(false) + + router.push(`?keyword=${keyword}`) + } + return ( - <> - - -
- {/* input 클릭 시 나타날 최근 검색어 */} - {isSearchFocused && } - - {/* 검색 결과 O : 검색 결과 리스트 */} - {!isSearchFocused && ( - - {Array.from({ length: 5 }).map((_, idx) => ( - +
+ + ( + + )} + /> + +
+ ( + <> + {/* input 클릭 시 나타날 최근 검색어 */} + {isSearchFocused && ( + + )} + + )} + /> + + {isPending && } + + {/* 검색 결과 O : 검색 결과 리스트 */} + {!isSearchFocused && + (!data || searchResults.length === 0 ? ( +
+ 검색결과가 없습니다 + + 다른 키워드를 입력해보세요 + +
+ ) : ( + data && + searchResults.length > 0 && ( + + {searchResults.map((searchItem, idx) => ( + + ) : ( + highlightAndTrimText( + searchItem.question ?? 'Q...' + searchItem.answer ?? 'A...', + keyword + ) + ) + } + resultType={searchItem.question ? 'quiz' : 'document'} + relativeDirectory={ + searchItem.directory + ? searchItem.directory.name + : searchItem.directoryName ?? '' + } + lastItem={idx === searchResults.length - 1} + /> + ))} + + ) ))} - - )} - - {/* 검색 결과 X : 검색 결과 리스트 */} - {/*
- 검색결과가 없습니다 - - 다른 키워드를 입력해보세요 - -
*/} -
- +
+ + ) } export default SearchInDocument + +/** 마크다운 텍스트를 받아 + * 문법을 제거하고 키워드에 강조를 해서 반환하는 함수 + */ +const MarkdownProcessor = ({ + markdownText, + keyword, +}: { + markdownText: string + keyword: string +}) => { + const plainText = extractPlainText(markdownText) + const highlightedText = highlightAndTrimText(plainText, keyword) + + return
{highlightedText}
+} diff --git a/src/features/search/utils/index.tsx b/src/features/search/utils/index.tsx new file mode 100644 index 00000000..af18fa94 --- /dev/null +++ b/src/features/search/utils/index.tsx @@ -0,0 +1,114 @@ +'use client' + +import Text from '@/shared/components/ui/text' +import React from 'react' +import { marked } from 'marked' +import { RECENT_SEARCHES } from '../config' + +/** 최근 검색어 저장 + * (5개까지만 저장가능하게 만든 함수입니다) */ +export const saveRecentSearches = (prevSearches: string[], keyword: string) => { + // eslint-disable-next-line no-console + console.log(prevSearches, keyword) + + if (!keyword || keyword.trim() === '') return + + let newSearchList = prevSearches.filter((search) => search !== keyword) + // eslint-disable-next-line no-console + console.log(newSearchList) + + newSearchList.unshift(keyword) + + if (newSearchList.length > 5) { + newSearchList = newSearchList.slice(0, 5) + } + + // eslint-disable-next-line no-console + console.log(newSearchList) + + const newSearches = { recentSearches: [...newSearchList] } + localStorage.setItem(RECENT_SEARCHES, JSON.stringify(newSearches)) + + // eslint-disable-next-line no-console + console.log( + 'Saved searches after save:', + JSON.parse(localStorage.getItem(RECENT_SEARCHES) ?? '저장된 값 없음') + ) +} + +/** 최근 검색어 가져오기 */ +export const getRecentSearches = () => { + const storageSearches = localStorage.getItem(RECENT_SEARCHES) + // eslint-disable-next-line no-console + console.log('Saved searches:', storageSearches) + + if (storageSearches) { + const parsedSearches = JSON.parse(storageSearches) as { recentSearches: string[] } + const searchList = parsedSearches.recentSearches + return searchList + } else { + return [] + } +} + +/** + * 텍스트에서 키워드를 강조하는 함수 + */ +export function highlightAndTrimText(text: string, keyword: string): JSX.Element | string { + if (!text) return text + + const totalLength = 70 + const regex = new RegExp(`(${keyword})`, 'gi') + const match = text.match(regex) + + if (!keyword || !match) { + // 키워드가 없으면 텍스트를 70자로 자르기 + const trimmedText = text.length > totalLength ? text.slice(0, totalLength) + '...' : text + return trimmedText + } + + // 첫 번째 매칭된 키워드의 위치 + const keywordIndex = text.toLowerCase().indexOf(keyword.toLowerCase()) + const keywordLength = keyword.length + + // 키워드 기준 앞뒤로 잘라낼 텍스트 길이 계산 + const surroundingLength = Math.floor((totalLength - keywordLength) / 2) + + const start = Math.max(0, keywordIndex - surroundingLength) + const end = Math.min(text.length, keywordIndex + keywordLength + surroundingLength) + + const trimmedText = text.slice(start, end) + + // 앞뒤가 잘린 경우 생략 표시 추가 + const prefix = start > 0 ? '...' : '' + const suffix = end < text.length ? '...' : '' + + // 텍스트 분리 및 강조 처리 + const parts = trimmedText.split(regex) + + return ( + <> + {prefix} + {parts.map((part, index) => + regex.test(part) ? ( + + {part} + + ) : ( + part + ) + )} + {suffix} + + ) +} + +export function extractPlainText(markdownText: string): string { + // 마크다운 -> HTML 변환 + const html = marked(markdownText, { headerIds: false, mangle: false }) + + // HTML -> 텍스트 추출 + const div = document.createElement('div') + div.innerHTML = html + return div.textContent || '' +} diff --git a/src/requests/document/hooks.ts b/src/requests/document/hooks.ts index 3e5e254e..4be8749c 100644 --- a/src/requests/document/hooks.ts +++ b/src/requests/document/hooks.ts @@ -3,7 +3,7 @@ import { useMutation } from '@tanstack/react-query' import { useSession } from 'next-auth/react' import { createDocument } from './create-document' -import { deleteDocument, fetchDocumentDetail, moveDocument } from '.' +import { deleteDocument, fetchDocumentDetail, moveDocument, searchDocument } from '.' import { queries } from '@/shared/lib/tanstack-query/query-keys' import { getQueryClient } from '@/shared/lib/tanstack-query/client' import { updateDocument } from './update-document' @@ -82,3 +82,13 @@ export const useDeleteDocument = (listOption?: { }, }) } + +/** + * 문서 검색 Hook + */ +export const useSearchDocument = () => { + return useMutation({ + mutationFn: async (requestBody: Document.Request.SearchDocuments) => + searchDocument(requestBody), + }) +} diff --git a/src/requests/document/index.tsx b/src/requests/document/index.tsx index 96aa961c..93feae70 100644 --- a/src/requests/document/index.tsx +++ b/src/requests/document/index.tsx @@ -94,3 +94,26 @@ export const deleteDocument = async (requestBody: Document.Request.DeleteDocumen throw error } } + +export const searchDocument = async (requestBody: Document.Request.SearchDocuments) => { + if (!requestBody.keyword || requestBody.keyword === '') return null + + try { + const session = await auth() + + const { data } = await http.post( + API_ENDPOINTS.DOCUMENT.POST.SEARCH, + requestBody, + { + headers: { + Authorization: `Bearer ${session?.user.accessToken}`, + }, + } + ) + + return data + } catch (error) { + console.error(error) + throw error + } +} diff --git a/src/shared/lib/tanstack-query/query-keys.ts b/src/shared/lib/tanstack-query/query-keys.ts index c991a56f..158f713d 100644 --- a/src/shared/lib/tanstack-query/query-keys.ts +++ b/src/shared/lib/tanstack-query/query-keys.ts @@ -24,6 +24,12 @@ export const queries = createQueryKeyStore({ queryFn: () => REQUEST.document.fetchDocumentDetail(documentId), enabled: !!documentId, }), + search: (requestBody: Document.Request.SearchDocuments) => ({ + queryKey: [requestBody], + queryFn: () => REQUEST.document.searchDocument(requestBody), + enabled: requestBody.keyword.trim() !== '', + initialData: { documents: [], quizzes: [] }, + }), }, quiz: { From 33ac86a16ec16c92c48ddae31eac1df2374e4cb2 Mon Sep 17 00:00:00 2001 From: "nageuna922@gmail.com" Date: Tue, 10 Dec 2024 11:27:39 +0900 Subject: [PATCH 2/7] fix: merge --- src/requests/collection/client.ts | 76 + src/requests/collection/server.ts | 15 + src/requests/directory/client.tsx | 80 + src/requests/document/client.tsx | 89 + src/requests/document/server.ts | 34 + src/requests/quiz/client.tsx | 120 + src/requests/quiz/server.tsx | 41 + src/requests/user/client.ts | 38 + src/requests/user/server.ts | 13 + src/shared/lib/axios/http-server.ts | 41 + src/shared/provider/auth-provider.tsx | 21 + src/store/auth.ts | 22 + src/types/schema.d.ts | 4300 +++++++++++++++++++++++++ 13 files changed, 4890 insertions(+) create mode 100644 src/requests/collection/client.ts create mode 100644 src/requests/collection/server.ts create mode 100644 src/requests/directory/client.tsx create mode 100644 src/requests/document/client.tsx create mode 100644 src/requests/document/server.ts create mode 100644 src/requests/quiz/client.tsx create mode 100644 src/requests/quiz/server.tsx create mode 100644 src/requests/user/client.ts create mode 100644 src/requests/user/server.ts create mode 100644 src/shared/lib/axios/http-server.ts create mode 100644 src/shared/provider/auth-provider.tsx create mode 100644 src/store/auth.ts create mode 100644 src/types/schema.d.ts diff --git a/src/requests/collection/client.ts b/src/requests/collection/client.ts new file mode 100644 index 00000000..f0b3baaf --- /dev/null +++ b/src/requests/collection/client.ts @@ -0,0 +1,76 @@ +'use client' + +import { API_ENDPOINTS } from '@/shared/configs/endpoint' +import { http } from '@/shared/lib/axios/http' + +export const getAllCollections = async () => { + try { + const { data } = await http.get( + API_ENDPOINTS.COLLECTION.GET.ALL + ) + return data + } catch (error) { + throw error + } +} + +export const getMyCollections = async () => { + try { + const { data } = await http.get( + API_ENDPOINTS.COLLECTION.GET.MY_COLLECTIONS + ) + return data + } catch (error) { + throw error + } +} + +export const getBookmarkedCollections = async () => { + try { + const { data } = await http.get( + API_ENDPOINTS.COLLECTION.GET.BOOKMARKED + ) + return data + } catch (error) { + throw error + } +} + +export const createCollection = async (payload: Collection.Request.CreateCollection) => { + try { + const { data } = await http.post( + API_ENDPOINTS.COLLECTION.POST.CREATE_COLLECTION, + payload + ) + return data + } catch (error) { + throw error + } +} + +export const createBookmark = async (collectionId: number) => { + try { + await http.post(API_ENDPOINTS.COLLECTION.POST.CREATE_BOOKMARK(collectionId)) + } catch (error) { + throw error + } +} + +export const deleteBookmark = async (collectionId: number) => { + try { + await http.delete(API_ENDPOINTS.COLLECTION.DELETE.BOOKMARK(collectionId)) + } catch (error) { + throw error + } +} + +export const getCollectionInfo = async ({ collectionId }: { collectionId: number }) => { + try { + const { data } = await http.get( + API_ENDPOINTS.COLLECTION.GET.INFO(collectionId) + ) + return data + } catch (error) { + throw error + } +} diff --git a/src/requests/collection/server.ts b/src/requests/collection/server.ts new file mode 100644 index 00000000..fdd1a545 --- /dev/null +++ b/src/requests/collection/server.ts @@ -0,0 +1,15 @@ +'use server' + +import { API_ENDPOINTS } from '@/shared/configs/endpoint' +import { httpServer } from '@/shared/lib/axios/http-server' + +export const getBookmarkedCollections = async () => { + try { + const { data } = await httpServer.get( + API_ENDPOINTS.COLLECTION.GET.BOOKMARKED + ) + return data + } catch (error) { + throw error + } +} diff --git a/src/requests/directory/client.tsx b/src/requests/directory/client.tsx new file mode 100644 index 00000000..9034393e --- /dev/null +++ b/src/requests/directory/client.tsx @@ -0,0 +1,80 @@ +'use client' + +import { API_ENDPOINTS } from '@/shared/configs/endpoint' +import { http } from '@/shared/lib/axios/http' + +/** + * 모든 디렉토리 가져오기 + */ +export const fetchDirectories = async () => { + try { + const { data } = await http.get( + API_ENDPOINTS.DIRECTORY.GET.ALL + ) + return data + } catch (error: unknown) { + console.error(error) + throw error + } +} + +/** + * directory_id로 디렉토리 가져오기 + */ +export const fetchDirectory = async (directoryId: Directory.Item['id']) => { + try { + const { data } = await http.get( + API_ENDPOINTS.DIRECTORY.GET.BY_ID(directoryId) + ) + return data + } catch (error: unknown) { + console.error(error) + throw error + } +} + +/** + * 디렉토리 생성 + */ +export const createDirectory = async ({ name, emoji }: Directory.Request.CreateDirectory) => { + try { + const { data } = await http.post( + API_ENDPOINTS.DIRECTORY.POST.CREATE, + { name, emoji } + ) + return data + } catch (error: unknown) { + console.error(error) + throw error + } +} + +/** + * 디렉토리 정보 변경 + */ +export const updateDirectoryInfo = async ({ + directoryId, + name, + emoji, +}: { + directoryId: Directory.Item['id'] +} & Directory.Request.UpdateDirectoryInfo) => { + try { + await http.patch(API_ENDPOINTS.DIRECTORY.PATCH.UPDATE_INFO(directoryId), { name, emoji }) + } catch (error: unknown) { + console.error(error) + throw error + } +} + +/** + * 디렉토리 삭제 + */ +export const deleteDirectory = async (directoryId: Directory.Item['id']) => { + try { + await http.delete(API_ENDPOINTS.DIRECTORY.DELETE.BY_ID(directoryId)) + } catch (error: unknown) { + console.error(error) + throw error + } +} diff --git a/src/requests/document/client.tsx b/src/requests/document/client.tsx new file mode 100644 index 00000000..b61774e5 --- /dev/null +++ b/src/requests/document/client.tsx @@ -0,0 +1,89 @@ +'use client' + +import { API_ENDPOINTS } from '@/shared/configs/endpoint' +import { http } from '@/shared/lib/axios/http' + +type GetDocumentsParams = { + directoryId?: string + sortOption?: Document.Sort +} + +export const fetchDocuments = async (params?: GetDocumentsParams) => { + const defaultSortOption = 'CREATED_AT' + + const DocsParams = + params?.directoryId == null + ? { 'sort-option': params?.sortOption ?? defaultSortOption } + : { + 'directory-id': params.directoryId, + 'sort-option': params.sortOption ?? defaultSortOption, + } + + try { + const { data } = await http.get<{ documents: Document.List }>(API_ENDPOINTS.DOCUMENT.GET.ALL, { + params: DocsParams, + }) + return data + } catch (error: unknown) { + console.error(error) + throw error + } +} + +// document id page 컴포넌트에서도 사용됨 수정 필요 +export const fetchDocumentDetail = async (documentId?: number) => { + if (documentId === null || documentId === undefined) return + + try { + const { data } = await http.get( + API_ENDPOINTS.DOCUMENT.GET.BY_ID(documentId) + ) + return data + } catch (error: unknown) { + console.error(error) + throw error + } +} + +export const moveDocument = async (requestBody: Document.Request.MoveDocument) => { + try { + const response = await http.patch(API_ENDPOINTS.DOCUMENT.PATCH.MOVE, requestBody) + + // eslint-disable-next-line no-console + console.log(response) // 디버깅용 + } catch (error: unknown) { + console.error(error) + throw error + } +} + +/** delete 메서드로 body를 받는 api입니다 (여러 문서 id를 리스트로 보냄) */ +export const deleteDocument = async (requestBody: Document.Request.DeleteDocuments) => { + try { + const response = await http.delete(API_ENDPOINTS.DOCUMENT.DELETE.DOCUMENTS, { + data: requestBody, + }) + + // eslint-disable-next-line no-console + console.log(response) // 디버깅용 + } catch (error) { + console.error(error) + throw error + } +} + +export const searchDocument = async (requestBody: Document.Request.SearchDocuments) => { + if (!requestBody.keyword || requestBody.keyword === '') return null + + try { + const { data } = await http.post( + API_ENDPOINTS.DOCUMENT.POST.SEARCH, + requestBody + ) + + return data + } catch (error) { + console.error(error) + throw error + } +} diff --git a/src/requests/document/server.ts b/src/requests/document/server.ts new file mode 100644 index 00000000..cb12aa07 --- /dev/null +++ b/src/requests/document/server.ts @@ -0,0 +1,34 @@ +'use server' + +import { API_ENDPOINTS } from '@/shared/configs/endpoint' +import { httpServer } from '@/shared/lib/axios/http-server' + +type GetDocumentsParams = { + directoryId?: string + sortOption?: Document.Sort +} + +export const fetchDocumentsServer = async (params?: GetDocumentsParams) => { + const defaultSortOption = 'CREATED_AT' + + const DocsParams = + params?.directoryId == null + ? { 'sort-option': params?.sortOption ?? defaultSortOption } + : { + 'directory-id': params.directoryId, + 'sort-option': params.sortOption ?? defaultSortOption, + } + + try { + const { data } = await httpServer.get<{ documents: Document.List }>( + API_ENDPOINTS.DOCUMENT.GET.ALL, + { + params: DocsParams, + } + ) + return data + } catch (error: unknown) { + console.error(error) + throw error + } +} diff --git a/src/requests/quiz/client.tsx b/src/requests/quiz/client.tsx new file mode 100644 index 00000000..9d31b0ff --- /dev/null +++ b/src/requests/quiz/client.tsx @@ -0,0 +1,120 @@ +'use client' + +import { API_ENDPOINTS } from '@/shared/configs/endpoint' +import { http } from '@/shared/lib/axios/http' + +export const fetchDirectoryQuizzes = async ({ directoryId }: { directoryId: number }) => { + try { + const { data } = await http.get( + API_ENDPOINTS.QUIZ.GET.BY_DIRECTORY(directoryId) + ) + return data + } catch (error: unknown) { + throw error + } +} + +// query-keys.ts에서 사용 중 +export const fetchDocumentQuizzes = async ({ + documentId, + quizType, +}: { + documentId: number + quizType?: Quiz.Type +}) => { + const params = quizType ? { 'quiz-type': quizType } : null + + try { + const { data } = await http.get( + API_ENDPOINTS.QUIZ.GET.BY_DOCUMENT(documentId), + { + params, + } + ) + return data + } catch (error: unknown) { + throw error + } +} + +// query-keys.ts에서 사용 중 +export const fetchWrongAnswerQuizzes = async () => { + try { + const { data } = await http.get( + API_ENDPOINTS.QUIZ.GET.WRONG_ANSWER + ) + return data + } catch (error: unknown) { + throw error + } +} + +// query-keys.ts에서 사용 중 +export const fetchQuizSetRecord = async ({ + quizSetId, + quizSetType, +}: { + quizSetId: string + quizSetType: Quiz.SetType +}) => { + try { + const { data } = await http.get( + API_ENDPOINTS.QUIZ.GET.RECORD(quizSetId, quizSetType) + ) + return data + } catch (error: unknown) { + throw error + } +} + +export const createQuizSetForCheck = async ({ documentId }: { documentId: number }) => { + try { + const { data } = await http.post( + API_ENDPOINTS.QUIZ.POST.CHECK_QUIZ_SET(documentId), + null + ) + return data + } catch (error: unknown) { + throw error + } +} + +export const createReplayDocumentQuizSet = async ({ + documentId, + requestBody, +}: { + documentId: number + requestBody: Quiz.Request.CreateReplayQuizSet +}) => { + try { + const { data } = await http.post( + API_ENDPOINTS.QUIZ.POST.REPLAY(documentId), + requestBody + ) + return data + } catch (error: unknown) { + throw error + } +} + +export const updateQuizResult = async (requestBody: Quiz.Request.UpdateQuizResult) => { + try { + const { data } = await http.patch( + API_ENDPOINTS.QUIZ.PATCH.UPDATE_RESULT, + requestBody + ) + return data + } catch (error: unknown) { + throw error + } +} + +export const updateWrongQuizResult = async (requestBody: Quiz.Request.UpdateWrongQuizResult) => { + try { + const response = await http.patch(API_ENDPOINTS.QUIZ.PATCH.UPDATE_WRONG_RESULT, requestBody) + // eslint-disable-next-line no-console + console.log(response) // 디버깅용 + } catch (error: unknown) { + throw error + } +} diff --git a/src/requests/quiz/server.tsx b/src/requests/quiz/server.tsx new file mode 100644 index 00000000..66d94071 --- /dev/null +++ b/src/requests/quiz/server.tsx @@ -0,0 +1,41 @@ +'use server' + +import { API_ENDPOINTS } from '@/shared/configs/endpoint' +import { httpServer } from '@/shared/lib/axios/http-server' + +export const fetchTodayQuizSetId = async () => { + try { + const { data } = await httpServer.get( + API_ENDPOINTS.QUIZ.GET.TODAY_SET + ) + return data + } catch (error: unknown) { + throw error + } +} + +export const fetchQuizSetById = async ({ + quizSetId, + collectionId, + quizSetType, +}: { + quizSetId: string + collectionId?: number + quizSetType: Quiz.SetType +}) => { + const params = collectionId + ? { 'collection-id': collectionId, 'quiz-set-type': quizSetType } + : { 'quiz-set-type': quizSetType } + + try { + const { data } = await httpServer.get( + API_ENDPOINTS.QUIZ.GET.BY_SET_ID(quizSetId), + { + params, + } + ) + return data + } catch (error: unknown) { + throw error + } +} diff --git a/src/requests/user/client.ts b/src/requests/user/client.ts new file mode 100644 index 00000000..bb130d19 --- /dev/null +++ b/src/requests/user/client.ts @@ -0,0 +1,38 @@ +'use client' + +import { API_ENDPOINTS } from '@/shared/configs/endpoint' +import { http } from '@/shared/lib/axios/http' + +export const updateTodayQuizCount = async (payload: User.Request.UpdateTodayQuizCount) => { + try { + await http.patch(API_ENDPOINTS.USER.PATCH.UPDATE_QUIZ_COUNT, payload) + } catch (error: unknown) { + throw error + } +} + +export const updateQuizNotification = async (payload: User.Request.UpdateQuizNotification) => { + try { + await http.patch(API_ENDPOINTS.USER.PATCH.UPDATE_NOTIFICATION, payload) + } catch (error: unknown) { + throw error + } +} + +export const updateUserName = async (payload: User.Request.UpdateName) => { + try { + await http.patch(API_ENDPOINTS.USER.PATCH.UPDATE_NAME, payload) + } catch (error: unknown) { + throw error + } +} + +export const updateCollectionCategories = async ( + payload: User.Request.UpdateCollectionCategories +) => { + try { + await http.patch(API_ENDPOINTS.USER.PATCH.UPDATE_COLLECTION_CATEGORIES, payload) + } catch (error: unknown) { + throw error + } +} diff --git a/src/requests/user/server.ts b/src/requests/user/server.ts new file mode 100644 index 00000000..ff29174d --- /dev/null +++ b/src/requests/user/server.ts @@ -0,0 +1,13 @@ +'use server' + +import { API_ENDPOINTS } from '@/shared/configs/endpoint' +import { httpServer } from '@/shared/lib/axios/http-server' + +export const fetchUserInfo = async () => { + try { + const { data } = await httpServer.get(API_ENDPOINTS.USER.GET.INFO) + return data + } catch (error: unknown) { + throw error + } +} diff --git a/src/shared/lib/axios/http-server.ts b/src/shared/lib/axios/http-server.ts new file mode 100644 index 00000000..34026b40 --- /dev/null +++ b/src/shared/lib/axios/http-server.ts @@ -0,0 +1,41 @@ +import { ServerEnv } from '@/actions/api-client/server-env' +import { auth } from '@/app/api/auth/[...nextauth]/auth' +import axios, { isAxiosError } from 'axios' + +export const httpServer = axios.create({ + baseURL: ServerEnv.apiUrl(), + headers: { + 'Content-Type': 'application/json', + }, +}) + +httpServer.interceptors.request.use( + async (config) => { + if (typeof window !== 'undefined') { + throw new Error('httpServer should only be used in server-side code.') + } + + const session = await auth() + const token = session?.user?.accessToken + + if (token) { + config.headers.Authorization = `Bearer ${token}` + } + return config + }, + (error) => { + return Promise.reject(error) + } +) + +httpServer.interceptors.response.use( + (response) => { + return response + }, + (error) => { + if (isAxiosError(error)) { + console.error(error.response?.data) + } + return Promise.reject(error) + } +) diff --git a/src/shared/provider/auth-provider.tsx b/src/shared/provider/auth-provider.tsx new file mode 100644 index 00000000..9601bdc2 --- /dev/null +++ b/src/shared/provider/auth-provider.tsx @@ -0,0 +1,21 @@ +'use client' + +import { useEffect } from 'react' +import { useSession } from 'next-auth/react' +import { useAuthStore } from '@/store/auth' + +/** 사용할 때 useToken */ +export const AuthProvider = ({ children }: { children: React.ReactNode }) => { + const { data: session } = useSession() + const setAccessToken = useAuthStore.getState().setAccessToken + + useEffect(() => { + if (session?.user?.accessToken) { + setAccessToken(session.user.accessToken) + } else { + setAccessToken(null) + } + }, [session, setAccessToken]) + + return <>{children} +} diff --git a/src/store/auth.ts b/src/store/auth.ts new file mode 100644 index 00000000..8275902f --- /dev/null +++ b/src/store/auth.ts @@ -0,0 +1,22 @@ +import { create } from 'zustand' +import { persist } from 'zustand/middleware' + +// 추가로 auth관련 데이터를 저장할 수 있음 + +interface AuthStore { + accessToken: string | null + setAccessToken: (token: string | null) => void +} + +export const useAuthStore = create( + persist( + (set) => ({ + accessToken: null, + setAccessToken: (token) => set({ accessToken: token }), + }), + { + name: 'auth-storage', // localStorage용 + partialize: (state) => ({ ...state, accessToken: state.accessToken }), + } + ) +) diff --git a/src/types/schema.d.ts b/src/types/schema.d.ts new file mode 100644 index 00000000..8b3f709f --- /dev/null +++ b/src/types/schema.d.ts @@ -0,0 +1,4300 @@ +/** + * This file was auto-generated by openapi-typescript. + * Do not make direct changes to the file. + */ + +export interface paths { + "/api/v2/tokens": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Fcm token 저장 */ + post: operations["saveFcmToken"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v2/test/create-today-quiz": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** 오늘의 퀴즈 생성 API(테스트 혹은 예외처리를 위한 API로서 실제 사용 X) */ + post: operations["createTodayQuizForTest"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v2/test/create-member": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["createMemberForTest"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v2/quizzes/documents/{document_id}/custom-quiz-set": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** 사용자가 생성한 문서에서 직접 퀴즈 세트 생성(랜덤, OX, 객관식) */ + post: operations["createMemberGeneratedQuizSet"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v2/quizzes/documents/{document_id}/check-quiz-set": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** 퀴즈 생성 후 퀴즈 오류 확인을 위한 퀴즈세트 생성(퀴즈 시작하기 후 모든 퀴즈 생성이 완료되면 요청) */ + post: operations["createErrorCheckQuizSet"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v2/payments/verify-amount": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** 결제금액 확인 */ + post: operations["verifyAmount"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v2/payments/temp-save": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** 주문과 결제금액 임시저장 */ + post: operations["tempSaveAmount"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v2/payments/confirm": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** 결제 승인 요청 */ + post: operations["confirmPayment"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v2/payments/cancel": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** 결제 취소 요청 */ + post: operations["cancelPayment"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v2/message/send": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** 메시지 전송 2 */ + post: operations["messageSendSend"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v2/message-send": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** 메시지 전송 1 */ + post: operations["messageSend"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v2/login": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** login */ + post: operations["login"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v2/integrated-search": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** 통합(문서, 컬렉션, 퀴즈) 검색 */ + post: operations["integratedSearchByKeyword"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v2/feedback": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Create Feedback */ + post: operations["createFeedback"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v2/documents": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** 문서 생성 */ + post: operations["createDocument"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v2/documents/search": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** 문서 검색 */ + post: operations["searchDocumentByKeyword"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v2/directories": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** 모든 디렉토리 가져오기 */ + get: operations["getDirectories"]; + put?: never; + /** 디렉토리 생성 */ + post: operations["createDirectory"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v2/collections": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** 모든 컬렉션 가져오기(탐색) */ + get: operations["getAllCollections"]; + put?: never; + /** 컬렉션 생성 */ + post: operations["createCollection"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v2/collections/{collection_id}/create-bookmark": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** 컬렉션 북마크하기 */ + post: operations["createCollectionBookmark"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v2/collections/{collection_id}/collection-quizzes": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** 컬렉션 퀴즈 시작하기 */ + post: operations["createCollectionQuizSet"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v2/auth/verification": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** 이메일 인증 코드 생성 및 발송 */ + post: operations["sendVerificationCode"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v2/auth/verification/check": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** 이메일 코드 인증 */ + post: operations["verifyVerificationCode"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v2/auth/invite-code/verify": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** 초대 코드 인증 */ + post: operations["verifyInviteCode"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v2/wrong-quiz/result": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + /** 오답 터뜨리기 결과 업데이트 */ + patch: operations["updateWrongQuizResult"]; + trace?: never; + }; + "/api/v2/random-quiz/result": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + /** 랜덤 퀴즈 결과 업데이트 */ + patch: operations["updateRandomQuizResult"]; + trace?: never; + }; + "/api/v2/quiz/result": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + /** 퀴즈 결과 업데이트 */ + patch: operations["updateQuizResult"]; + trace?: never; + }; + "/api/v2/members/update-today-quiz-count": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + /** 오늘의 퀴즈 관리(오늘의 퀴즈 개수 설정) */ + patch: operations["updateTodayQuizCount"]; + trace?: never; + }; + "/api/v2/members/update-quiz-notification": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + /** 사용자 퀴즈 알림 ON/OFF */ + patch: operations["updateQuizNotification"]; + trace?: never; + }; + "/api/v2/members/update-name": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + /** 사용자 이름 수정 */ + patch: operations["updateMemberName"]; + trace?: never; + }; + "/api/v2/members/update-collection-categories": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + /** 관심분야 태그 설정 */ + patch: operations["updateInterestCollectionCategories"]; + trace?: never; + }; + "/api/v2/documents/{document_id}/update-name": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + /** 문서 이름 변경 */ + patch: operations["updateDocumentName"]; + trace?: never; + }; + "/api/v2/documents/{document_id}/update-content": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + /** 문서 내용 업데이트 */ + patch: operations["changeDocumentContent"]; + trace?: never; + }; + "/api/v2/documents/today-quiz-settings": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + /** + * 오늘의 퀴즈 관리(문제를 가져올 노트 선택) + * @description Request map에서 key값은 number, value값은 boolean입니다. + */ + patch: operations["selectDocumentToNotGenerateByTodayQuiz"]; + trace?: never; + }; + "/api/v2/documents/move": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + /** 문서 다른 폴더로 옮기기 */ + patch: operations["moveDocumentToDirectory"]; + trace?: never; + }; + "/api/v2/directories/{directory_id}/update-info": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + /** 디렉토리 정보 변경 */ + patch: operations["updateDirectoryInfo"]; + trace?: never; + }; + "/api/v2/collections/{collection_id}/update-quizzes": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + /** 컬렉션 문제 편집 */ + patch: operations["updateCollectionQuizzes"]; + trace?: never; + }; + "/api/v2/collections/{collection_id}/update-info": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + /** 컬렉션 정보 수정 */ + patch: operations["updateCollectionInfo"]; + trace?: never; + }; + "/api/v2/collection/{collection_id}/add-quiz": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + /** + * 컬렉션에 퀴즈 추가 + * @description 노트 상세에서 특정 퀴즈를 특정 컬렉션에 추가 + */ + patch: operations["addQuizToCollection"]; + trace?: never; + }; + "/api/v2/today-quiz-info": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** 오늘의 퀴즈 현황 */ + get: operations["getCurrentTodayQuizInfo"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v2/quizzes/{quiz_set_id}/{quiz_set_type}/quiz-record": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** 퀴즈 세트에 대한 상세 기록 */ + get: operations["getSingleQuizSetRecord"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v2/quizzes/quiz-records": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** 전체 퀴즈 기록 */ + get: operations["getAllQuizzesAndCollectionRecords"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v2/quiz-sets/{quiz_set_id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** quiz_set_id와 quiz-set-type으로 퀴즈 가져오기 */ + get: operations["getQuizSetByCollection"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v2/quiz-sets/today": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** 오늘의 퀴즈 세트 정보 가져오기 */ + get: operations["getQuizSetToday"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v2/quiz-analysis": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** 퀴즈 분석 */ + get: operations["getQuizAnswerRateAnalysis"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v2/payments/{payment_key}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** paymentKey로 결제 조회 */ + get: operations["getPaymentByPaymentKey"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v2/payments/orders/{order_id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** orderId로 결제 조회 */ + get: operations["getPaymentByOrderId"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v2/oauth/url": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Oauth url api */ + get: operations["oauthUrlApi"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v2/notion/pages": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get notion pages */ + get: operations["getNotionPages"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v2/notion/page/{page_id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get notion page */ + get: operations["getNotionPage"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v2/notion/oauth": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["verifyNotion"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v2/notion/callback": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["notionCallback"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v2/members/info": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get member info */ + get: operations["getMemberInfo"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v2/incorrect-quizzes": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** 오답 터뜨리기 퀴즈 가져오기 */ + get: operations["getIncorrectQuizzes"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v2/health-check": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Health check */ + get: operations["healthCheck"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v2/documents/{document_id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** document_id로 문서 가져오기 */ + get: operations["getSingleDocument"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v2/documents/{document_id}/review-pick": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** document_id로 복습 pick 가져오기 */ + get: operations["getDocumentsNeedingReviewPick"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v2/documents/{document_id}/quizzes": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** document_id에 해당하는 모든 퀴즈 가져오기 */ + get: operations["getGeneratedQuizzes"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v2/documents/{document_id}/download-quiz": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** 퀴즈 다운로드 */ + get: operations["downloadQuizzes"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v2/documents/review-need-documents": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** 복습 필수 노트 top 5 */ + get: operations["getDocumentsNeedingReview"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v2/directories/{directory_id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** directory_id로 디렉토리 가져오기 */ + get: operations["getSingleDirectory"]; + put?: never; + post?: never; + /** 디렉토리 삭제 */ + delete: operations["deleteDirectory"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v2/directories/{directory_id}/quizzes": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** 디렉토리에 생성된 모든 퀴즈 랜덤하게 가져오기(랜덤 퀴즈) */ + get: operations["getAllQuizzesByMemberId"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v2/directories/documents": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** 모든 문서 가져오기 */ + get: operations["getAllDocuments"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v2/collections/{keyword}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** 컬렉션 검색하기 */ + get: operations["searchCollections"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v2/collections/{collection_id}/info": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** 컬렉션 상세 정보 가져오기 */ + get: operations["getCollectionInfoByCollectionId"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v2/collections/{collection_category}/quizzes": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** 북마크하거나 소유한 컬렉션 분야별로 모든 퀴즈 랜덤하게 가져오기 */ + get: operations["getQuizzesInCollectionByCollectionCategory"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v2/collections/my-collections": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** 직접 생성한 컬렉션 가져오기 */ + get: operations["getAllByMemberId"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v2/collections/interest-category-collection": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** 사용자 관심 분야 컬렉션 가져오기 */ + get: operations["getInterestCategoryCollections"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v2/collections/categories": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** 사용자가 북마크했거나 생성한 컬렉션 카테고리 가져오기 */ + get: operations["getCollectionCategories"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v2/collections/bookmarked-collections": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** 북마크한 컬렉션 가져오기 */ + get: operations["getAllByMemberIdAndBookmarked"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v2/collections-analysis": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** 컬렉션 분석 */ + get: operations["getCollectionAnalysis"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v2/callback": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Oauth callback */ + get: operations["googleLogin"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v2/auth/invite-link": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** 초대 링크 생성 */ + get: operations["createInviteLink"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v2/quizzes/{quiz_id}/invalid": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + /** 오류가 발생한 퀴즈 삭제 */ + delete: operations["deleteInvalidQuiz"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v2/quizzes/{quiz_id}/delete-quiz": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + /** 퀴즈 삭제 */ + delete: operations["deleteQuiz"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v2/documents/delete-documents": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + /** 문서 삭제 */ + delete: operations["deleteDocument"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v2/collections/{collection_id}/delete-collection": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + /** 컬렉션 삭제 */ + delete: operations["deleteCollection"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v2/collections/{collection_id}/delete-bookmark": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + /** 컬렉션 북마크 취소하기 */ + delete: operations["deleteCollectionBookmark"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; +} +export type webhooks = Record; +export interface components { + schemas: { + SaveFcmTokenRequest: { + fcmToken?: string; + }; + CreateQuizzesResponse: { + quizSetId?: string; + /** @enum {string} */ + quizSetType?: "TODAY_QUIZ_SET" | "DOCUMENT_QUIZ_SET" | "COLLECTION_QUIZ_SET" | "FIRST_QUIZ_SET"; + /** Format: date-time */ + createdAt?: string; + }; + CreateQuizzesByDocumentRequest: { + /** @enum {string} */ + quizType?: "MIX_UP" | "MULTIPLE_CHOICE"; + /** Format: int32 */ + quizCount?: number; + }; + SaveAmountRequest: { + orderId?: string; + /** Format: int32 */ + amount?: number; + }; + TossPaymentRequest: { + paymentKey?: string; + orderId?: string; + /** Format: int32 */ + amount?: number; + }; + CancelPaymentRequest: { + paymentKey?: string; + }; + FcmMessageDto: { + content?: string; + }; + FcmNotificationRequestDto: { + title?: string; + body?: string; + content?: string; + }; + LoginRequest: { + accessToken?: string; + /** @enum {string} */ + socialPlatform?: "KAKAO" | "GOOGLE"; + }; + LoginResponse: { + accessToken?: string; + /** Format: date-time */ + accessTokenExpiration?: string; + signUp?: boolean; + }; + SearchRequest: { + keyword?: string; + }; + IntegratedSearchCollectionDto: { + /** Format: int64 */ + id?: number; + name?: string; + emoji?: string; + /** Format: int32 */ + bookmarkCount?: number; + /** @enum {string} */ + collectionCategory?: "IT" | "LAW" | "BUSINESS_ECONOMY" | "SOCIETY_POLITICS" | "LANGUAGE" | "MEDICINE_PHARMACY" | "ART" | "SCIENCE_ENGINEERING" | "HISTORY_PHILOSOPHY" | "OTHER"; + memberName?: string; + /** Format: int32 */ + quizCount?: number; + }; + IntegratedSearchDirectoryDto: { + /** Format: int64 */ + id?: number; + name?: string; + }; + IntegratedSearchDocumentDto: { + /** Format: int64 */ + documentId?: number; + documentName?: string; + content?: string; + directory?: components["schemas"]["IntegratedSearchDirectoryDto"]; + }; + IntegratedSearchQuizDto: { + /** Format: int64 */ + id?: number; + question?: string; + answer?: string; + documentName?: string; + directoryName?: string; + }; + IntegratedSearchResponse: { + documents?: components["schemas"]["IntegratedSearchDocumentDto"][]; + quizzes?: components["schemas"]["IntegratedSearchQuizDto"][]; + collections?: components["schemas"]["IntegratedSearchCollectionDto"][]; + }; + CreateFeedbackRequest: { + files?: string[]; + title?: string; + content?: string; + /** @enum {string} */ + type?: "ERROR" | "PAYMENT" | "PARTNERSHIP" | "EVENT" | "ACCOUNT_INFO" | "CANCELLATION" | "OTHER"; + email?: string; + }; + CreateDocumentRequest: { + /** Format: binary */ + file?: string; + directoryId?: string; + documentName?: string; + star?: string; + /** @enum {string} */ + quizType?: "MIX_UP" | "MULTIPLE_CHOICE"; + /** @enum {string} */ + documentType?: "FILE" | "TEXT" | "NOTION"; + }; + CreateDocumentResponse: { + /** Format: int64 */ + id?: number; + }; + SearchDocumentDirectoryDto: { + /** Format: int64 */ + id?: number; + name?: string; + }; + SearchDocumentDto: { + /** Format: int64 */ + documentId?: number; + documentName?: string; + content?: string; + directory?: components["schemas"]["SearchDocumentDirectoryDto"]; + }; + SearchDocumentQuizDto: { + /** Format: int64 */ + id?: number; + question?: string; + answer?: string; + documentName?: string; + directoryName?: string; + }; + SearchDocumentResponse: { + documents?: components["schemas"]["SearchDocumentDto"][]; + quizzes?: components["schemas"]["SearchDocumentQuizDto"][]; + }; + CreateDirectoryRequest: { + name?: string; + emoji?: string; + }; + CreateDirectoryResponse: { + /** Format: int64 */ + id?: number; + }; + CreateCollectionRequest: { + /** @example 컬렉션 제목 */ + name?: string; + /** @example 📙 */ + emoji?: string; + /** @example 컬렉션 설명 */ + description?: string; + /** + * @example IT, LAW, BUSINESS_ECONOMY, SOCIETY_POLITICS, LANGUAGE, MEDICINE_PHARMACY, ART, SCIENCE_ENGINEERING, HISTORY_PHILOSOPHY, OTHER + * @enum {string} + */ + collectionCategory?: "IT" | "LAW" | "BUSINESS_ECONOMY" | "SOCIETY_POLITICS" | "LANGUAGE" | "MEDICINE_PHARMACY" | "ART" | "SCIENCE_ENGINEERING" | "HISTORY_PHILOSOPHY" | "OTHER"; + quizzes?: number[]; + }; + CreateCollectionResponse: { + /** Format: int64 */ + collectionId?: number; + }; + SendVerificationCodeRequest: { + email?: string; + }; + VerifyVerificationCodeRequest: { + email?: string; + verificationCode?: string; + }; + VerifyInviteCode: { + inviteCode?: string; + }; + UpdateRandomQuizResultDto: { + /** Format: int64 */ + id?: number; + answer?: boolean; + }; + UpdateRandomQuizResultRequest: { + quizzes?: components["schemas"]["UpdateRandomQuizResultDto"][]; + }; + UpdateQuizResultQuizDto: { + /** Format: int64 */ + id?: number; + answer?: boolean; + choseAnswer?: string; + /** Format: int32 */ + elapsedTime?: number; + }; + UpdateQuizResultRequest: { + quizSetId?: string; + quizzes?: components["schemas"]["UpdateQuizResultQuizDto"][]; + }; + UpdateQuizResultResponse: { + /** Format: int32 */ + reward?: number; + /** Format: int32 */ + currentConsecutiveTodayQuizDate?: number; + }; + UpdateTodayQuizCountRequest: { + /** Format: int32 */ + todayQuizCount?: number; + }; + UpdateQuizNotificationRequest: { + quizNotificationEnabled?: boolean; + }; + UpdateMemberNameRequest: { + name?: string; + }; + UpdateInterestCollectionCategoriesRequest: { + interestCollectionCategories?: ("IT" | "LAW" | "BUSINESS_ECONOMY" | "SOCIETY_POLITICS" | "LANGUAGE" | "MEDICINE_PHARMACY" | "ART" | "SCIENCE_ENGINEERING" | "HISTORY_PHILOSOPHY" | "OTHER")[]; + }; + UpdateDocumentNameRequest: { + name?: string; + }; + UpdateDocumentContentRequest: { + name?: string; + /** Format: binary */ + file?: string; + }; + UpdateTodayQuizSettingsRequest: { + /** + * @description 문서 ID와 퀴즈 설정 값 (true/false) 맵 + * @example { + * "1": true, + * "2": false, + * "3": true + * } + */ + documentIdTodayQuizMap?: { + [key: string]: boolean; + }; + }; + MoveDocumentToDirectoryRequest: { + documentIds?: number[]; + /** Format: int64 */ + directoryId?: number; + }; + UpdateDirectoryInfoRequest: { + name?: string; + emoji?: string; + }; + UpdateCollectionQuizzesRequest: { + quizzes?: number[]; + }; + UpdateCollectionInfoRequest: { + /** @example 컬렉션 제목 */ + name?: string; + /** @example 📙 */ + emoji?: string; + /** @example 컬렉션 설명 */ + description?: string; + /** + * @example IT, LAW, BUSINESS_ECONOMY, SOCIETY_POLITICS, LANGUAGE, MEDICINE_PHARMACY, ART, SCIENCE_ENGINEERING, HISTORY_PHILOSOPHY, OTHER + * @enum {string} + */ + collectionCategory?: "IT" | "LAW" | "BUSINESS_ECONOMY" | "SOCIETY_POLITICS" | "LANGUAGE" | "MEDICINE_PHARMACY" | "ART" | "SCIENCE_ENGINEERING" | "HISTORY_PHILOSOPHY" | "OTHER"; + }; + AddQuizToCollectionRequest: { + /** Format: int64 */ + quizId?: number; + }; + GetCurrentTodayQuizInfo: { + /** Format: int32 */ + currentConsecutiveDays?: number; + /** Format: int32 */ + maxConsecutiveDays?: number; + }; + GetSingleQuizSetRecordDto: { + question?: string; + answer?: string; + explanation?: string; + options?: string[]; + choseAnswer?: string; + documentName?: string; + directoryName?: string; + collectionName?: string; + /** @enum {string} */ + quizSetType?: "TODAY_QUIZ_SET" | "DOCUMENT_QUIZ_SET" | "COLLECTION_QUIZ_SET" | "FIRST_QUIZ_SET"; + }; + GetSingleQuizSetRecordResponse: { + /** Format: int32 */ + totalElapsedTimeMs?: number; + quizzes?: components["schemas"]["GetSingleQuizSetRecordDto"][]; + /** Format: date-time */ + createdAt?: string; + }; + GetQuizRecordDto: { + quizSetId?: string; + name?: string; + /** Format: int32 */ + quizCount?: number; + /** Format: int32 */ + score?: number; + /** @enum {string} */ + quizSetType?: "TODAY_QUIZ_SET" | "DOCUMENT_QUIZ_SET" | "COLLECTION_QUIZ_SET" | "FIRST_QUIZ_SET"; + /** Format: date-time */ + solvedDate?: string; + }; + GetQuizRecordResponse: { + /** Format: int32 */ + currentConsecutiveDays?: number; + /** Format: int32 */ + maxConsecutiveDays?: number; + quizRecords?: components["schemas"]["GetQuizRecordDto"][]; + }; + GetQuizSetDirectoryDto: { + /** Format: int64 */ + id?: number; + name?: string; + }; + GetQuizSetDocumentDto: { + /** Format: int64 */ + id?: number; + name?: string; + }; + GetQuizSetQuizDto: { + /** Format: int64 */ + id?: number; + question?: string; + answer?: string; + explanation?: string; + options?: string[]; + /** @enum {string} */ + quizType?: "MIX_UP" | "MULTIPLE_CHOICE"; + document?: components["schemas"]["GetQuizSetDocumentDto"]; + directory?: components["schemas"]["GetQuizSetDirectoryDto"]; + }; + GetQuizSetResponse: { + quizzes?: components["schemas"]["GetQuizSetQuizDto"][]; + collectionName?: string; + }; + GetQuizSetTodayResponse: { + quizSetId?: string; + /** @enum {string} */ + quizSetType?: "TODAY_QUIZ_SET" | "DOCUMENT_QUIZ_SET" | "COLLECTION_QUIZ_SET" | "FIRST_QUIZ_SET"; + /** @enum {string} */ + type?: "READY" | "NOT_READY" | "DONE"; + /** Format: date-time */ + createdAt?: string; + }; + GetQuizAnswerRateAnalysisResponse: { + /** Format: int32 */ + totalElapsedTime?: number; + quizzes?: components["schemas"]["QuizAnswerRateAnalysisDto"][]; + }; + QuizAnswerRateAnalysisDto: { + /** Format: date */ + date?: string; + /** Format: int32 */ + quizCount?: number; + /** Format: int32 */ + incorrectAnswerCount?: number; + }; + ApplicationContext: { + parent?: components["schemas"]["ApplicationContext"]; + id?: string; + displayName?: string; + applicationName?: string; + /** Format: int64 */ + startupDate?: number; + autowireCapableBeanFactory?: components["schemas"]["AutowireCapableBeanFactory"]; + environment?: components["schemas"]["Environment"]; + /** Format: int32 */ + beanDefinitionCount?: number; + beanDefinitionNames?: string[]; + parentBeanFactory?: components["schemas"]["BeanFactory"]; + classLoader?: { + name?: string; + registeredAsParallelCapable?: boolean; + parent?: { + name?: string; + registeredAsParallelCapable?: boolean; + unnamedModule?: { + name?: string; + classLoader?: { + name?: string; + registeredAsParallelCapable?: boolean; + definedPackages?: { + name?: string; + annotations?: Record[]; + declaredAnnotations?: Record[]; + sealed?: boolean; + specificationTitle?: string; + specificationVersion?: string; + specificationVendor?: string; + implementationTitle?: string; + implementationVersion?: string; + implementationVendor?: string; + }[]; + defaultAssertionStatus?: boolean; + }; + descriptor?: { + open?: boolean; + automatic?: boolean; + }; + named?: boolean; + annotations?: Record[]; + declaredAnnotations?: Record[]; + packages?: string[]; + nativeAccessEnabled?: boolean; + layer?: Record; + }; + definedPackages?: { + name?: string; + annotations?: Record[]; + declaredAnnotations?: Record[]; + sealed?: boolean; + specificationTitle?: string; + specificationVersion?: string; + specificationVendor?: string; + implementationTitle?: string; + implementationVersion?: string; + implementationVendor?: string; + }[]; + defaultAssertionStatus?: boolean; + }; + unnamedModule?: { + name?: string; + classLoader?: { + name?: string; + registeredAsParallelCapable?: boolean; + definedPackages?: { + name?: string; + annotations?: Record[]; + declaredAnnotations?: Record[]; + sealed?: boolean; + specificationTitle?: string; + specificationVersion?: string; + specificationVendor?: string; + implementationTitle?: string; + implementationVersion?: string; + implementationVendor?: string; + }[]; + defaultAssertionStatus?: boolean; + }; + descriptor?: { + open?: boolean; + automatic?: boolean; + }; + named?: boolean; + annotations?: Record[]; + declaredAnnotations?: Record[]; + packages?: string[]; + nativeAccessEnabled?: boolean; + layer?: Record; + }; + definedPackages?: { + name?: string; + annotations?: Record[]; + declaredAnnotations?: Record[]; + sealed?: boolean; + specificationTitle?: string; + specificationVersion?: string; + specificationVendor?: string; + implementationTitle?: string; + implementationVersion?: string; + implementationVendor?: string; + }[]; + defaultAssertionStatus?: boolean; + }; + }; + AutowireCapableBeanFactory: Record; + BeanFactory: Record; + Environment: { + activeProfiles?: string[]; + defaultProfiles?: string[]; + }; + FilterRegistration: { + servletNameMappings?: string[]; + urlPatternMappings?: string[]; + name?: string; + className?: string; + initParameters?: { + [key: string]: string; + }; + }; + HttpStatusCode: { + error?: boolean; + is4xxClientError?: boolean; + is5xxServerError?: boolean; + is1xxInformational?: boolean; + is2xxSuccessful?: boolean; + is3xxRedirection?: boolean; + }; + JspConfigDescriptor: { + taglibs?: components["schemas"]["TaglibDescriptor"][]; + jspPropertyGroups?: components["schemas"]["JspPropertyGroupDescriptor"][]; + }; + JspPropertyGroupDescriptor: { + defaultContentType?: string; + deferredSyntaxAllowedAsLiteral?: string; + elIgnored?: string; + errorOnELNotFound?: string; + pageEncoding?: string; + scriptingInvalid?: string; + isXml?: string; + includePreludes?: string[]; + includeCodas?: string[]; + trimDirectiveWhitespaces?: string; + errorOnUndeclaredNamespace?: string; + buffer?: string; + urlPatterns?: string[]; + }; + RedirectView: { + applicationContext?: components["schemas"]["ApplicationContext"]; + servletContext?: components["schemas"]["ServletContext"]; + contentType?: string; + requestContextAttribute?: string; + staticAttributes?: { + [key: string]: Record; + }; + exposePathVariables?: boolean; + exposeContextBeansAsAttributes?: boolean; + exposedContextBeanNames?: string[]; + beanName?: string; + url?: string; + contextRelative?: boolean; + http10Compatible?: boolean; + exposeModelAttributes?: boolean; + encodingScheme?: string; + statusCode?: components["schemas"]["HttpStatusCode"]; + expandUriTemplateVariables?: boolean; + propagateQueryParams?: boolean; + hosts?: string[]; + redirectView?: boolean; + propagateQueryProperties?: boolean; + attributesMap?: { + [key: string]: Record; + }; + attributes?: { + [key: string]: string; + }; + attributesCSV?: string; + }; + ServletContext: { + sessionCookieConfig?: components["schemas"]["SessionCookieConfig"]; + virtualServerName?: string; + classLoader?: { + name?: string; + registeredAsParallelCapable?: boolean; + definedPackages?: { + name?: string; + annotations?: Record[]; + declaredAnnotations?: Record[]; + sealed?: boolean; + specificationTitle?: string; + specificationVersion?: string; + specificationVendor?: string; + implementationTitle?: string; + implementationVersion?: string; + implementationVendor?: string; + }[]; + defaultAssertionStatus?: boolean; + }; + /** Format: int32 */ + majorVersion?: number; + /** Format: int32 */ + minorVersion?: number; + attributeNames?: Record; + contextPath?: string; + initParameterNames?: Record; + sessionTrackingModes?: ("COOKIE" | "URL" | "SSL")[]; + /** Format: int32 */ + sessionTimeout?: number; + servletRegistrations?: { + [key: string]: components["schemas"]["ServletRegistration"]; + }; + defaultSessionTrackingModes?: ("COOKIE" | "URL" | "SSL")[]; + /** Format: int32 */ + effectiveMajorVersion?: number; + /** Format: int32 */ + effectiveMinorVersion?: number; + serverInfo?: string; + servletContextName?: string; + filterRegistrations?: { + [key: string]: components["schemas"]["FilterRegistration"]; + }; + effectiveSessionTrackingModes?: ("COOKIE" | "URL" | "SSL")[]; + jspConfigDescriptor?: components["schemas"]["JspConfigDescriptor"]; + requestCharacterEncoding?: string; + responseCharacterEncoding?: string; + }; + ServletRegistration: { + mappings?: string[]; + runAsRole?: string; + name?: string; + className?: string; + initParameters?: { + [key: string]: string; + }; + }; + SessionCookieConfig: { + /** Format: int32 */ + maxAge?: number; + domain?: string; + httpOnly?: boolean; + path?: string; + secure?: boolean; + name?: string; + attributes?: { + [key: string]: string; + }; + /** @deprecated */ + comment?: string; + }; + TaglibDescriptor: { + taglibURI?: string; + taglibLocation?: string; + }; + GetMemberInfoDocumentDto: { + /** Format: int32 */ + possessDocumentCount?: number; + /** Format: int32 */ + maxPossessDocumentCount?: number; + }; + GetMemberInfoResponse: { + /** Format: int64 */ + id?: number; + name?: string; + email?: string; + /** @enum {string} */ + socialPlatform?: "KAKAO" | "GOOGLE"; + /** @enum {string} */ + role?: "ROLE_USER" | "ROLE_ADMIN"; + interestCategories?: string[]; + documentUsage?: components["schemas"]["GetMemberInfoDocumentDto"]; + /** Format: int32 */ + star?: number; + quizNotificationEnabled?: boolean; + }; + DirectoryDto: { + /** Format: int64 */ + id?: number; + name?: string; + }; + DocumentDto: { + /** Format: int64 */ + id?: number; + name?: string; + }; + QuizDto: { + /** Format: int64 */ + id?: number; + question?: string; + answer?: string; + explanation?: string; + options?: string[]; + /** @enum {string} */ + quizType?: "MIX_UP" | "MULTIPLE_CHOICE"; + document?: components["schemas"]["DocumentDto"]; + directory?: components["schemas"]["DirectoryDto"]; + }; + QuizResponseDto: { + quizzes?: components["schemas"]["QuizDto"][]; + }; + GetSingleDocumentDirectoryDto: { + /** Format: int64 */ + id?: number; + name?: string; + emoji?: string; + }; + GetSingleDocumentQuizDto: { + /** Format: int64 */ + id?: number; + question?: string; + answer?: string; + explanation?: string; + options?: string[]; + /** @enum {string} */ + quizType?: "MIX_UP" | "MULTIPLE_CHOICE"; + }; + GetSingleDocumentResponse: { + /** Format: int64 */ + id?: number; + documentName?: string; + /** @enum {string} */ + status?: "UNPROCESSED" | "PROCESSED" | "PROCESSING" | "COMPLETELY_FAILED" | "PARTIAL_SUCCESS" | "DEFAULT_DOCUMENT"; + content?: string; + /** Format: int32 */ + characterCount?: number; + /** Format: int32 */ + totalQuizCount?: number; + /** Format: date-time */ + updatedAt?: string; + directory?: components["schemas"]["GetSingleDocumentDirectoryDto"]; + quizzes?: components["schemas"]["GetSingleDocumentQuizDto"][]; + }; + GetDocumentsNeedingReviewPickResponse: { + quizzes?: components["schemas"]["GetReviewQuizDto"][]; + }; + GetReviewQuizDto: { + /** Format: int64 */ + id?: number; + question?: string; + answer?: string; + explanation?: string; + options?: string[]; + /** @enum {string} */ + quizType?: "MIX_UP" | "MULTIPLE_CHOICE"; + description?: string; + }; + GetDocumentsNeedingReviewResponse: { + documents?: components["schemas"]["GetReviewNeededDocumentsDto"][]; + }; + GetReviewNeededDocumentsDirectoryDto: { + /** Format: int64 */ + id?: number; + name?: string; + }; + GetReviewNeededDocumentsDto: { + /** Format: int64 */ + id?: number; + name?: string; + /** Format: int32 */ + reviewNeededQuizCount?: number; + directory?: components["schemas"]["GetReviewNeededDocumentsDirectoryDto"]; + }; + GetAllDirectoriesDirectoryDto: { + /** Format: int64 */ + id?: number; + name?: string; + /** @enum {string} */ + tag?: "DEFAULT" | "NORMAL"; + emoji?: string; + /** Format: int32 */ + documentCount?: number; + }; + GetAllDirectoriesResponse: { + directories?: components["schemas"]["GetAllDirectoriesDirectoryDto"][]; + }; + GetSingleDirectoryResponse: { + /** Format: int64 */ + id?: number; + name?: string; + emoji?: string; + /** @enum {string} */ + tag?: "DEFAULT" | "NORMAL"; + }; + GetAllDocumentsDirectoryDto: { + name?: string; + /** @enum {string} */ + tag?: "DEFAULT" | "NORMAL"; + }; + GetAllDocumentsDocumentDto: { + /** Format: int64 */ + id?: number; + name?: string; + previewContent?: string; + /** Format: int32 */ + characterCount?: number; + /** @enum {string} */ + status?: "UNPROCESSED" | "PROCESSED" | "PROCESSING" | "COMPLETELY_FAILED" | "PARTIAL_SUCCESS" | "DEFAULT_DOCUMENT"; + /** Format: int32 */ + totalQuizCount?: number; + /** @enum {string} */ + documentType?: "FILE" | "TEXT" | "NOTION"; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + updatedAt?: string; + /** Format: int32 */ + reviewNeededQuizCount?: number; + directory?: components["schemas"]["GetAllDocumentsDirectoryDto"]; + }; + GetAllDocumentsResponse: { + documents?: components["schemas"]["GetAllDocumentsDocumentDto"][]; + }; + CollectionDto: { + /** Format: int64 */ + id?: number; + name?: string; + description?: string; + emoji?: string; + /** Format: int32 */ + bookmarkCount?: number; + collectionCategory?: string; + /** Format: int32 */ + solvedMemberCount?: number; + bookmarked?: boolean; + /** Format: int32 */ + totalQuizCount?: number; + member?: components["schemas"]["CollectionMemberDto"]; + }; + CollectionMemberDto: { + /** Format: int64 */ + creatorId?: number; + creatorName?: string; + }; + CollectionResponseDto: { + collections?: components["schemas"]["CollectionDto"][]; + }; + GetSingleCollectionMemberDto: { + /** Format: int64 */ + creatorId?: number; + creatorName?: string; + }; + GetSingleCollectionQuizDto: { + /** Format: int64 */ + id?: number; + question?: string; + answer?: string; + explanation?: string; + options?: string[]; + /** @enum {string} */ + quizType?: "MIX_UP" | "MULTIPLE_CHOICE"; + }; + GetSingleCollectionResponse: { + /** Format: int64 */ + id?: number; + name?: string; + description?: string; + emoji?: string; + /** Format: int32 */ + bookmarkCount?: number; + collectionCategory?: string; + /** Format: int32 */ + solvedMemberCount?: number; + bookmarked?: boolean; + member?: components["schemas"]["GetSingleCollectionMemberDto"]; + quizzes?: components["schemas"]["GetSingleCollectionQuizDto"][]; + }; + GetQuizzesInCollectionByCollectionCategory: { + quizzes?: components["schemas"]["QuizInCollectionDto"][]; + }; + QuizInCollectionDto: { + /** Format: int64 */ + id?: number; + question?: string; + answer?: string; + explanation?: string; + options?: string[]; + /** @enum {string} */ + quizType?: "MIX_UP" | "MULTIPLE_CHOICE"; + }; + GetCollectionCategoriesCollectionDto: { + /** Format: int64 */ + id?: number; + name?: string; + }; + GetCollectionCategoriesDto: { + /** @enum {string} */ + collectionCategory?: "IT" | "LAW" | "BUSINESS_ECONOMY" | "SOCIETY_POLITICS" | "LANGUAGE" | "MEDICINE_PHARMACY" | "ART" | "SCIENCE_ENGINEERING" | "HISTORY_PHILOSOPHY" | "OTHER"; + categoryName?: string; + emoji?: string; + collections?: components["schemas"]["GetCollectionCategoriesCollectionDto"][]; + }; + GetCollectionCategoriesResponse: { + collectionCategories?: components["schemas"]["GetCollectionCategoriesDto"][]; + }; + GetCollectionSAnalysisResponse: { + /** + * @description 컬렉션 분야와 해당 컬렉션을 푼 횟수 map + * @example { + * "IT": 1, + * "RAW": 1 + * } + */ + collectionsAnalysis?: { + [key: string]: number; + }; + }; + CreateInviteLinkResponse: { + inviteLink?: string; + }; + DeleteInvalidQuizRequest: { + /** + * @example CHOICE_OR_QUESTION_MISSING, QUIZ_TYPE_MISMATCH, UNRELATED_QUIZ + * @enum {string} + */ + quizErrorType?: "CHOICE_OR_QUESTION_MISSING" | "QUIZ_TYPE_MISMATCH" | "UNRELATED_QUIZ"; + }; + DeleteDocumentRequest: { + documentIds?: number[]; + }; + }; + responses: never; + parameters: never; + requestBodies: never; + headers: never; + pathItems: never; +} +export type $defs = Record; +export interface operations { + saveFcmToken: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json;charset=UTF-8": components["schemas"]["SaveFcmTokenRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + createTodayQuizForTest: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description No Content */ + 204: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json;charset=UTF-8": components["schemas"]["CreateQuizzesResponse"]; + }; + }; + }; + }; + createMemberForTest: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description No Content */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + createMemberGeneratedQuizSet: { + parameters: { + query?: never; + header?: never; + path: { + document_id: number; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json;charset=UTF-8": components["schemas"]["CreateQuizzesByDocumentRequest"]; + }; + }; + responses: { + /** @description Created */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json;charset=UTF-8": components["schemas"]["CreateQuizzesResponse"]; + }; + }; + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + createErrorCheckQuizSet: { + parameters: { + query?: never; + header?: never; + path: { + document_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Created */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json;charset=UTF-8": components["schemas"]["CreateQuizzesResponse"]; + }; + }; + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + verifyAmount: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json;charset=UTF-8": components["schemas"]["SaveAmountRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json;charset=UTF-8": Record; + }; + }; + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + tempSaveAmount: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json;charset=UTF-8": components["schemas"]["SaveAmountRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json;charset=UTF-8": Record; + }; + }; + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + confirmPayment: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json;charset=UTF-8": components["schemas"]["TossPaymentRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + cancelPayment: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json;charset=UTF-8": components["schemas"]["CancelPaymentRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + messageSendSend: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json;charset=UTF-8": components["schemas"]["FcmMessageDto"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + messageSend: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json;charset=UTF-8": components["schemas"]["FcmNotificationRequestDto"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + login: { + parameters: { + query?: { + "invite-link"?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json;charset=UTF-8": components["schemas"]["LoginRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json;charset=UTF-8": components["schemas"]["LoginResponse"]; + }; + }; + }; + }; + integratedSearchByKeyword: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json;charset=UTF-8": components["schemas"]["SearchRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json;charset=UTF-8": components["schemas"]["IntegratedSearchResponse"]; + }; + }; + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + createFeedback: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "multipart/form-data": components["schemas"]["CreateFeedbackRequest"]; + }; + }; + responses: { + /** @description Created */ + 201: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + createDocument: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "multipart/form-data": components["schemas"]["CreateDocumentRequest"]; + }; + }; + responses: { + /** @description Created */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CreateDocumentResponse"]; + }; + }; + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + searchDocumentByKeyword: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json;charset=UTF-8": components["schemas"]["SearchRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json;charset=UTF-8": components["schemas"]["SearchDocumentResponse"]; + }; + }; + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + getDirectories: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json;charset=UTF-8": components["schemas"]["GetAllDirectoriesResponse"]; + }; + }; + }; + }; + createDirectory: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json;charset=UTF-8": components["schemas"]["CreateDirectoryRequest"]; + }; + }; + responses: { + /** @description Created */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json;charset=UTF-8": components["schemas"]["CreateDirectoryResponse"]; + }; + }; + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + getAllCollections: { + parameters: { + query?: { + "collection-sort-option"?: "POPULARITY" | "UPDATED"; + "collection-category"?: ("IT" | "LAW" | "BUSINESS_ECONOMY" | "SOCIETY_POLITICS" | "LANGUAGE" | "MEDICINE_PHARMACY" | "ART" | "SCIENCE_ENGINEERING" | "HISTORY_PHILOSOPHY" | "OTHER")[]; + "quiz-type"?: "MIX_UP" | "MULTIPLE_CHOICE"; + "quiz-count"?: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json;charset=UTF-8": components["schemas"]["CollectionResponseDto"]; + }; + }; + }; + }; + createCollection: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json;charset=UTF-8": components["schemas"]["CreateCollectionRequest"]; + }; + }; + responses: { + /** @description Created */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json;charset=UTF-8": components["schemas"]["CreateCollectionResponse"]; + }; + }; + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + createCollectionBookmark: { + parameters: { + query?: never; + header?: never; + path: { + collection_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description No Content */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + createCollectionQuizSet: { + parameters: { + query?: never; + header?: never; + path: { + collection_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Created */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json;charset=UTF-8": components["schemas"]["CreateQuizzesResponse"]; + }; + }; + }; + }; + sendVerificationCode: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json;charset=UTF-8": components["schemas"]["SendVerificationCodeRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + verifyVerificationCode: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json;charset=UTF-8": components["schemas"]["VerifyVerificationCodeRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + verifyInviteCode: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json;charset=UTF-8": components["schemas"]["VerifyInviteCode"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + updateWrongQuizResult: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json;charset=UTF-8": components["schemas"]["UpdateRandomQuizResultRequest"]; + }; + }; + responses: { + /** @description No Content */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + updateRandomQuizResult: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json;charset=UTF-8": components["schemas"]["UpdateRandomQuizResultRequest"]; + }; + }; + responses: { + /** @description No Content */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + updateQuizResult: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json;charset=UTF-8": components["schemas"]["UpdateQuizResultRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json;charset=UTF-8": components["schemas"]["UpdateQuizResultResponse"]; + }; + }; + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + updateTodayQuizCount: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json;charset=UTF-8": components["schemas"]["UpdateTodayQuizCountRequest"]; + }; + }; + responses: { + /** @description No Content */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + updateQuizNotification: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json;charset=UTF-8": components["schemas"]["UpdateQuizNotificationRequest"]; + }; + }; + responses: { + /** @description No Content */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + updateMemberName: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json;charset=UTF-8": components["schemas"]["UpdateMemberNameRequest"]; + }; + }; + responses: { + /** @description No Content */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + updateInterestCollectionCategories: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json;charset=UTF-8": components["schemas"]["UpdateInterestCollectionCategoriesRequest"]; + }; + }; + responses: { + /** @description No Content */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + updateDocumentName: { + parameters: { + query?: never; + header?: never; + path: { + document_id: number; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json;charset=UTF-8": components["schemas"]["UpdateDocumentNameRequest"]; + }; + }; + responses: { + /** @description No Content */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + changeDocumentContent: { + parameters: { + query?: never; + header?: never; + path: { + document_id: number; + }; + cookie?: never; + }; + requestBody?: { + content: { + "multipart/form-data": components["schemas"]["UpdateDocumentContentRequest"]; + }; + }; + responses: { + /** @description No Content */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + selectDocumentToNotGenerateByTodayQuiz: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json;charset=UTF-8": components["schemas"]["UpdateTodayQuizSettingsRequest"]; + }; + }; + responses: { + /** @description No Content */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + moveDocumentToDirectory: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json;charset=UTF-8": components["schemas"]["MoveDocumentToDirectoryRequest"]; + }; + }; + responses: { + /** @description No Content */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + updateDirectoryInfo: { + parameters: { + query?: never; + header?: never; + path: { + directory_id: number; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json;charset=UTF-8": components["schemas"]["UpdateDirectoryInfoRequest"]; + }; + }; + responses: { + /** @description No Content */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + updateCollectionQuizzes: { + parameters: { + query?: never; + header?: never; + path: { + collection_id: number; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json;charset=UTF-8": components["schemas"]["UpdateCollectionQuizzesRequest"]; + }; + }; + responses: { + /** @description No Content */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + updateCollectionInfo: { + parameters: { + query?: never; + header?: never; + path: { + collection_id: number; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json;charset=UTF-8": components["schemas"]["UpdateCollectionInfoRequest"]; + }; + }; + responses: { + /** @description No Content */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + addQuizToCollection: { + parameters: { + query?: never; + header?: never; + path: { + collection_id: number; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json;charset=UTF-8": components["schemas"]["AddQuizToCollectionRequest"]; + }; + }; + responses: { + /** @description No Content */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + getCurrentTodayQuizInfo: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json;charset=UTF-8": components["schemas"]["GetCurrentTodayQuizInfo"]; + }; + }; + }; + }; + getSingleQuizSetRecord: { + parameters: { + query?: never; + header?: never; + path: { + quiz_set_id: string; + quiz_set_type: "TODAY_QUIZ_SET" | "DOCUMENT_QUIZ_SET" | "COLLECTION_QUIZ_SET" | "FIRST_QUIZ_SET"; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json;charset=UTF-8": components["schemas"]["GetSingleQuizSetRecordResponse"]; + }; + }; + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + getAllQuizzesAndCollectionRecords: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json;charset=UTF-8": components["schemas"]["GetQuizRecordResponse"]; + }; + }; + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + getQuizSetByCollection: { + parameters: { + query: { + "collection-id"?: number; + "quiz-set-type": "TODAY_QUIZ_SET" | "DOCUMENT_QUIZ_SET" | "COLLECTION_QUIZ_SET" | "FIRST_QUIZ_SET"; + }; + header?: never; + path: { + quiz_set_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json;charset=UTF-8": components["schemas"]["GetQuizSetResponse"]; + }; + }; + }; + }; + getQuizSetToday: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json;charset=UTF-8": components["schemas"]["GetQuizSetTodayResponse"]; + }; + }; + }; + }; + getQuizAnswerRateAnalysis: { + parameters: { + query?: { + "directory-id"?: number; + week?: string; + month?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json;charset=UTF-8": components["schemas"]["GetQuizAnswerRateAnalysisResponse"]; + }; + }; + }; + }; + getPaymentByPaymentKey: { + parameters: { + query?: never; + header?: never; + path: { + payment_key: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + getPaymentByOrderId: { + parameters: { + query?: never; + header?: never; + path: { + order_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + oauthUrlApi: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json;charset=UTF-8": components["schemas"]["RedirectView"]; + }; + }; + }; + }; + getNotionPages: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json;charset=UTF-8": string; + }; + }; + }; + }; + getNotionPage: { + parameters: { + query?: never; + header?: never; + path: { + page_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json;charset=UTF-8": string; + }; + }; + }; + }; + verifyNotion: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + notionCallback: { + parameters: { + query: { + code: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json;charset=UTF-8": string; + }; + }; + }; + }; + getMemberInfo: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json;charset=UTF-8": components["schemas"]["GetMemberInfoResponse"]; + }; + }; + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + getIncorrectQuizzes: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json;charset=UTF-8": components["schemas"]["QuizResponseDto"]; + }; + }; + }; + }; + healthCheck: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json;charset=UTF-8": string; + }; + }; + }; + }; + getSingleDocument: { + parameters: { + query?: never; + header?: never; + path: { + document_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json;charset=UTF-8": components["schemas"]["GetSingleDocumentResponse"]; + }; + }; + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + getDocumentsNeedingReviewPick: { + parameters: { + query?: never; + header?: never; + path: { + document_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json;charset=UTF-8": components["schemas"]["GetDocumentsNeedingReviewPickResponse"]; + }; + }; + }; + }; + getGeneratedQuizzes: { + parameters: { + query?: { + "quiz-type"?: "MIX_UP" | "MULTIPLE_CHOICE"; + }; + header?: never; + path: { + document_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json;charset=UTF-8": components["schemas"]["QuizResponseDto"]; + }; + }; + }; + }; + downloadQuizzes: { + parameters: { + query?: never; + header?: never; + path: { + document_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json;charset=UTF-8": string[]; + }; + }; + }; + }; + getDocumentsNeedingReview: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json;charset=UTF-8": components["schemas"]["GetDocumentsNeedingReviewResponse"]; + }; + }; + }; + }; + getSingleDirectory: { + parameters: { + query?: never; + header?: never; + path: { + directory_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json;charset=UTF-8": components["schemas"]["GetSingleDirectoryResponse"]; + }; + }; + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + deleteDirectory: { + parameters: { + query?: never; + header?: never; + path: { + directory_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description No Content */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + getAllQuizzesByMemberId: { + parameters: { + query?: never; + header?: never; + path: { + directory_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json;charset=UTF-8": components["schemas"]["QuizResponseDto"]; + }; + }; + }; + }; + getAllDocuments: { + parameters: { + query?: { + "directory-id"?: number; + "sort-option"?: "CREATED_AT" | "UPDATED_AT"; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json;charset=UTF-8": components["schemas"]["GetAllDocumentsResponse"]; + }; + }; + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + searchCollections: { + parameters: { + query?: never; + header?: never; + path: { + keyword: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json;charset=UTF-8": components["schemas"]["CollectionResponseDto"]; + }; + }; + }; + }; + getCollectionInfoByCollectionId: { + parameters: { + query?: never; + header?: never; + path: { + collection_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json;charset=UTF-8": components["schemas"]["GetSingleCollectionResponse"]; + }; + }; + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + getQuizzesInCollectionByCollectionCategory: { + parameters: { + query?: never; + header?: never; + path: { + collection_category: "IT" | "LAW" | "BUSINESS_ECONOMY" | "SOCIETY_POLITICS" | "LANGUAGE" | "MEDICINE_PHARMACY" | "ART" | "SCIENCE_ENGINEERING" | "HISTORY_PHILOSOPHY" | "OTHER"; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json;charset=UTF-8": components["schemas"]["GetQuizzesInCollectionByCollectionCategory"]; + }; + }; + }; + }; + getAllByMemberId: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json;charset=UTF-8": components["schemas"]["CollectionResponseDto"]; + }; + }; + }; + }; + getInterestCategoryCollections: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json;charset=UTF-8": components["schemas"]["CollectionResponseDto"]; + }; + }; + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + getCollectionCategories: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json;charset=UTF-8": components["schemas"]["GetCollectionCategoriesResponse"]; + }; + }; + }; + }; + getAllByMemberIdAndBookmarked: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json;charset=UTF-8": components["schemas"]["CollectionResponseDto"]; + }; + }; + }; + }; + getCollectionAnalysis: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json;charset=UTF-8": components["schemas"]["GetCollectionSAnalysisResponse"]; + }; + }; + }; + }; + googleLogin: { + parameters: { + query: { + code: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json;charset=UTF-8": string; + }; + }; + }; + }; + createInviteLink: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Created */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json;charset=UTF-8": components["schemas"]["CreateInviteLinkResponse"]; + }; + }; + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + deleteInvalidQuiz: { + parameters: { + query?: never; + header?: never; + path: { + quiz_id: number; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json;charset=UTF-8": components["schemas"]["DeleteInvalidQuizRequest"]; + }; + }; + responses: { + /** @description No Content */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + deleteQuiz: { + parameters: { + query?: never; + header?: never; + path: { + quiz_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description No Content */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + deleteDocument: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json;charset=UTF-8": components["schemas"]["DeleteDocumentRequest"]; + }; + }; + responses: { + /** @description No Content */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + deleteCollection: { + parameters: { + query?: never; + header?: never; + path: { + collection_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description No Content */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + deleteCollectionBookmark: { + parameters: { + query?: never; + header?: never; + path: { + collection_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description No Content */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; +} From 0f5ea7b59e6bc51995c539d214c2f2cd6e6a8956 Mon Sep 17 00:00:00 2001 From: "nageuna922@gmail.com" Date: Tue, 10 Dec 2024 11:28:04 +0900 Subject: [PATCH 3/7] feat: use-recent-searches hook --- .../search/hook/use-recent-searches.tsx | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 src/features/search/hook/use-recent-searches.tsx diff --git a/src/features/search/hook/use-recent-searches.tsx b/src/features/search/hook/use-recent-searches.tsx new file mode 100644 index 00000000..ec359b30 --- /dev/null +++ b/src/features/search/hook/use-recent-searches.tsx @@ -0,0 +1,74 @@ +'use client' + +import { useCallback, useEffect, useState } from 'react' +import { RECENT_SEARCHES } from '../config' + +/** + * 최근 검색어를 관리하는 커스텀 훅 + */ +export const useRecentSearches = () => { + const [searches, setSearches] = useState([]) + + // localStorage에서 검색어 가져오기 + const getRecentSearches = useCallback(() => { + try { + const storageSearches = localStorage.getItem(RECENT_SEARCHES) + if (storageSearches) { + const parsedSearches = JSON.parse(storageSearches) as { recentSearches: string[] } + return parsedSearches.recentSearches || [] + } + } catch (error) { + console.error('Failed to parse recent searches:', error) + } + return [] + }, []) + + // 검색어 저장 + const saveRecentSearches = useCallback( + (keyword: string) => { + if (!keyword || keyword.trim() === '') return + + const prevSearches = getRecentSearches() + + let newSearchList = prevSearches.filter((search) => search !== keyword) + newSearchList.unshift(keyword) + + if (newSearchList.length > 5) { + newSearchList = newSearchList.slice(0, 5) + } + + const newSearches = { recentSearches: [...newSearchList] } + localStorage.setItem(RECENT_SEARCHES, JSON.stringify(newSearches)) + setSearches(newSearchList) // 상태 업데이트 + }, + [getRecentSearches] + ) + + // 특정 검색어 삭제 + const deleteRecentSearch = useCallback( + (keyword: string) => { + const prevSearches = getRecentSearches() + const newSearchList = prevSearches.filter((search) => search !== keyword) + localStorage.setItem(RECENT_SEARCHES, JSON.stringify({ recentSearches: newSearchList })) + setSearches(newSearchList) // 상태 업데이트 + }, + [getRecentSearches] + ) + + // 초기화 + const clearRecentSearches = useCallback(() => { + localStorage.removeItem(RECENT_SEARCHES) + setSearches([]) + }, []) + + useEffect(() => { + setSearches(getRecentSearches()) + }, [getRecentSearches]) + + return { + searches, + saveRecentSearches, + deleteRecentSearch, + clearRecentSearches, + } +} From f88f931625a1927f87c9f9ca9db6dfed6b17850f Mon Sep 17 00:00:00 2001 From: "nageuna922@gmail.com" Date: Tue, 10 Dec 2024 11:38:19 +0900 Subject: [PATCH 4/7] =?UTF-8?q?fix:=20=EC=8A=A4=ED=86=A0=EB=A6=AC=EC=A7=80?= =?UTF-8?q?=EC=97=90=20=EC=A0=80=EC=9E=A5=EB=90=98=EB=8A=94=20=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=84=B0=20=EA=B5=AC=EC=A1=B0=20=EA=B0=84=EB=8B=A8?= =?UTF-8?q?=ED=95=98=EA=B2=8C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/search/hook/use-recent-searches.tsx | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/features/search/hook/use-recent-searches.tsx b/src/features/search/hook/use-recent-searches.tsx index ec359b30..2da61d18 100644 --- a/src/features/search/hook/use-recent-searches.tsx +++ b/src/features/search/hook/use-recent-searches.tsx @@ -14,8 +14,8 @@ export const useRecentSearches = () => { try { const storageSearches = localStorage.getItem(RECENT_SEARCHES) if (storageSearches) { - const parsedSearches = JSON.parse(storageSearches) as { recentSearches: string[] } - return parsedSearches.recentSearches || [] + const parsedSearches = JSON.parse(storageSearches) as string[] + return parsedSearches || [] } } catch (error) { console.error('Failed to parse recent searches:', error) @@ -37,9 +37,8 @@ export const useRecentSearches = () => { newSearchList = newSearchList.slice(0, 5) } - const newSearches = { recentSearches: [...newSearchList] } - localStorage.setItem(RECENT_SEARCHES, JSON.stringify(newSearches)) - setSearches(newSearchList) // 상태 업데이트 + localStorage.setItem(RECENT_SEARCHES, JSON.stringify(newSearchList)) + setSearches(newSearchList) }, [getRecentSearches] ) @@ -49,8 +48,8 @@ export const useRecentSearches = () => { (keyword: string) => { const prevSearches = getRecentSearches() const newSearchList = prevSearches.filter((search) => search !== keyword) - localStorage.setItem(RECENT_SEARCHES, JSON.stringify({ recentSearches: newSearchList })) - setSearches(newSearchList) // 상태 업데이트 + localStorage.setItem(RECENT_SEARCHES, JSON.stringify(newSearchList)) + setSearches(newSearchList) }, [getRecentSearches] ) From e9c12067b3b40e416511be421b8ebaae18964fa9 Mon Sep 17 00:00:00 2001 From: "nageuna922@gmail.com" Date: Tue, 10 Dec 2024 16:18:41 +0900 Subject: [PATCH 5/7] fix: localstorage --- .../search/components/header-in-document.tsx | 26 +-- .../search/components/recent-searches.tsx | 43 ++-- .../search/hook/use-recent-searches.tsx | 73 ------- .../search/screen/search-in-document.tsx | 201 ++++++++---------- src/requests/document/hooks.ts | 6 +- 5 files changed, 134 insertions(+), 215 deletions(-) delete mode 100644 src/features/search/hook/use-recent-searches.tsx diff --git a/src/features/search/components/header-in-document.tsx b/src/features/search/components/header-in-document.tsx index 84af43ce..19f3753b 100644 --- a/src/features/search/components/header-in-document.tsx +++ b/src/features/search/components/header-in-document.tsx @@ -3,31 +3,28 @@ import Icon from '@/shared/components/custom/icon' import { Input } from '@/shared/components/ui/input' import { useRouter } from 'next/navigation' -import { RefObject } from 'react' -import { ControllerRenderProps, useFormContext } from 'react-hook-form' +import { ChangeEventHandler, RefObject } from 'react' interface Props { - field: ControllerRenderProps< - { - keyword: string - }, - 'keyword' - > + inputValue: string + onChangeInputValue: ChangeEventHandler searchInputRef: RefObject isSearchFocused: boolean setIsSearchFocused: (value: boolean) => void onDeleteKeyword: () => void + onSubmit: (e: React.FormEvent) => void } const HeaderInDocument = ({ - field, + inputValue, + onChangeInputValue, searchInputRef, isSearchFocused, setIsSearchFocused, onDeleteKeyword, + onSubmit, }: Props) => { const router = useRouter() - const { register } = useFormContext() const handleCancel = () => { if (isSearchFocused) { @@ -40,13 +37,12 @@ const HeaderInDocument = ({ return (
-
+
setIsSearchFocused(true)} - value={field.value} - onChange={field.onChange} + value={inputValue} + onChange={onChangeInputValue} placeholder="노트명, 노트, 퀴즈 검색" className="h-[40px] placeholder:text-text-placeholder-01" variant={'round'} @@ -62,7 +58,7 @@ const HeaderInDocument = ({ } /> -
+
- {searches.map((keyword) => ( + {recentSearches.map((keyword) => (
onUpdateKeyword(keyword)} @@ -44,7 +59,7 @@ const RecentSearches = ({
) } diff --git a/src/requests/document/hooks.ts b/src/requests/document/hooks.ts index cdd21622..f5623b14 100644 --- a/src/requests/document/hooks.ts +++ b/src/requests/document/hooks.ts @@ -3,11 +3,7 @@ import { useMutation } from '@tanstack/react-query' import { useSession } from 'next-auth/react' import { createDocument } from './create-document' -<<<<<<< HEAD -import { deleteDocument, fetchDocumentDetail, moveDocument, searchDocument } from '.' -======= -import { deleteDocument, fetchDocumentDetail, moveDocument } from './client' ->>>>>>> 6d343830dda659633aa4f7f32bb4c985ad13f46d +import { deleteDocument, fetchDocumentDetail, moveDocument, searchDocument } from './client' import { queries } from '@/shared/lib/tanstack-query/query-keys' import { getQueryClient } from '@/shared/lib/tanstack-query/client' import { updateDocument } from './update-document' From c3f7d201d55621cf5534e87f52c59e5153c8dd82 Mon Sep 17 00:00:00 2001 From: "nageuna922@gmail.com" Date: Tue, 10 Dec 2024 16:49:29 +0900 Subject: [PATCH 6/7] refactor: markdown processor --- .../search/components/search-item/index.tsx | 2 +- .../search/screen/search-in-document.tsx | 23 ++++--------------- src/features/search/utils/index.tsx | 16 +++++++++++++ 3 files changed, 22 insertions(+), 19 deletions(-) diff --git a/src/features/search/components/search-item/index.tsx b/src/features/search/components/search-item/index.tsx index 9843a2ff..995f9f94 100644 --- a/src/features/search/components/search-item/index.tsx +++ b/src/features/search/components/search-item/index.tsx @@ -6,7 +6,7 @@ import DocumentTypeIcon from '@/features/document/components/document-type-icon' interface Props { createType: Document.ItemInList['documentType'] - documentTitle: string + documentTitle: React.ReactNode matchingSentence: React.ReactNode resultType: 'document' | 'quiz' relativeDirectory: string diff --git a/src/features/search/screen/search-in-document.tsx b/src/features/search/screen/search-in-document.tsx index 88c0307b..b1d44f64 100644 --- a/src/features/search/screen/search-in-document.tsx +++ b/src/features/search/screen/search-in-document.tsx @@ -5,7 +5,7 @@ import SearchList from '../components/search-list' import SearchItem from '../components/search-item' import RecentSearches from '../components/recent-searches' import HeaderInDocument from '../components/header-in-document' -import { extractPlainText, highlightAndTrimText } from '../utils' +import { highlightAndTrimText, MarkdownProcessor } from '../utils' import Text from '@/shared/components/ui/text' import { useRouter, useSearchParams } from 'next/navigation' import { useQuery } from '@tanstack/react-query' @@ -123,7 +123,10 @@ const SearchInDocument = () => { { } export default SearchInDocument - -/** 마크다운 텍스트를 받아 - * 문법을 제거하고 키워드에 강조를 해서 반환하는 함수 - */ -const MarkdownProcessor = ({ - markdownText, - keyword, -}: { - markdownText: string - keyword: string -}) => { - const plainText = extractPlainText(markdownText) - const highlightedText = highlightAndTrimText(plainText, keyword) - - return
{highlightedText}
-} diff --git a/src/features/search/utils/index.tsx b/src/features/search/utils/index.tsx index 532b2d25..cb50dfbf 100644 --- a/src/features/search/utils/index.tsx +++ b/src/features/search/utils/index.tsx @@ -65,3 +65,19 @@ export function extractPlainText(markdownText: string): string { div.innerHTML = html return div.textContent || '' } + +/** 마크다운 텍스트를 받아 + * 문법을 제거하고 키워드에 강조를 해서 반환하는 함수 + */ +export const MarkdownProcessor = ({ + markdownText, + keyword, +}: { + markdownText: string + keyword: string +}) => { + const plainText = extractPlainText(markdownText) + const highlightedText = highlightAndTrimText(plainText, keyword) + + return
{highlightedText}
+} From 6f99eb6da7f56fa0b1749acbc091482bd68390e2 Mon Sep 17 00:00:00 2001 From: "nageuna922@gmail.com" Date: Wed, 11 Dec 2024 12:41:14 +0900 Subject: [PATCH 7/7] =?UTF-8?q?fix:=20api=20=EB=B3=80=EA=B2=BD=20+=20usePr?= =?UTF-8?q?eviousPath=20hook=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/document-type-icon.tsx | 12 +++ .../search/components/recent-searches.tsx | 2 - .../search/components/search-item/index.tsx | 8 +- .../search/components/search-list.tsx | 2 +- .../search/screen/search-in-document.tsx | 12 ++- src/shared/hooks/use-previous-path.tsx | 10 +- src/types/document.d.ts | 5 +- src/types/schema.d.ts | 101 ++++++++++++++++-- 8 files changed, 130 insertions(+), 22 deletions(-) diff --git a/src/features/document/components/document-type-icon.tsx b/src/features/document/components/document-type-icon.tsx index bb865344..25caf170 100644 --- a/src/features/document/components/document-type-icon.tsx +++ b/src/features/document/components/document-type-icon.tsx @@ -47,6 +47,18 @@ const DocumentTypeIcon = ({ type, containerClassName, iconClassName }: Props) =>
) } + + // default + return ( +
+ +
+ ) } export default DocumentTypeIcon diff --git a/src/features/search/components/recent-searches.tsx b/src/features/search/components/recent-searches.tsx index 8ef1ef3c..85b7b3fd 100644 --- a/src/features/search/components/recent-searches.tsx +++ b/src/features/search/components/recent-searches.tsx @@ -16,8 +16,6 @@ const RecentSearches = ({ containerRef, onUpdateKeyword }: Props) => { useEffect(() => { const storageSearches = getLocalStorage(RECENT_SEARCHES) ?? [] - // eslint-disable-next-line no-console - console.log(storageSearches) setRecentSearches(storageSearches) }, []) diff --git a/src/features/search/components/search-item/index.tsx b/src/features/search/components/search-item/index.tsx index 995f9f94..1db9a393 100644 --- a/src/features/search/components/search-item/index.tsx +++ b/src/features/search/components/search-item/index.tsx @@ -3,8 +3,10 @@ import Tag from '@/shared/components/ui/tag' import Text from '@/shared/components/ui/text' import { cn } from '@/shared/lib/utils' import DocumentTypeIcon from '@/features/document/components/document-type-icon' +import Link from 'next/link' interface Props { + documentId: number | undefined // api 수정되면 undefined 제거 createType: Document.ItemInList['documentType'] documentTitle: React.ReactNode matchingSentence: React.ReactNode @@ -14,6 +16,7 @@ interface Props { } const SearchItem = ({ + documentId, createType, documentTitle, matchingSentence, @@ -22,7 +25,8 @@ const SearchItem = ({ lastItem, }: Props) => { return ( -
{relativeDirectory}
-
+ ) } diff --git a/src/features/search/components/search-list.tsx b/src/features/search/components/search-list.tsx index 4d7bfd31..1940a41f 100644 --- a/src/features/search/components/search-list.tsx +++ b/src/features/search/components/search-list.tsx @@ -3,7 +3,7 @@ import { PropsWithChildren } from 'react' const SearchList = ({ length, children }: PropsWithChildren & { length: number }) => { return ( -
+
퀴즈 노트 {length} diff --git a/src/features/search/screen/search-in-document.tsx b/src/features/search/screen/search-in-document.tsx index b1d44f64..02d3cc7c 100644 --- a/src/features/search/screen/search-in-document.tsx +++ b/src/features/search/screen/search-in-document.tsx @@ -1,6 +1,6 @@ 'use client' -import { useEffect, useRef, useState } from 'react' +import { ChangeEvent, useEffect, useRef, useState } from 'react' import SearchList from '../components/search-list' import SearchItem from '../components/search-item' import RecentSearches from '../components/recent-searches' @@ -13,9 +13,12 @@ import { queries } from '@/shared/lib/tanstack-query/query-keys' import Loading from '@/shared/components/custom/loading' import { RECENT_SEARCHES } from '../config' import { getLocalStorage, setLocalStorage } from '@/shared/utils/storage' +import usePreviousPath from '@/shared/hooks/use-previous-path' // 퀴즈노트 탭 내 검색창 화면 const SearchInDocument = () => { + usePreviousPath() + const router = useRouter() const searchParams = useSearchParams() const initialKeyword = searchParams.get('keyword') || '' @@ -89,7 +92,7 @@ const SearchInDocument = () => {
setKeyword(e.target.value)} + onChangeInputValue={(e: ChangeEvent) => setKeyword(e.target.value)} onDeleteKeyword={handleDeleteKeyword} onSubmit={handleSubmit} searchInputRef={searchInputRef} @@ -108,7 +111,7 @@ const SearchInDocument = () => { {!isSearchFocused && // 검색 결과 X (!data || searchResults.length === 0 ? ( -
+
검색결과가 없습니다 다른 키워드를 입력해보세요 @@ -122,7 +125,8 @@ const SearchInDocument = () => { {searchResults.map((searchItem, idx) => ( { const pathname = usePathname() + const searchParams = useSearchParams() // 이전 경로 가져오기 const getPreviousPath = useCallback((): string | null => { @@ -25,12 +26,15 @@ const usePreviousPath = ({ getCustomPath } = { getCustomPath: false }): UsePrevi useEffect(() => { const setPreviousPath = () => { if (pathname && !getCustomPath) { - sessionStorage.setItem(PREVIOUS_PATH_KEY, pathname) + const fullPath = searchParams?.toString() + ? `${pathname}?${searchParams.toString()}` + : pathname + sessionStorage.setItem(PREVIOUS_PATH_KEY, fullPath) } } return () => setPreviousPath() - }, [pathname, getCustomPath]) + }, [pathname, getCustomPath, searchParams]) return { getPreviousPath, setPreviousPath } } diff --git a/src/types/document.d.ts b/src/types/document.d.ts index c7f49a47..4f433fef 100644 --- a/src/types/document.d.ts +++ b/src/types/document.d.ts @@ -12,7 +12,10 @@ declare global { type Sort = 'CREATED_AT' | 'UPDATED_AT' type Status = DeepRequired - type Type = DeepRequired + type Type = Exclude< + DeepRequired, + undefined + > type SearchedDocument = DeepRequired diff --git a/src/types/schema.d.ts b/src/types/schema.d.ts index 8b3f709f..d4a2886e 100644 --- a/src/types/schema.d.ts +++ b/src/types/schema.d.ts @@ -258,6 +258,23 @@ export interface paths { patch?: never; trace?: never; }; + "/api/v2/documents/{document_id}/add-quizzes": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** 문서에서 추가 퀴즈 생성 */ + post: operations["createQuizzes"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/api/v2/documents/search": { parameters: { query?: never; @@ -716,7 +733,7 @@ export interface paths { cookie?: never; }; /** quiz_set_id와 quiz-set-type으로 퀴즈 가져오기 */ - get: operations["getQuizSetByCollection"]; + get: operations["getQuizSet"]; put?: never; post?: never; delete?: never; @@ -876,6 +893,23 @@ export interface paths { patch?: never; trace?: never; }; + "/api/v2/members/reward": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** 초대 링크 보상 확인? */ + get: operations["getInviteLinkMember"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/api/v2/members/info": { parameters: { query?: never; @@ -1334,8 +1368,7 @@ export interface components { createdAt?: string; }; CreateQuizzesByDocumentRequest: { - /** @enum {string} */ - quizType?: "MIX_UP" | "MULTIPLE_CHOICE"; + quizType?: string; /** Format: int32 */ quizCount?: number; }; @@ -1398,6 +1431,8 @@ export interface components { documentId?: number; documentName?: string; content?: string; + /** @enum {string} */ + documentType?: "FILE" | "TEXT" | "NOTION"; directory?: components["schemas"]["IntegratedSearchDirectoryDto"]; }; IntegratedSearchQuizDto: { @@ -1436,6 +1471,12 @@ export interface components { /** Format: int64 */ id?: number; }; + CreateQuizzesRequest: { + /** Format: int32 */ + star?: number; + /** @enum {string} */ + quizType?: "MIX_UP" | "MULTIPLE_CHOICE"; + }; SearchDocumentDirectoryDto: { /** Format: int64 */ id?: number; @@ -1446,6 +1487,8 @@ export interface components { documentId?: number; documentName?: string; content?: string; + /** @enum {string} */ + documentType?: "FILE" | "TEXT" | "NOTION"; directory?: components["schemas"]["SearchDocumentDirectoryDto"]; }; SearchDocumentQuizDto: { @@ -1653,7 +1696,6 @@ export interface components { }; GetQuizSetResponse: { quizzes?: components["schemas"]["GetQuizSetQuizDto"][]; - collectionName?: string; }; GetQuizSetTodayResponse: { quizSetId?: string; @@ -1681,10 +1723,10 @@ export interface components { parent?: components["schemas"]["ApplicationContext"]; id?: string; displayName?: string; + autowireCapableBeanFactory?: components["schemas"]["AutowireCapableBeanFactory"]; applicationName?: string; /** Format: int64 */ startupDate?: number; - autowireCapableBeanFactory?: components["schemas"]["AutowireCapableBeanFactory"]; environment?: components["schemas"]["Environment"]; /** Format: int32 */ beanDefinitionCount?: number; @@ -1850,13 +1892,13 @@ export interface components { hosts?: string[]; redirectView?: boolean; propagateQueryProperties?: boolean; + attributesCSV?: string; attributesMap?: { [key: string]: Record; }; attributes?: { [key: string]: string; }; - attributesCSV?: string; }; ServletContext: { sessionCookieConfig?: components["schemas"]["SessionCookieConfig"]; @@ -1891,7 +1933,6 @@ export interface components { servletRegistrations?: { [key: string]: components["schemas"]["ServletRegistration"]; }; - defaultSessionTrackingModes?: ("COOKIE" | "URL" | "SSL")[]; /** Format: int32 */ effectiveMajorVersion?: number; /** Format: int32 */ @@ -1901,6 +1942,7 @@ export interface components { filterRegistrations?: { [key: string]: components["schemas"]["FilterRegistration"]; }; + defaultSessionTrackingModes?: ("COOKIE" | "URL" | "SSL")[]; effectiveSessionTrackingModes?: ("COOKIE" | "URL" | "SSL")[]; jspConfigDescriptor?: components["schemas"]["JspConfigDescriptor"]; requestCharacterEncoding?: string; @@ -2603,6 +2645,30 @@ export interface operations { }; }; }; + createQuizzes: { + parameters: { + query?: never; + header?: never; + path: { + document_id: number; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json;charset=UTF-8": components["schemas"]["CreateQuizzesRequest"]; + }; + }; + responses: { + /** @description Created */ + 201: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; searchDocumentByKeyword: { parameters: { query?: never; @@ -3403,10 +3469,9 @@ export interface operations { }; }; }; - getQuizSetByCollection: { + getQuizSet: { parameters: { query: { - "collection-id"?: number; "quiz-set-type": "TODAY_QUIZ_SET" | "DOCUMENT_QUIZ_SET" | "COLLECTION_QUIZ_SET" | "FIRST_QUIZ_SET"; }; header?: never; @@ -3614,6 +3679,24 @@ export interface operations { }; }; }; + getInviteLinkMember: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; getMemberInfo: { parameters: { query?: never;