From 6f556d8a2693c167cfd15cc190002ecf1eff7f26 Mon Sep 17 00:00:00 2001 From: chaeyoung103 Date: Fri, 11 Jul 2025 17:33:07 +0900 Subject: [PATCH 1/9] =?UTF-8?q?fix(service):=20=EC=B9=B4=EC=B9=B4=EC=98=A4?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20api=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/service/src/apis/authMiddleware.ts | 13 +++-- .../apis/controller/auth/postKakaoLogin.ts | 48 ++++--------------- .../src/app/api/auth/callback/kakao/page.tsx | 9 +--- apps/service/src/app/login/page.tsx | 10 +++- apps/service/src/types/api/schema.d.ts | 37 +++++++------- 5 files changed, 43 insertions(+), 74 deletions(-) diff --git a/apps/service/src/apis/authMiddleware.ts b/apps/service/src/apis/authMiddleware.ts index 87fc26fb..73bbcf61 100644 --- a/apps/service/src/apis/authMiddleware.ts +++ b/apps/service/src/apis/authMiddleware.ts @@ -3,14 +3,17 @@ import { Middleware } from 'openapi-fetch'; import { getAccessToken, setAccessToken } from '@utils'; -const UNPROTECTED_ROUTES = ['/api/v1/auth/admin/login', '/api/v1/auth/oauth/social-login']; +const UNPROTECTED_ROUTES = ['/api/v1/auth/admin/login', '/api/student/auth/social/login']; const reissueToken = async () => { try { - const response = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/api/v1/auth/reissue`, { - method: 'GET', - credentials: 'include', - }); + const response = await fetch( + `${process.env.NEXT_PUBLIC_API_BASE_URL}/api/common/auth/refresh`, + { + method: 'GET', + credentials: 'include', + } + ); if (!response.ok) throw new Error('Token reissue failed'); diff --git a/apps/service/src/apis/controller/auth/postKakaoLogin.ts b/apps/service/src/apis/controller/auth/postKakaoLogin.ts index f79e80eb..030d1f99 100644 --- a/apps/service/src/apis/controller/auth/postKakaoLogin.ts +++ b/apps/service/src/apis/controller/auth/postKakaoLogin.ts @@ -1,54 +1,24 @@ 'use client'; -import { setAccessToken, setName } from '@utils'; import { client } from '@/apis/client'; -const postKakaoAccessToken = async (code: string) => { - const response = await fetch(`https://kauth.kakao.com/oauth/token`, { - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8', - }, - body: `grant_type=authorization_code&client_id=${process.env.NEXT_PUBLIC_REST_API_KEY}&redirect_uri=${process.env.NEXT_PUBLIC_REDIRECT_URI}&code=${code}`, - }); - const jsonData = await response.json(); - return jsonData.access_token; -}; - -const postKakaoLogin = async (code: string) => { - const accessToken = await postKakaoAccessToken(code); - const response = await client.POST('/api/v1/auth/oauth/social-login', { - params: { - header: { - social_access_token: accessToken, - }, - query: { - provider: 'KAKAO', - }, +const postKakaoLogin = async () => { + const response = await client.POST('/api/student/auth/social/login', { + body: { + provider: 'KAKAO', + redirectUri: process.env.NEXT_PUBLIC_REDIRECT_URI, }, }); try { - if ( - response && - response.data && - response.data.data && - response.data.data.name && - response.data.data.accessToken - ) { - const { accessToken, name } = response.data.data; - setAccessToken(accessToken); - setName(name); - - window.location.href = '/'; + if (response && response.data) { + return { isSuccess: true, loginUrl: response.data.loginUrl }; } else { - console.error('accessToken을 찾을 수 없습니다:', response); + return { isSuccess: false, error: '데이터를 찾을 수 없습니다.' }; } } catch (error) { - console.error('소셜 로그인 요청 오류:', error); + return { isSuccess: false, error: error }; } - - return response; }; export default postKakaoLogin; diff --git a/apps/service/src/app/api/auth/callback/kakao/page.tsx b/apps/service/src/app/api/auth/callback/kakao/page.tsx index d9f216cb..23872e19 100644 --- a/apps/service/src/app/api/auth/callback/kakao/page.tsx +++ b/apps/service/src/app/api/auth/callback/kakao/page.tsx @@ -3,17 +3,12 @@ import { useSearchParams } from 'next/navigation'; import { useEffect } from 'react'; -import { postKakaoLogin } from '@apis'; - const Page = () => { const searchParams = useSearchParams(); - const code = searchParams.get('code'); useEffect(() => { - if (code) { - postKakaoLogin(code); - } - }, [code]); + console.log(searchParams); + }, [searchParams]); return <>; }; diff --git a/apps/service/src/app/login/page.tsx b/apps/service/src/app/login/page.tsx index 9f04fe5a..768acfb8 100644 --- a/apps/service/src/app/login/page.tsx +++ b/apps/service/src/app/login/page.tsx @@ -3,6 +3,7 @@ import { useEffect } from 'react'; import { useRouter } from 'next/navigation'; import { getAccessToken, trackEvent } from '@utils'; +import { postKakaoLogin } from '@apis'; import { LogoLogin } from '@/assets/svg/logo'; import { KakaoButton } from '@/components/login'; @@ -12,9 +13,14 @@ const Page = () => { process.env.NEXT_PUBLIC_REST_API_KEY }&redirect_uri=${process.env.NEXT_PUBLIC_REDIRECT_URI}&response_type=code`; - const handleLoginClick = () => { + const handleLoginClick = async () => { trackEvent('kakao_login_click'); - window.location.replace(kakaoLoginUrl); + const result = await postKakaoLogin(); + if (result.isSuccess && result.loginUrl) { + router.push(result.loginUrl); + } else { + console.error('로그인 URL을 가져오는 데 실패했습니다.'); + } }; useEffect(() => { diff --git a/apps/service/src/types/api/schema.d.ts b/apps/service/src/types/api/schema.d.ts index 0f76b3e6..6c870ce9 100644 --- a/apps/service/src/types/api/schema.d.ts +++ b/apps/service/src/types/api/schema.d.ts @@ -225,7 +225,7 @@ export interface paths { patch?: never; trace?: never; }; - '/api/v1/auth/oauth/social-login': { + '/api/student/auth/social/login': { parameters: { query?: never; header?: never; @@ -593,7 +593,7 @@ export interface paths { patch?: never; trace?: never; }; - '/api/v1/auth/reissue': { + 'api/common/auth/refresh': { parameters: { query?: never; header?: never; @@ -873,13 +873,14 @@ export interface components { /** Format: int64 */ problemId: number; }; + LoginRequest: { + provider?: string; + redirectUri?: string; + }; LoginResponse: { /** Format: int64 */ - memberId?: number; - name?: string; - email?: string; - accessToken?: string; - refreshToken?: string; + provider?: string; + loginUrl?: string; }; AccessTokenResponse: { accessToken: string; @@ -1620,30 +1621,24 @@ export interface operations { }; socialLogin: { parameters: { - query: { - provider: string; - }; - header: { - social_access_token: string; - }; + query?: never; + header?: never; path?: never; cookie?: never; }; - requestBody?: never; + requestBody?: { + content: { + 'application/json': components['schemas']['LoginRequest']; + }; + }; responses: { /** @description 로그인 성공 */ 200: { headers: { - /** @description 발급된 액세스 토큰 */ - Authorization?: string; - /** @description 리프레시 토큰이 담긴 HTTP Only 쿠키 */ - 'Set-Cookie'?: string; [name: string]: unknown; }; content: { - 'application/json': { - data: components['schemas']['LoginResponse']; - }; + 'application/json': components['schemas']['LoginResponse']; }; }; /** @description 유효하지 않은 소셜 액세스 토큰 */ From cecd630d1b4547879d6291df05cfcc41e8736ebc Mon Sep 17 00:00:00 2001 From: chaeyoung103 Date: Fri, 11 Jul 2025 17:40:03 +0900 Subject: [PATCH 2/9] =?UTF-8?q?fix(service):=20api=20=ED=83=80=EC=9E=85=20?= =?UTF-8?q?openapi=20=EB=B0=A9=EC=8B=9D=EC=9C=BC=EB=A1=9C=20=EC=8A=A4?= =?UTF-8?q?=ED=82=A4=EB=A7=88=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/service/package.json | 2 +- .../service/src/apis/controller/auth/index.ts | 3 +- .../apis/controller/auth/postKakaoLogin.ts | 2 +- .../src/apis/controller/auth/postLogin.ts | 13 - apps/service/src/types/api/schema.d.ts | 3571 ++++++++++++----- 5 files changed, 2521 insertions(+), 1070 deletions(-) delete mode 100644 apps/service/src/apis/controller/auth/postLogin.ts diff --git a/apps/service/package.json b/apps/service/package.json index 95153592..06aaa09c 100644 --- a/apps/service/package.json +++ b/apps/service/package.json @@ -7,7 +7,7 @@ "build": "next build", "start": "next start", "lint": "next lint", - "openapi": "pnpm dlx openapi-typescript https://dev.math-pointer.com/v3/api-docs --output ./src/types/api/schema.d.ts && prettier --write ./src/types/api/schema.d.ts" + "openapi": "pnpm dlx openapi-typescript https://api.math-pointer.com/v3/api-docs --output ./src/types/api/schema.d.ts && prettier --write ./src/types/api/schema.d.ts" }, "dependencies": { "@next/third-parties": "^15.2.4", diff --git a/apps/service/src/apis/controller/auth/index.ts b/apps/service/src/apis/controller/auth/index.ts index bdae8e16..27f91b57 100644 --- a/apps/service/src/apis/controller/auth/index.ts +++ b/apps/service/src/apis/controller/auth/index.ts @@ -1,4 +1,3 @@ -import postLogin from './postLogin'; import postKakaoLogin from './postKakaoLogin'; -export { postLogin, postKakaoLogin }; +export { postKakaoLogin }; diff --git a/apps/service/src/apis/controller/auth/postKakaoLogin.ts b/apps/service/src/apis/controller/auth/postKakaoLogin.ts index 030d1f99..e4803285 100644 --- a/apps/service/src/apis/controller/auth/postKakaoLogin.ts +++ b/apps/service/src/apis/controller/auth/postKakaoLogin.ts @@ -6,7 +6,7 @@ const postKakaoLogin = async () => { const response = await client.POST('/api/student/auth/social/login', { body: { provider: 'KAKAO', - redirectUri: process.env.NEXT_PUBLIC_REDIRECT_URI, + redirectUri: process.env.NEXT_PUBLIC_REDIRECT_URI ?? '', }, }); diff --git a/apps/service/src/apis/controller/auth/postLogin.ts b/apps/service/src/apis/controller/auth/postLogin.ts deleted file mode 100644 index fd73e233..00000000 --- a/apps/service/src/apis/controller/auth/postLogin.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { client } from '@apis'; - -const postLogin = async (email: string, password: string) => { - return await client.POST('/api/v1/auth/admin/login', { - body: { - email, - password, - }, - credentials: 'include', - }); -}; - -export default postLogin; diff --git a/apps/service/src/types/api/schema.d.ts b/apps/service/src/types/api/schema.d.ts index 6c870ce9..123796c7 100644 --- a/apps/service/src/types/api/schema.d.ts +++ b/apps/service/src/types/api/schema.d.ts @@ -4,60 +4,169 @@ */ export interface paths { - '/api/v1/problems/{id}': { + '/api/teacher/qna/chat/{chatId}': { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** - * 문항 조회 - * @description 문항를 조회합니다. - */ + get?: never; + /** 채팅메시지 수정 */ + put: operations['updateChat']; + post?: never; + /** 채팅메시지 삭제 */ + delete: operations['deleteChat']; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/teacher/notice/{id}': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + /** 수정 */ + put: operations['update']; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/student/qna/{qnaId}': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Q&A 상세 조회 */ + get: operations['getById']; + /** Q&A 수정 */ + put: operations['update_1']; + post?: never; + /** Q&A 삭제 */ + delete: operations['delete']; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/student/qna/chat/{chatId}': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + /** 채팅메시지 수정 */ + put: operations['updateChat_1']; + post?: never; + /** 채팅메시지 삭제 */ + delete: operations['deleteChat_1']; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/student/notice/read/{noticeId}': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + /** 공지사항 읽기 */ + put: operations['readNotice']; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/student/me': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** 내 정보 조회 */ + get: operations['me']; + /** 내 정보 수정 */ + put: operations['update_2']; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/admin/teacher/{id}': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + /** 수정 */ + put: operations['update_3']; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/admin/problem/{id}': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** 상세 조회 */ get: operations['getProblem']; - /** - * 문항 업데이트 - * @description 문제를 업데이트합니다. 새끼문항은 들어온 list의 순서로 저장됩니다. - */ + /** 수정 */ put: operations['updateProblem']; post?: never; - /** 문항 삭제 */ + /** 삭제 */ delete: operations['deleteProblem']; options?: never; head?: never; patch?: never; trace?: never; }; - '/api/v1/problemSet/{problemSetId}': { + '/api/admin/problem-set/{id}': { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** - * 문항세트 개별 조회 - * @description 문항세트를 조회합니다. - */ + /** 상세 조회 */ get: operations['getProblemSet']; - /** - * 문항세트 수정 - * @description 문항세트의 이름 및 문항 리스트를 수정합니다. - */ - put: operations['updateProblemSet']; + /** 수정 */ + put: operations['update_4']; post?: never; - /** - * 문항세트 삭제 - * @description 문항세트를 삭제합니다. (soft delete) - */ - delete: operations['deleteProblemSet_1']; + /** 삭제 */ + delete: operations['delete_1']; options?: never; head?: never; patch?: never; trace?: never; }; - '/api/v1/problemSet/{problemSetId}/confirm': { + '/api/admin/problem-set/{id}/status': { parameters: { query?: never; header?: never; @@ -65,11 +174,8 @@ export interface paths { cookie?: never; }; get?: never; - /** - * 문항세트 컨펌 토글 - * @description 문항세트의 컨펌 상태를 토글합니다. - */ - put: operations['toggleConfirmProblemSet']; + /** 수정 */ + put: operations['update_5']; post?: never; delete?: never; options?: never; @@ -77,7 +183,7 @@ export interface paths { patch?: never; trace?: never; }; - '/api/v1/client/problemSubmit': { + '/api/admin/problem-set/toggle-status/{id}': { parameters: { query?: never; header?: never; @@ -85,23 +191,34 @@ export interface paths { cookie?: never; }; get?: never; - /** - * 문항 제출 업데이트 - * @description 제출한 답안을 바탕으로 문항 제출의 상태를 업데이트합니다. - */ - put: operations['updateProblemSubmit']; - /** - * 문항 제출 생성 - * @description 문항 제출을 '진행중'으로 생성합니다. - */ - post: operations['createProblemSubmit']; + /** 컨펌 여부 토글 */ + put: operations['toggleStatus']; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - '/api/v1/client/childProblemSubmit': { + '/api/admin/practice-test/{id}': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + /** 수정 */ + put: operations['update_6']; + post?: never; + /** 삭제 */ + delete: operations['delete_2']; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/admin/notice/{id}': { parameters: { query?: never; header?: never; @@ -109,23 +226,34 @@ export interface paths { cookie?: never; }; get?: never; - /** - * 새끼문항 제출 업데이트 - * @description 제출한 답안을 바탕으로 문항 제출의 상태를 업데이트합니다. - */ - put: operations['updateChildProblemSubmit']; - /** - * 새끼문항 제출 생성 - * @description 문항에 속한 새끼문항들을 '시작전'으로 생성합니다. - */ - post: operations['createProblemSubmit_1']; + /** 수정 */ + put: operations['update_7']; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - '/api/v1/client/childProblemSubmit/incorrect': { + '/api/admin/concept/{conceptId}': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + /** 개념태그 수정 */ + put: operations['update_8']; + post?: never; + /** 개념태그 삭제 */ + delete: operations['delete_3']; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/admin/concept/category/{categoryId}': { parameters: { query?: never; header?: never; @@ -133,19 +261,52 @@ export interface paths { cookie?: never; }; get?: never; - /** - * 새끼문항 제출 틀림 업데이트 - * @description 새끼문항 제출의 상태를 틀림으로 업데이트합니다. - */ - put: operations['updateChildProblemSubmitIncorrect']; + /** 대분류 수정 */ + put: operations['updateCategory']; post?: never; + /** 대분류 삭제 */ + delete: operations['deleteCategory']; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/teacher/qna/chat': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** 채팅메시지 생성 */ + post: operations['addChat']; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/teacher/notice': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** 학생 별 공지사항 전체 조회 */ + get: operations['getsAll']; + put?: never; + /** 생성 */ + post: operations['create']; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - '/api/v1/publish': { + '/api/teacher/auth/login/local': { parameters: { query?: never; header?: never; @@ -154,18 +315,15 @@ export interface paths { }; get?: never; put?: never; - /** - * 발행 생성하기 - * @description 특정 날짜에 문항세트를 발행합니다. - */ - post: operations['postPublish']; + /** 이메일 로그인 */ + post: operations['login']; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - '/api/v1/problems': { + '/api/student/study/submit/pointing': { parameters: { query?: never; header?: never; @@ -174,18 +332,50 @@ export interface paths { }; get?: never; put?: never; - /** - * 문항 생성 - * @description 문제를 생성합니다. 기출/변형 문제는 모든 값이 필수이며 창작 문제는 문항 타입만 필수 입니다. - */ - post: operations['createProblem']; + /** 포인팅 피드백(이해했어요/모르겠어요) 제출 */ + post: operations['feedback']; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/student/study/submit/answer': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** 답안 제출 */ + post: operations['submit']; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/student/qna': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Q&A 목록 조회 */ + get: operations['gets']; + put?: never; + /** Q&A 생성 */ + post: operations['create_1']; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - '/api/v1/problems/{problemId}/child-problems': { + '/api/student/qna/exist': { parameters: { query?: never; header?: never; @@ -194,18 +384,15 @@ export interface paths { }; get?: never; put?: never; - /** - * 새끼문항 추가 - * @description 추가되는 새끼 문항의 id를 반환합니다. 컨펌 이후에는 새끼 문항 추가가 불가능합니다. - */ - post: operations['createChildProblem']; + /** Q&A 존재 여부 확인, 사용자가 동일한 항목에 대해 QnA 작성 여부 확인 */ + post: operations['checkExists']; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - '/api/v1/problemSet': { + '/api/student/qna/chat': { parameters: { query?: never; header?: never; @@ -214,11 +401,8 @@ export interface paths { }; get?: never; put?: never; - /** - * 문항세트 생성 - * @description 문항세트를 생성합니다. 문항은 요청 순서대로 저장합니다. - */ - post: operations['createProblemSet']; + /** 채팅메시지 생성 */ + post: operations['addChat_1']; delete?: never; options?: never; head?: never; @@ -234,18 +418,32 @@ export interface paths { }; get?: never; put?: never; - /** - * 소셜 로그인 - * @description 소셜 액세스 토큰으로 로그인하여 자체 액세스 토큰을 발급받고 리프레시 토큰을 쿠키에 설정합니다. - */ - post: operations['socialLogin']; + /** 소셜 로그인 URL 요청 [네이버만 완료] */ + post: operations['getSocialLoginUrl']; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/student/auth/register/social': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** 소셜 로그인 이후, 정보 등록 */ + post: operations['registerSocial']; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - '/api/v1/auth/admin/login': { + '/api/common/upload-file': { parameters: { query?: never; header?: never; @@ -254,160 +452,218 @@ export interface paths { }; get?: never; put?: never; - /** - * 어드민 로그인 - * @description 이메일과 비밀번호로 로그인하여 액세스 토큰을 발급받고 리프레시 토큰을 쿠키에 설정합니다. - */ - post: operations['adminLogin']; + /** 파일 업로드, (응답의 uploadUrl로 AWS S3 업로드 해야합니다) */ + post: operations['getPreSignedUrl']; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - '/api/v1/publish/{year}/{month}': { + '/api/admin/user': { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** - * 연월별 발행 조회 - * @description 연월별로 발행된 세트들을 조회합니다. - */ - get: operations['getPublishMonth']; + get?: never; put?: never; - post?: never; + /** 관리자 계정 생성 */ + post: operations['create_2']; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/admin/teacher': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** 전체 조회 */ + get: operations['gets_1']; + put?: never; + /** 생성 */ + post: operations['create_3']; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/admin/teacher/assign/{teacherId}': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** 선생님에게 학생 배정 */ + post: operations['assignStudentsToTeacher']; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - '/api/v1/problems/search': { + '/api/admin/publish': { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** - * 문제 검색 - * @description 문항 ID, 문제명, 개념 태그리스트로 문제를 검색합니다. 개념 태그리스트는 OR 조건으로 검색하며 값이 없으면 쿼리파라미터에서 빼주세요 - */ + /** 검색 */ get: operations['search']; put?: never; - post?: never; + /** 생성 */ + post: operations['create_4']; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - '/api/v1/problemSet/search': { + '/api/admin/problem': { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** - * 문항세트 검색 - * @description 문항세트 타이틀, 문항세트 내 포함된 개념태그, 문항세트 내 포함된 문항 타이틀로 검색합니다. 발행상태는 발행이면 CONFIRMED, 아니면 NOT_CONFIRMED 입니다. - */ + /** 검색 */ get: operations['search_1']; put?: never; - post?: never; + /** 생성 */ + post: operations['createProblem']; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - '/api/v1/problemSet/confirm/search': { + '/api/admin/problem-set': { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** - * 발행용 문항세트 검색 - * @description 문항세트 타이틀, 문항세트 내 포함된 개념태그, 문항세트 내 포함된 문항 타이틀로 검색합니다. 발행상태가 CONFIRMED 문항세트만 조회됩니다.. - */ - get: operations['confirmSearch']; + /** 검색 */ + get: operations['search_2']; put?: never; - post?: never; + /** 생성 */ + post: operations['create_5']; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - '/api/v1/practiceTestTags': { + '/api/admin/practice-test': { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** 모의고사 목록 조회 */ - get: operations['getPracticeTestTags']; + /** 검색 */ + get: operations['search_3']; put?: never; - post?: never; + /** 생성 */ + post: operations['create_6']; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - '/api/v1/member/me': { + '/api/admin/notice': { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** - * 내 정보 조회 - * @description jwt accessToken을 통해 내 정보를 조회합니다. - */ - get: operations['getMyInfo']; + /** 학생 별 공지사항 전체 조회 */ + get: operations['getsAll_1']; put?: never; - post?: never; + /** 생성 */ + post: operations['create_7']; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - '/api/v1/images/{fileName}': { + '/api/admin/concept': { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** 이미지 업로드 완료 후 URL 조회 */ - get: operations['getImageUrl']; + /** 개념 태그 검색 */ + get: operations['search_4']; put?: never; - post?: never; + /** 개념태그 생성 */ + post: operations['create_8']; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/admin/concept/category': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** 대분류 검색 */ + get: operations['searchCategory']; + put?: never; + /** 대분류 생성 */ + post: operations['createCategory']; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/admin/auth/login/local': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** 이메일 로그인 */ + post: operations['login_1']; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - '/api/v1/images/problem/{problemId}/presigned-url': { + '/api/teacher/study/publish/weekly': { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** 이미지 업로드를 위한 presigned URL 발급 */ - get: operations['getProblemImagePresignedUrl']; + /** 학생 주간 발행(숙제) 조회 */ + get: operations['search_5']; put?: never; post?: never; delete?: never; @@ -416,15 +672,15 @@ export interface paths { patch?: never; trace?: never; }; - '/api/v1/conceptTags': { + '/api/teacher/study/publish/monthly': { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** 모든 개념 태그 리스트 조회 */ - get: operations['getConceptTags']; + /** 학생 월간 발행(숙제) 조회 */ + get: operations['searchMonthly']; put?: never; post?: never; delete?: never; @@ -433,18 +689,15 @@ export interface paths { patch?: never; trace?: never; }; - '/api/v1/client/problem/{publishId}': { + '/api/teacher/study/publish/detail/{id}': { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** - * 특정 발행 속 문항들 조회 - * @description 사용자에게 보여지는 특정 발행에 속한 문항을 조회합니다. - */ - get: operations['getProblemsInPublish']; + /** 발행(숙제) 상세 조회 */ + get: operations['getPublishById']; put?: never; post?: never; delete?: never; @@ -453,18 +706,15 @@ export interface paths { patch?: never; trace?: never; }; - '/api/v1/client/problem/{publishId}/{problemId}': { + '/api/teacher/study/progress/weekly': { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** - * 문항 조회 - * @description 사용자에게 보여지는 문항을 조회합니다. - */ - get: operations['getProblem_1']; + /** 학생 주간 학습 진행 상황 조회 */ + get: operations['getWeeklyProgress']; put?: never; post?: never; delete?: never; @@ -473,18 +723,15 @@ export interface paths { patch?: never; trace?: never; }; - '/api/v1/client/problem/{publishId}/{problemId}/{childProblemId}': { + '/api/teacher/study/problem/{id}': { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** - * 새끼문항 조회 - * @description 사용자에게 보여지는 새끼문항을 조회합니다. - */ - get: operations['getChildProblem']; + /** 문제 상세 조회 */ + get: operations['getProblemById']; put?: never; post?: never; delete?: never; @@ -493,18 +740,15 @@ export interface paths { patch?: never; trace?: never; }; - '/api/v1/client/problem/thumbnail/{publishId}/{problemId}': { + '/api/teacher/study/child-problem/{id}': { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** - * 문항 썸네일 조회 - * @description 바로 풀어보기/단계별로 풀어보기 화면에서 필요한 문항을 조회합니다. - */ - get: operations['getProblemThumbnail']; + /** 새끼 문제 상세 조회 */ + get: operations['getChildProblemById']; put?: never; post?: never; delete?: never; @@ -513,18 +757,15 @@ export interface paths { patch?: never; trace?: never; }; - '/api/v1/client/problem/child/{publishId}/{problemId}': { + '/api/teacher/students': { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** - * 문항에 포함된 새끼문항 정보 조회 - * @description 단계별로 풀어보기 이후 화면들에서 필요한 정보들을 조회합니다. - */ - get: operations['getChildProblems']; + /** 내 학생 전체 조회 */ + get: operations['getMyStudents']; put?: never; post?: never; delete?: never; @@ -533,18 +774,15 @@ export interface paths { patch?: never; trace?: never; }; - '/api/v1/client/problem/all/{year}/{month}': { + '/api/teacher/qna': { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** - * 전체 문제 조회 - * @description 월별 문제들에 대한 진행도와 정보들을 조회합니다. - */ - get: operations['getAllProblem']; + /** Q&A 목록 조회 */ + get: operations['gets_2']; put?: never; post?: never; delete?: never; @@ -553,18 +791,15 @@ export interface paths { patch?: never; trace?: never; }; - '/api/v1/client/home-feed': { + '/api/teacher/qna/{qnaId}': { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** - * 홈 피드 조회 - * @description 회원의 홈 피드 정보를 조회합니다. - */ - get: operations['getHomeFeed']; + /** Q&A 상세 조회 */ + get: operations['getById_1']; put?: never; post?: never; delete?: never; @@ -573,18 +808,15 @@ export interface paths { patch?: never; trace?: never; }; - '/api/v1/client/commentary': { + '/api/teacher/notice/available': { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** - * 해설 조회 - * @description 문항 별 해설/처방을 조회합니다. - */ - get: operations['getCommentary']; + /** 학생 별 유효 공지사항(현재 학생이 볼 수 있는 공지사항) 조회 */ + get: operations['getsAvailable']; put?: never; post?: never; delete?: never; @@ -593,18 +825,15 @@ export interface paths { patch?: never; trace?: never; }; - 'api/common/auth/refresh': { + '/api/teacher/me': { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** - * 토큰 재발급 - * @description 리프레시 토큰을 통해 새로운 액세스 토큰을 발급하고 새로운 리프레시 토큰을 쿠키에 설정합니다. - */ - get: operations['reissueToken']; + /** 내 정보 조회 */ + get: operations['getTeacherMe']; put?: never; post?: never; delete?: never; @@ -613,41 +842,274 @@ export interface paths { patch?: never; trace?: never; }; - '/api/v1/publish/{publishId}': { + '/api/student/study/publish/weekly': { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; + /** 주간 발행(숙제) 조회 */ + get: operations['search_6']; put?: never; post?: never; - /** - * 발행 삭제 - * @description 발행을 삭제합니다. - */ - delete: operations['deleteProblemSet']; + delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - '/api/v1/problems/{problemId}/child-problems/{childProblemId}': { + '/api/student/study/publish/monthly': { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; + /** 월간 발행(숙제) 조회 */ + get: operations['searchMonthly_1']; put?: never; post?: never; - /** - * 새끼 문항 삭제 - * @description 컨펌 이후에는 새끼 문항 삭제가 불가능합니다. - */ - delete: operations['deleteChildProblem']; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/student/study/publish/detail/{id}': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** 발행(숙제) 상세 조회 */ + get: operations['getPublishById_1']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/student/study/progress/weekly': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** 학생 주간 학습 진행 상황 조회 */ + get: operations['getWeeklyProgress_1']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/student/study/problem/{id}': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** 문제 상세 조회 */ + get: operations['getProblemById_1']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/student/study/child-problem/{id}': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** 새끼 문제 상세 조회 */ + get: operations['getChildProblemById_1']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/student/notice': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** 공지사항 목록 조회 */ + get: operations['getsAvailable_1']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/student/notice/count': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** 읽지 않은 공지사항 개수 조회 */ + get: operations['countAvailable']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/common/auth/refresh': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** 토큰 갱신 */ + get: operations['refresh']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/admin/student': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** 검색 */ + get: operations['search_7']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/admin/publish/{id}': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** 상세 조회 */ + get: operations['getById_2']; + put?: never; + post?: never; + /** 삭제 */ + delete: operations['delete_4']; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/admin/notice/available': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** 학생 별 유효 공지사항(현재 학생이 볼 수 있는 공지사항) 조회 */ + get: operations['getsAvailable_2']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/admin/auth/refresh': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** 토큰 갱신 */ + get: operations['refresh_1']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/common/upload-file/{id}': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + /** 삭제 */ + delete: operations['delete_5']; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/admin/teacher/{teacherId}/{studentId}': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + /** 선생님으로부터 학생 제거 */ + delete: operations['removeStudentFromTeacher']; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/admin/problem-set/{id}/{problemId}': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + /** 문제 세트에서 문제 삭제 */ + delete: operations['deleteItem']; options?: never; head?: never; patch?: never; @@ -657,449 +1119,1415 @@ export interface paths { export type webhooks = Record; export interface components { schemas: { - ErrorResponse: { - message: string; - /** @enum {string} */ - status: - | '100 CONTINUE' - | '101 SWITCHING_PROTOCOLS' - | '102 PROCESSING' - | '103 EARLY_HINTS' - | '103 CHECKPOINT' - | '200 OK' - | '201 CREATED' - | '202 ACCEPTED' - | '203 NON_AUTHORITATIVE_INFORMATION' - | '204 NO_CONTENT' - | '205 RESET_CONTENT' - | '206 PARTIAL_CONTENT' - | '207 MULTI_STATUS' - | '208 ALREADY_REPORTED' - | '226 IM_USED' - | '300 MULTIPLE_CHOICES' - | '301 MOVED_PERMANENTLY' - | '302 FOUND' - | '302 MOVED_TEMPORARILY' - | '303 SEE_OTHER' - | '304 NOT_MODIFIED' - | '305 USE_PROXY' - | '307 TEMPORARY_REDIRECT' - | '308 PERMANENT_REDIRECT' - | '400 BAD_REQUEST' - | '401 UNAUTHORIZED' - | '402 PAYMENT_REQUIRED' - | '403 FORBIDDEN' - | '404 NOT_FOUND' - | '405 METHOD_NOT_ALLOWED' - | '406 NOT_ACCEPTABLE' - | '407 PROXY_AUTHENTICATION_REQUIRED' - | '408 REQUEST_TIMEOUT' - | '409 CONFLICT' - | '410 GONE' - | '411 LENGTH_REQUIRED' - | '412 PRECONDITION_FAILED' - | '413 PAYLOAD_TOO_LARGE' - | '413 REQUEST_ENTITY_TOO_LARGE' - | '414 URI_TOO_LONG' - | '414 REQUEST_URI_TOO_LONG' - | '415 UNSUPPORTED_MEDIA_TYPE' - | '416 REQUESTED_RANGE_NOT_SATISFIABLE' - | '417 EXPECTATION_FAILED' - | '418 I_AM_A_TEAPOT' - | '419 INSUFFICIENT_SPACE_ON_RESOURCE' - | '420 METHOD_FAILURE' - | '421 DESTINATION_LOCKED' - | '422 UNPROCESSABLE_ENTITY' - | '423 LOCKED' - | '424 FAILED_DEPENDENCY' - | '425 TOO_EARLY' - | '426 UPGRADE_REQUIRED' - | '428 PRECONDITION_REQUIRED' - | '429 TOO_MANY_REQUESTS' - | '431 REQUEST_HEADER_FIELDS_TOO_LARGE' - | '451 UNAVAILABLE_FOR_LEGAL_REASONS' - | '500 INTERNAL_SERVER_ERROR' - | '501 NOT_IMPLEMENTED' - | '502 BAD_GATEWAY' - | '503 SERVICE_UNAVAILABLE' - | '504 GATEWAY_TIMEOUT' - | '505 HTTP_VERSION_NOT_SUPPORTED' - | '506 VARIANT_ALSO_NEGOTIATES' - | '507 INSUFFICIENT_STORAGE' - | '508 LOOP_DETECTED' - | '509 BANDWIDTH_LIMIT_EXCEEDED' - | '510 NOT_EXTENDED' - | '511 NETWORK_AUTHENTICATION_REQUIRED'; - }; - ChildProblemUpdateRequest: { + ChatUpdateRequest: { + content: string; + images?: number[]; + }; + ChatResp: { /** Format: int64 */ - childProblemId: number; - imageUrl?: string; - /** @enum {string} */ - answerType?: 'MULTIPLE_CHOICE' | 'SHORT_ANSWER'; - answer?: string; - conceptTagIds: number[]; - prescriptionImageUrls: string[]; + id: number; + isMine: boolean; + content: string; + images: components['schemas']['UploadFileResp'][]; }; - ProblemUpdateRequest: { - /** @enum {string} */ - problemType: 'GICHUL_PROBLEM' | 'VARIANT_PROBLEM' | 'CREATION_PROBLEM'; + ContentBlockResp: { /** Format: int64 */ - practiceTestId?: number; - /** Format: int32 */ - number?: number; - conceptTagIds: number[]; - answer?: string; - title?: string; + id: number; + style: string; /** Format: int32 */ - difficulty?: number; - memo?: string; - mainProblemImageUrl?: string; - mainAnalysisImageUrl?: string; - mainHandwritingExplanationImageUrl?: string; - readingTipImageUrl?: string; - seniorTipImageUrl?: string; - prescriptionImageUrls?: string[]; + rank: number; /** @enum {string} */ - answerType?: 'MULTIPLE_CHOICE' | 'SHORT_ANSWER'; - updateChildProblems: components['schemas']['ChildProblemUpdateRequest'][]; - /** Format: int32 */ - recommendedMinute?: number; - /** Format: int32 */ - recommendedSecond?: number; + type: 'TEXT' | 'IMAGE'; + data: string; }; - ChildProblemGetResponse: { + ContentResp: { /** Format: int64 */ - childProblemId: number; - imageUrl?: string; - /** @enum {string} */ - answerType?: 'MULTIPLE_CHOICE' | 'SHORT_ANSWER'; - answer?: string; - conceptTagIds: number[]; - prescriptionImageUrls: string[]; + id: number; + blocks: components['schemas']['ContentBlockResp'][]; }; - ProblemGetResponse: { + QnAMetaResp: { /** Format: int64 */ id: number; - problemCustomId: string; - conceptTagIds: number[]; - isConfirmed?: boolean; - /** Format: int64 */ - practiceTestId?: number; - /** Format: int32 */ - number?: number; - /** Format: int32 */ - difficulty?: number; - title?: string; - answer?: string; - memo?: string; + title: string; /** @enum {string} */ - problemType?: 'GICHUL_PROBLEM' | 'VARIANT_PROBLEM' | 'CREATION_PROBLEM'; - /** @enum {string} */ - answerType?: 'MULTIPLE_CHOICE' | 'SHORT_ANSWER'; - mainProblemImageUrl?: string; - mainHandwritingExplanationImageUrl?: string; - mainAnalysisImageUrl?: string; - readingTipImageUrl?: string; - seniorTipImageUrl?: string; - prescriptionImageUrls: string[]; - childProblems: components['schemas']['ChildProblemGetResponse'][]; - /** Format: int32 */ - recommendedMinute?: number; - /** Format: int32 */ - recommendedSecond?: number; - }; - ProblemSetUpdateRequest: { - problemSetTitle?: string; - problemIds: number[]; - }; - ProblemSubmitUpdateRequest: { - /** Format: int64 */ - publishId: number; - /** Format: int64 */ - problemId: number; - answer?: string; + type: + | 'PROBLEM_CONTENT' + | 'PROBLEM_POINTING_QUESTION' + | 'PROBLEM_POINTING_COMMENT' + | 'PROBLEM_MAIN_ANALYSIS' + | 'PROBLEM_MAIN_HAND_ANALYSIS' + | 'PROBLEM_READING_TIP_CONTENT' + | 'PROBLEM_ONE_STEP_MORE' + | 'CHILD_PROBLEM_CONTENT' + | 'CHILD_PROBLEM_POINTING_QUESTION' + | 'CHILD_PROBLEM_POINTING_COMMENT'; + /** Format: date */ + publishDate: string; }; - ChildProblemSubmitUpdateRequest: { - /** Format: int64 */ - publishId: number; + QnAResp: { /** Format: int64 */ - childProblemId: number; - answer?: string; - }; - ChildProblemSubmitUpdateResponse: { + id: number; + title: string; /** @enum {string} */ - status: 'CORRECT' | 'INCORRECT' | 'RETRY_CORRECT' | 'NOT_STARTED'; - answer: string; + type: + | 'PROBLEM_CONTENT' + | 'PROBLEM_POINTING_QUESTION' + | 'PROBLEM_POINTING_COMMENT' + | 'PROBLEM_MAIN_ANALYSIS' + | 'PROBLEM_MAIN_HAND_ANALYSIS' + | 'PROBLEM_READING_TIP_CONTENT' + | 'PROBLEM_ONE_STEP_MORE' + | 'CHILD_PROBLEM_CONTENT' + | 'CHILD_PROBLEM_POINTING_QUESTION' + | 'CHILD_PROBLEM_POINTING_COMMENT'; + /** Format: date */ + publishDate: string; + contentTitle: string; + content: components['schemas']['ContentResp']; + question: string; + chats: components['schemas']['ChatResp'][]; }; - ChildProblemSubmitUpdateIncorrectRequest: { - /** Format: int64 */ - publishId: number; + UploadFileResp: { /** Format: int64 */ - childProblemId: number; + id: number; + fileName: string; + url: string; }; - PublishPostRequest: { + NoticeUpdateRequest: { /** Format: date */ - publishedDate: string; - /** Format: int64 */ - problemSetId: number; + startAt: string; + /** Format: date */ + endAt: string; + content: string; }; - IdResponse: { + NoticeResp: { /** Format: int64 */ id: number; + student: components['schemas']['StudentResp']; + /** Format: date */ + startAt: string; + /** Format: date */ + endAt: string; + isRead: boolean; + content: string; }; - ProblemPostRequest: { - /** @enum {string} */ - problemType: 'GICHUL_PROBLEM' | 'VARIANT_PROBLEM' | 'CREATION_PROBLEM'; + StudentResp: { /** Format: int64 */ - practiceTestId?: number; + id: number; + name: string; /** Format: int32 */ - number?: number; + grade: number; + isFirstLogin: boolean; }; - ProblemPostResponse: { - /** Format: int64 */ - id: number; - problemCustomId: string; + QnAUpdateRequest: { + question: string; }; - ProblemSubmitCreateRequest: { - /** Format: int64 */ - publishId: number; - /** Format: int64 */ - problemId: number; + StudentUpdateRequest: { + name: string; + /** Format: int32 */ + grade: number; }; - ChildProblemSubmitCreateRequest: { + TeacherUpdateRequest: { + name: string; + email: string; + newPassword?: string; + }; + TeacherResp: { /** Format: int64 */ - publishId: number; + id: number; + name: string; + email: string; + students: components['schemas']['StudentResp'][]; + }; + 'ChildProblemUpdateDTO.Request': { + /** Format: int64 */ + id?: number; + /** Format: int32 */ + no?: number; + problemContent?: components['schemas']['ContentUpdateRequest']; + /** @enum {string} */ + answerType?: 'MULTIPLE_CHOICE' | 'SHORT_ANSWER'; + /** Format: int32 */ + answer?: number; + concepts?: number[]; + pointings?: components['schemas']['PointingUpdateRequest'][]; + }; + ContentBlockUpdateRequest: { + /** Format: int64 */ + id: number; + /** Format: int32 */ + rank: number; + /** @enum {string} */ + type?: 'TEXT' | 'IMAGE'; + data?: string; + }; + ContentUpdateRequest: { + /** Format: int64 */ + id?: number; + blocks?: components['schemas']['ContentBlockUpdateRequest'][]; + }; + PointingUpdateRequest: { + /** Format: int64 */ + id?: number; + /** Format: int32 */ + no?: number; + questionContent?: components['schemas']['ContentUpdateRequest']; + commentContent?: components['schemas']['ContentUpdateRequest']; + concepts?: number[]; + }; + ProblemUpdateRequest: { + customId: string; + /** @enum {string} */ + problemType: 'GICHUL_PROBLEM' | 'VARIANT_PROBLEM' | 'CREATION_PROBLEM'; + /** Format: int64 */ + practiceTestId?: number; + /** Format: int32 */ + practiceTestNo?: number; + title: string; + concepts?: number[]; + /** @enum {string} */ + answerType: 'MULTIPLE_CHOICE' | 'SHORT_ANSWER'; + /** Format: int32 */ + answer: number; + /** Format: int32 */ + difficulty: number; + /** Format: int32 */ + recommendedTimeSec: number; + memo?: string; + problemContent: components['schemas']['ContentUpdateRequest']; + pointings?: components['schemas']['PointingUpdateRequest'][]; + /** Format: int64 */ + mainAnalysisImageId?: number; + /** Format: int64 */ + mainHandAnalysisImageId?: number; + readingTipContent?: components['schemas']['ContentUpdateRequest']; + oneStepMoreContent?: components['schemas']['ContentUpdateRequest']; + childProblems?: components['schemas']['ChildProblemUpdateDTO.Request'][]; + }; + ChildProblemWithStudyInfoResp: { + /** Format: int64 */ + id: number; + /** Format: int32 */ + no: number; + problemContent: components['schemas']['ContentResp']; + /** @enum {string} */ + answerType: 'MULTIPLE_CHOICE' | 'SHORT_ANSWER'; + /** Format: int32 */ + answer: number; + concepts: components['schemas']['ConceptResp'][]; + pointings: components['schemas']['PointingWithFeedbackResp'][]; + /** Format: int32 */ + submitAnswer: number; + isCorrect: boolean; + isDone: boolean; + }; + ConceptCategoryResp: { + /** Format: int64 */ + id: number; + name: string; + }; + ConceptResp: { + /** Format: int64 */ + id: number; + name: string; + category: components['schemas']['ConceptCategoryResp']; + }; + PointingResp: { + /** Format: int64 */ + id: number; + /** Format: int32 */ + no: number; + questionContent: components['schemas']['ContentResp']; + commentContent: components['schemas']['ContentResp']; + concepts: components['schemas']['ConceptResp'][]; + }; + PointingWithFeedbackResp: { + /** Format: int64 */ + id: number; + /** Format: int32 */ + no: number; + questionContent: components['schemas']['ContentResp']; + commentContent: components['schemas']['ContentResp']; + concepts: components['schemas']['ConceptResp'][]; + isUnderstood?: boolean; + }; + PracticeTestResp: { + /** Format: int64 */ + id: number; + /** Format: int32 */ + year: number; + /** Format: int32 */ + month: number; + /** Format: int32 */ + grade: number; + name: string; + displayName: string; + }; + ProblemInfoResp: { + /** Format: int64 */ + id: number; + customId: string; + /** @enum {string} */ + problemType: 'GICHUL_PROBLEM' | 'VARIANT_PROBLEM' | 'CREATION_PROBLEM'; + practiceTest: components['schemas']['PracticeTestResp']; + /** Format: int32 */ + practiceTestNo: number; + problemContent: components['schemas']['ContentResp']; + title: string; + /** @enum {string} */ + answerType: 'MULTIPLE_CHOICE' | 'SHORT_ANSWER'; + /** Format: int32 */ + answer: number; + /** Format: int32 */ + difficulty: number; + /** Format: int32 */ + recommendedTimeSec: number; + memo: string; + concepts: components['schemas']['ConceptResp'][]; + mainAnalysisImage: components['schemas']['UploadFileResp']; + mainHandAnalysisImage: components['schemas']['UploadFileResp']; + readingTipContent: components['schemas']['ContentResp']; + oneStepMoreContent: components['schemas']['ContentResp']; + pointings: components['schemas']['PointingResp'][]; + childProblems: components['schemas']['ChildProblemWithStudyInfoResp'][]; + }; + ProblemMetaResp: { + /** Format: int64 */ + id: number; + customId: string; + /** @enum {string} */ + problemType: 'GICHUL_PROBLEM' | 'VARIANT_PROBLEM' | 'CREATION_PROBLEM'; + practiceTest: components['schemas']['PracticeTestResp']; + /** Format: int32 */ + practiceTestNo: number; + problemContent: components['schemas']['ContentResp']; + title: string; + /** @enum {string} */ + answerType: 'MULTIPLE_CHOICE' | 'SHORT_ANSWER'; + /** Format: int32 */ + answer: number; + /** Format: int32 */ + difficulty: number; + /** Format: int32 */ + recommendedTimeSec: number; + memo: string; + concepts: components['schemas']['ConceptResp'][]; + }; + SubmissionResp: { + /** Format: int32 */ + submitAnswer: number; + isCorrect: boolean; + isDone: boolean; + }; + ProblemSetItemRequest: { + /** Format: int32 */ + no: number; /** Format: int64 */ problemId: number; }; - LoginRequest: { - provider?: string; - redirectUri?: string; + ProblemSetUpdateRequest: { + title: string; + /** @enum {string} */ + status: 'CONFIRMED' | 'DOING'; + problems?: components['schemas']['ProblemSetItemRequest'][]; + }; + ProblemSetItemResp: { + /** Format: int64 */ + id: number; + /** Format: int32 */ + no: number; + problem: components['schemas']['ProblemMetaResp']; + }; + ProblemSetMetaResp: { + /** Format: int64 */ + id: number; + title: string; + /** @enum {string} */ + status: 'CONFIRMED' | 'DOING'; + firstProblem: components['schemas']['ProblemMetaResp']; + }; + ProblemSetResp: { + /** Format: int64 */ + id: number; + title: string; + /** @enum {string} */ + status: 'CONFIRMED' | 'DOING'; + firstProblem: components['schemas']['ProblemMetaResp']; + problems: components['schemas']['ProblemSetItemResp'][]; + }; + ProblemSetUpdateStatusReq: { + /** @enum {string} */ + status: 'CONFIRMED' | 'DOING'; + }; + Request: { + /** Format: int32 */ + year: number; + /** Format: int32 */ + month: number; + /** Format: int32 */ + grade: number; + name: string; + }; + ConceptUpdateRequest: { + name: string; + /** Format: int64 */ + categoryId: number; + }; + ConceptCategoryUpdateRequest: { + name: string; + }; + ChatCreateRequest: { + /** Format: int64 */ + qnaId: number; + content: string; + images?: number[]; }; - LoginResponse: { + NoticeCreateRequest: { + /** Format: date */ + startAt: string; + /** Format: date */ + endAt: string; + content: string; /** Format: int64 */ - provider?: string; - loginUrl?: string; + studentId: number; + }; + TeacherLoginReq: { + email: string; + password: string; }; - AccessTokenResponse: { + JwtResp: { accessToken: string; }; - AdminLoginRequest: { + TeacherTokenResp: { + /** Format: int64 */ + id: number; + name: string; + email: string; + students: components['schemas']['StudentResp'][]; + token?: components['schemas']['JwtResp']; + }; + PointingFeedbackRequest: { + /** Format: int64 */ + pointingId: number; + isUnderstood: boolean; + }; + SubmissionRequest: { + /** Format: int64 */ + publishId: number; + /** Format: int64 */ + problemId?: number; + /** Format: int64 */ + childProblemId?: number; + /** Format: int32 */ + submitAnswer: number; + }; + /** @description problemId, childProblemId, pointingId 중 하나만 입력 가능 */ + QnACreateRequest: { + /** Format: int64 */ + publishId: number; + /** @enum {string} */ + type: + | 'PROBLEM_CONTENT' + | 'PROBLEM_POINTING_QUESTION' + | 'PROBLEM_POINTING_COMMENT' + | 'PROBLEM_MAIN_ANALYSIS' + | 'PROBLEM_MAIN_HAND_ANALYSIS' + | 'PROBLEM_READING_TIP_CONTENT' + | 'PROBLEM_ONE_STEP_MORE' + | 'CHILD_PROBLEM_CONTENT' + | 'CHILD_PROBLEM_POINTING_QUESTION' + | 'CHILD_PROBLEM_POINTING_COMMENT'; + /** Format: int64 */ + problemId?: number; + /** Format: int64 */ + childProblemId?: number; + /** Format: int64 */ + pointingId?: number; + question: string; + }; + QnACheckRequest: { + /** Format: int64 */ + publishId: number; + /** @enum {string} */ + type: + | 'PROBLEM_CONTENT' + | 'PROBLEM_POINTING_QUESTION' + | 'PROBLEM_POINTING_COMMENT' + | 'PROBLEM_MAIN_ANALYSIS' + | 'PROBLEM_MAIN_HAND_ANALYSIS' + | 'PROBLEM_READING_TIP_CONTENT' + | 'PROBLEM_ONE_STEP_MORE' + | 'CHILD_PROBLEM_CONTENT' + | 'CHILD_PROBLEM_POINTING_QUESTION' + | 'CHILD_PROBLEM_POINTING_COMMENT'; + /** + * Format: int64 + * @description 메인문제ID(메인 문제에 대한 질문일 경우) + */ + problemId?: number; + /** + * Format: int64 + * @description 새끼문제ID(새끼 문제에 대한 질문일 경우) + */ + childProblemId?: number; + /** + * Format: int64 + * @description 포인팅ID(포인팅에 대한 질문일 경우) + */ + pointingId?: number; + }; + QnACheckResp: { + /** Format: int64 */ + id: number; + isExist: boolean; + }; + SocialLoginReq: { + /** @enum {string} */ + provider: 'KAKAO' | 'GOOGLE'; + redirectUri: string; + }; + SocialLoginUrlResp: { + /** @enum {string} */ + provider: 'KAKAO' | 'GOOGLE'; + loginUrl: string; + }; + PreSignedReq: { + fileName: string; + }; + PreSignedResp: { + file: components['schemas']['UploadFileResp']; + contentDisposition: string; + uploadUrl: string; + }; + AdminCreateRequest: { + email: string; + password: string; + }; + TeacherCreateRequest: { + name: string; email: string; password: string; }; - PublishMonthGetResponse: { + TeacherStudentAssignReq: { + students: number[]; + }; + PublishCreateRequest: { + /** Format: int64 */ + problemSetId: number; /** Format: int64 */ - publishId?: number; + studentId: number; /** Format: date */ - date?: string; - problemSetInfo?: components['schemas']['PublishProblemSetResponse']; + publishAt: string; }; - PublishProblemSetResponse: { + ProblemWithStudyInfoResp: { /** Format: int64 */ - id?: number; - title?: string; + id: number; + customId: string; + /** @enum {string} */ + problemType: 'GICHUL_PROBLEM' | 'VARIANT_PROBLEM' | 'CREATION_PROBLEM'; + practiceTest: components['schemas']['PracticeTestResp']; + /** Format: int32 */ + practiceTestNo: number; + problemContent: components['schemas']['ContentResp']; + title: string; + /** @enum {string} */ + answerType: 'MULTIPLE_CHOICE' | 'SHORT_ANSWER'; + /** Format: int32 */ + answer: number; + /** Format: int32 */ + difficulty: number; + /** Format: int32 */ + recommendedTimeSec: number; + memo: string; + concepts: components['schemas']['ConceptResp'][]; + mainAnalysisImage: components['schemas']['UploadFileResp']; + mainHandAnalysisImage: components['schemas']['UploadFileResp']; + readingTipContent: components['schemas']['ContentResp']; + oneStepMoreContent: components['schemas']['ContentResp']; + pointings: components['schemas']['PointingWithFeedbackResp'][]; + /** Format: int32 */ + submitAnswer: number; + isCorrect: boolean; + isDone: boolean; + childProblems: components['schemas']['ChildProblemWithStudyInfoResp'][]; }; - ProblemSearchGetResponse: { + PublishMetaResp: { /** Format: int64 */ - problemId: number; - problemCustomId: string; - problemTitle?: string; + id: number; + /** Format: date */ + publishAt: string; + /** @enum {string} */ + progress: 'DONE' | 'DOING' | 'NONE'; + problemSet: components['schemas']['ProblemSetMetaResp']; + }; + PublishProblemGroupResp: { + /** Format: int32 */ + no: number; + /** @enum {string} */ + progress: 'DONE' | 'DOING' | 'NONE'; + problem: components['schemas']['ProblemWithStudyInfoResp']; + childProblems: components['schemas']['ChildProblemWithStudyInfoResp'][]; + }; + PublishResp: { + /** Format: int64 */ + id: number; + /** Format: date */ + publishAt: string; + /** @enum {string} */ + progress: 'DONE' | 'DOING' | 'NONE'; + problemSet: components['schemas']['ProblemSetMetaResp']; + data: components['schemas']['PublishProblemGroupResp'][]; + }; + ChildProblemCreateRequest: { + /** Format: int32 */ + no?: number; + problemContent?: components['schemas']['ContentCreateRequest']; + /** @enum {string} */ + answerType?: 'MULTIPLE_CHOICE' | 'SHORT_ANSWER'; + /** Format: int32 */ + answer?: number; + concepts?: number[]; + pointings?: components['schemas']['PointingCreateRequest'][]; + }; + ContentBlockCreateRequest: { + /** Format: int32 */ + rank: number; + /** @enum {string} */ + type?: 'TEXT' | 'IMAGE'; + data?: string; + style?: string; + }; + ContentCreateRequest: { + blocks?: components['schemas']['ContentBlockCreateRequest'][]; + }; + PointingCreateRequest: { + /** Format: int32 */ + no?: number; + questionContent?: components['schemas']['ContentCreateRequest']; + commentContent?: components['schemas']['ContentCreateRequest']; + concepts?: number[]; + }; + ProblemCreateRequest: { + customId: string; + /** @enum {string} */ + problemType: 'GICHUL_PROBLEM' | 'VARIANT_PROBLEM' | 'CREATION_PROBLEM'; + /** Format: int64 */ + practiceTestId?: number; + /** Format: int32 */ + practiceTestNo?: number; + title: string; + concepts?: number[]; + /** @enum {string} */ + answerType: 'MULTIPLE_CHOICE' | 'SHORT_ANSWER'; + /** Format: int32 */ + answer: number; + /** Format: int32 */ + difficulty: number; + /** Format: int32 */ + recommendedTimeSec: number; memo?: string; - mainProblemImageUrl?: string; - tagNames: string[]; + problemContent: components['schemas']['ContentCreateRequest']; + pointings?: components['schemas']['PointingCreateRequest'][]; + /** Format: int64 */ + mainAnalysisImageId?: number; + /** Format: int64 */ + mainHandAnalysisImageId?: number; + readingTipContent?: components['schemas']['ContentCreateRequest']; + oneStepMoreContent?: components['schemas']['ContentCreateRequest']; + childProblems?: components['schemas']['ChildProblemCreateRequest'][]; + }; + ProblemSetCreateRequest: { + title: string; + problems?: components['schemas']['ProblemSetItemRequest'][]; + }; + PracticeTestCreateRequest: { + /** Format: int32 */ + year: number; + /** Format: int32 */ + month: number; + /** Format: int32 */ + grade: number; + name: string; + }; + ConceptCreateRequest: { + name: string; + /** Format: int64 */ + categoryId: number; + }; + ConceptCategoryCreateRequest: { + name: string; + }; + AdminLoginReq: { + email: string; + password: string; + }; + AdminTokenResp: { + /** Format: int64 */ + id: number; + email: string; + token: components['schemas']['JwtResp']; + }; + ListRespPublishMetaResp: { + /** Format: int32 */ + total: number; + data: components['schemas']['PublishMetaResp'][]; + }; + PublishStudentProgressResp: { + /** Format: double */ + progress: number; + }; + ListRespStudentResp: { + /** Format: int32 */ + total: number; + data: components['schemas']['StudentResp'][]; + }; + PageRespQnAMetaResp: { + /** Format: int32 */ + page: number; + /** Format: int32 */ + size: number; + /** Format: int32 */ + lastPage: number; + data: components['schemas']['QnAMetaResp'][]; + }; + ListRespNoticeResp: { + /** Format: int32 */ + total: number; + data: components['schemas']['NoticeResp'][]; + }; + NoticeUnreadCountResp: { + /** Format: int64 */ + count?: number; + }; + ListRespTeacherResp: { + /** Format: int32 */ + total: number; + data: components['schemas']['TeacherResp'][]; + }; + PageRespStudentResp: { + /** Format: int32 */ + page: number; + /** Format: int32 */ + size: number; + /** Format: int32 */ + lastPage: number; + data: components['schemas']['StudentResp'][]; + }; + PageRespProblemMetaResp: { + /** Format: int32 */ + page: number; + /** Format: int32 */ + size: number; + /** Format: int32 */ + lastPage: number; + data: components['schemas']['ProblemMetaResp'][]; + }; + PageRespProblemSetResp: { + /** Format: int32 */ + page: number; + /** Format: int32 */ + size: number; + /** Format: int32 */ + lastPage: number; + data: components['schemas']['ProblemSetResp'][]; + }; + PageRespPracticeTestResp: { + /** Format: int32 */ + page: number; + /** Format: int32 */ + size: number; + /** Format: int32 */ + lastPage: number; + data: components['schemas']['PracticeTestResp'][]; + }; + PageRespConceptResp: { + /** Format: int32 */ + page: number; + /** Format: int32 */ + size: number; + /** Format: int32 */ + lastPage: number; + data: components['schemas']['ConceptResp'][]; + }; + PageRespConceptCategoryResp: { + /** Format: int32 */ + page: number; + /** Format: int32 */ + size: number; + /** Format: int32 */ + lastPage: number; + data: components['schemas']['ConceptCategoryResp'][]; + }; + }; + responses: never; + parameters: never; + requestBodies: never; + headers: never; + pathItems: never; +} +export type $defs = Record; +export interface operations { + updateChat: { + parameters: { + query?: never; + header?: never; + path: { + chatId: number; + }; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['ChatUpdateRequest']; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + '*/*': components['schemas']['QnAResp']; + }; + }; + }; + }; + deleteChat: { + parameters: { + query?: never; + header?: never; + path: { + chatId: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + '*/*': components['schemas']['QnAResp']; + }; + }; + }; + }; + update: { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['NoticeUpdateRequest']; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + '*/*': components['schemas']['NoticeResp']; + }; + }; + }; + }; + getById: { + parameters: { + query?: never; + header?: never; + path: { + qnaId: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + '*/*': components['schemas']['QnAResp']; + }; + }; + }; + }; + update_1: { + parameters: { + query?: never; + header?: never; + path: { + qnaId: number; + }; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['QnAUpdateRequest']; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + '*/*': components['schemas']['QnAResp']; + }; + }; + }; + }; + delete: { + parameters: { + query?: never; + header?: never; + path: { + qnaId: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + updateChat_1: { + parameters: { + query?: never; + header?: never; + path: { + chatId: number; + }; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['ChatUpdateRequest']; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + '*/*': components['schemas']['QnAResp']; + }; + }; + }; + }; + deleteChat_1: { + parameters: { + query?: never; + header?: never; + path: { + chatId: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + '*/*': components['schemas']['QnAResp']; + }; + }; + }; + }; + readNotice: { + parameters: { + query?: never; + header?: never; + path: { + noticeId: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + '*/*': components['schemas']['NoticeResp']; + }; + }; + }; + }; + me: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + '*/*': components['schemas']['StudentResp']; + }; + }; + }; + }; + update_2: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['StudentUpdateRequest']; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + '*/*': components['schemas']['StudentResp']; + }; + }; + }; + }; + update_3: { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['TeacherUpdateRequest']; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + '*/*': components['schemas']['TeacherResp']; + }; + }; + }; + }; + getProblem: { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + '*/*': components['schemas']['ProblemInfoResp']; + }; + }; + }; + }; + updateProblem: { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['ProblemUpdateRequest']; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + '*/*': components['schemas']['ProblemInfoResp']; + }; + }; + }; + }; + deleteProblem: { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + getProblemSet: { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + '*/*': components['schemas']['ProblemSetResp']; + }; + }; + }; + }; + update_4: { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['ProblemSetUpdateRequest']; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + '*/*': components['schemas']['ProblemSetResp']; + }; + }; + }; + }; + delete_1: { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + update_5: { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; }; - ProblemSetGetResponse: { - /** Format: int64 */ - id?: number; - title?: string; - /** @enum {string} */ - confirmStatus?: 'CONFIRMED' | 'NOT_CONFIRMED'; - publishedDates: string[]; - problemSummaries: components['schemas']['ProblemSummaryResponse'][]; + requestBody: { + content: { + 'application/json': components['schemas']['ProblemSetUpdateStatusReq']; + }; }; - ProblemSummaryResponse: { - /** Format: int64 */ - problemId: number; - problemCustomId: string; - problemTitle?: string; - memo?: string; - mainProblemImageUrl?: string; - tagNames: string[]; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + '*/*': components['schemas']['ProblemSetResp']; + }; + }; }; - ProblemSetSearchGetResponse: { - /** Format: int64 */ - id: number; - problemSetTitle?: string; - /** @enum {string} */ - confirmStatus?: 'CONFIRMED' | 'NOT_CONFIRMED'; - problemThumbnailResponses: components['schemas']['ProblemThumbnailResponse'][]; + }; + toggleStatus: { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; }; - ProblemThumbnailResponse: { - problemTitle?: string; - problemMemo?: string; - mainProblemImageUrl?: string; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + '*/*': components['schemas']['ProblemSetResp']; + }; + }; }; - PracticeTestTagResponse: { - /** Format: int64 */ - id: number; - name: string; + }; + update_6: { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; }; - MemberGetResponse: { - /** Format: int64 */ - id?: number; - name?: string; - email?: string; + requestBody: { + content: { + 'application/json': components['schemas']['Request']; + }; }; - PresignedUrlResponse: { - presignedUrl: string; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + '*/*': components['schemas']['PracticeTestResp']; + }; + }; }; - ConceptTagResponse: { - /** Format: int64 */ - id: number; - name: string; + }; + delete_2: { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; }; - ProblemFeedProgressesGetResponse: { - /** Format: int64 */ - problemId?: number; - /** @enum {string} */ - status?: 'CORRECT' | 'INCORRECT' | 'IN_PROGRESS' | 'RETRY_CORRECT' | 'NOT_STARTED'; - childProblemStatuses?: ('CORRECT' | 'INCORRECT' | 'RETRY_CORRECT' | 'NOT_STARTED')[]; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; }; - PublishClientGetResponse: { - /** Format: int64 */ - publishId?: number; - /** Format: date */ - date?: string; - title?: string; - problems?: components['schemas']['ProblemFeedProgressesGetResponse'][]; + }; + update_7: { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; }; - ProblemClientGetResponse: { - /** Format: int32 */ - number: number; - imageUrl: string; - /** Format: int32 */ - recommendedMinute: number; - /** Format: int32 */ - recommendedSecond: number; - /** @enum {string} */ - status: 'CORRECT' | 'INCORRECT' | 'IN_PROGRESS' | 'RETRY_CORRECT' | 'NOT_STARTED'; - childProblemStatuses: ('CORRECT' | 'INCORRECT' | 'RETRY_CORRECT' | 'NOT_STARTED')[]; - /** @enum {string} */ - answerType: 'MULTIPLE_CHOICE' | 'SHORT_ANSWER'; - answer: string; + requestBody: { + content: { + 'application/json': components['schemas']['NoticeUpdateRequest']; + }; }; - ChildProblemClientGetResponse: { - /** Format: int32 */ - problemNumber: number; - /** Format: int32 */ - childProblemNumber: number; - imageUrl: string; - /** @enum {string} */ - status: 'CORRECT' | 'INCORRECT' | 'RETRY_CORRECT' | 'NOT_STARTED'; - /** @enum {string} */ - answerType: 'MULTIPLE_CHOICE' | 'SHORT_ANSWER'; - answer: string; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + '*/*': components['schemas']['NoticeResp']; + }; + }; }; - ProblemClientThumbnailResponse: { - /** Format: int32 */ - number?: number; - imageUrl?: string; - /** Format: int32 */ - recommendedMinute?: number; - /** Format: int32 */ - recommendedSecond?: number; + }; + update_8: { + parameters: { + query?: never; + header?: never; + path: { + conceptId: number; + }; + cookie?: never; }; - ChildProblemsClientGetResponse: { - mainProblemImageUrl: string; - childProblemIds: number[]; + requestBody: { + content: { + 'application/json': components['schemas']['ConceptUpdateRequest']; + }; }; - AllProblemGetResponse: { - /** Format: int64 */ - publishId?: number; - /** Format: date */ - date?: string; - /** @enum {string} */ - progress?: 'COMPLETE' | 'INCOMPLETE' | 'IN_PROGRESS'; - problemStatuses?: ( - | 'CORRECT' - | 'INCORRECT' - | 'IN_PROGRESS' - | 'RETRY_CORRECT' - | 'NOT_STARTED' - )[]; - mainProblemImageUrl?: string; - }; - DailyProgressResponse: { - /** Format: date */ - date?: string; - /** @enum {string} */ - progressStatus?: 'COMPLETED' | 'IN_PROGRESS' | 'NOT_STARTED'; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + '*/*': components['schemas']['ConceptResp']; + }; + }; }; - HomeFeedResponse: { - dailyProgresses?: components['schemas']['DailyProgressResponse'][]; - problemSets?: components['schemas']['ProblemSetHomeFeedResponse'][]; + }; + delete_3: { + parameters: { + query?: never; + header?: never; + path: { + conceptId: number; + }; + cookie?: never; }; - ProblemHomeFeedResponse: { - /** Format: int64 */ - problemId?: number; - mainProblemImageUrl?: string; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; }; - ProblemSetHomeFeedResponse: { - /** Format: date */ - date?: string; - /** Format: int64 */ - publishId?: number; - title?: string; - /** Format: int64 */ - submitCount?: number; - problemHomeFeedResponse?: components['schemas']['ProblemHomeFeedResponse']; + }; + updateCategory: { + parameters: { + query?: never; + header?: never; + path: { + categoryId: number; + }; + cookie?: never; }; - ChildProblemDetailResponse: { - imageUrl?: string; - prescriptionImageUrls?: string[]; - /** @enum {string} */ - submitStatus?: 'CORRECT' | 'INCORRECT' | 'RETRY_CORRECT' | 'NOT_STARTED'; + requestBody: { + content: { + 'application/json': components['schemas']['ConceptCategoryUpdateRequest']; + }; }; - CommentaryGetResponse: { - /** Format: int32 */ - problemNumber?: number; - answer?: string; - mainAnalysisImageUrl?: string; - mainHandwritingExplanationImageUrl?: string; - readingTipImageUrl?: string; - seniorTipImageUrl?: string; - /** @enum {string} */ - answerType?: 'MULTIPLE_CHOICE' | 'SHORT_ANSWER'; - prescription?: components['schemas']['PrescriptionResponse']; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + '*/*': components['schemas']['ConceptCategoryResp']; + }; + }; }; - PrescriptionResponse: { - childProblem?: components['schemas']['ChildProblemDetailResponse'][]; - mainProblem?: components['schemas']['ProblemDetailResponse']; + }; + deleteCategory: { + parameters: { + query?: never; + header?: never; + path: { + categoryId: number; + }; + cookie?: never; }; - ProblemDetailResponse: { - imageUrl?: string; - prescriptionImageUrls?: string[]; - /** @enum {string} */ - submitStatus?: 'CORRECT' | 'INCORRECT' | 'IN_PROGRESS' | 'RETRY_CORRECT' | 'NOT_STARTED'; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; }; }; - responses: never; - parameters: never; - requestBodies: never; - headers: never; - pathItems: never; -} -export type $defs = Record; -export interface operations { - getProblem: { + addChat: { parameters: { query?: never; header?: never; - path: { - id: number; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['ChatCreateRequest']; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + '*/*': components['schemas']['QnAResp']; + }; + }; + }; + }; + getsAll: { + parameters: { + query: { + studentId: number; }; + header?: never; + path?: never; cookie?: never; }; requestBody?: never; @@ -1110,34 +2538,45 @@ export interface operations { [name: string]: unknown; }; content: { - '*/*': { - data: components['schemas']['ProblemGetResponse']; - }; + '*/*': components['schemas']['ListRespNoticeResp']; }; }; - /** @description Internal Server Error */ - 500: { + }; + }; + create: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['NoticeCreateRequest']; + }; + }; + responses: { + /** @description OK */ + 200: { headers: { [name: string]: unknown; }; content: { - '*/*': components['schemas']['ErrorResponse']; + '*/*': components['schemas']['NoticeResp']; }; }; }; }; - updateProblem: { + login: { parameters: { query?: never; header?: never; - path: { - id: number; - }; + path?: never; cookie?: never; }; requestBody: { content: { - 'application/json': components['schemas']['ProblemUpdateRequest']; + 'application/json': components['schemas']['TeacherLoginReq']; }; }; responses: { @@ -1147,32 +2586,23 @@ export interface operations { [name: string]: unknown; }; content: { - '*/*': { - data: components['schemas']['ProblemGetResponse']; - }; - }; - }; - /** @description Internal Server Error */ - 500: { - headers: { - [name: string]: unknown; - }; - content: { - '*/*': components['schemas']['ErrorResponse']; + '*/*': components['schemas']['TeacherTokenResp']; }; }; }; }; - deleteProblem: { + feedback: { parameters: { query?: never; header?: never; - path: { - id: number; - }; + path?: never; cookie?: never; }; - requestBody?: never; + requestBody: { + content: { + 'application/json': components['schemas']['PointingFeedbackRequest']; + }; + }; responses: { /** @description OK */ 200: { @@ -1181,24 +2611,37 @@ export interface operations { }; content?: never; }; - /** @description Internal Server Error */ - 500: { + }; + }; + submit: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['SubmissionRequest']; + }; + }; + responses: { + /** @description OK */ + 200: { headers: { [name: string]: unknown; }; content: { - '*/*': components['schemas']['ErrorResponse']; + '*/*': components['schemas']['SubmissionResp']; }; }; }; }; - getProblemSet: { + gets: { parameters: { query?: never; header?: never; - path: { - problemSetId: number; - }; + path?: never; cookie?: never; }; requestBody?: never; @@ -1209,94 +2652,71 @@ export interface operations { [name: string]: unknown; }; content: { - '*/*': { - data: components['schemas']['ProblemSetGetResponse']; - }; - }; - }; - /** @description Internal Server Error */ - 500: { - headers: { - [name: string]: unknown; - }; - content: { - '*/*': components['schemas']['ErrorResponse']; + '*/*': components['schemas']['PageRespQnAMetaResp']; }; }; }; }; - updateProblemSet: { + create_1: { parameters: { query?: never; header?: never; - path: { - problemSetId: number; - }; + path?: never; cookie?: never; }; requestBody: { content: { - 'application/json': components['schemas']['ProblemSetUpdateRequest']; + 'application/json': components['schemas']['QnACreateRequest']; }; }; responses: { /** @description OK */ 200: { - headers: { - [name: string]: unknown; - }; - content?: never; - }; - /** @description Internal Server Error */ - 500: { headers: { [name: string]: unknown; }; content: { - '*/*': components['schemas']['ErrorResponse']; + '*/*': components['schemas']['QnAResp']; }; }; }; }; - deleteProblemSet_1: { + checkExists: { parameters: { query?: never; header?: never; - path: { - problemSetId: number; - }; + path?: never; cookie?: never; }; - requestBody?: never; + requestBody: { + content: { + 'application/json': components['schemas']['QnACheckRequest']; + }; + }; responses: { /** @description OK */ 200: { - headers: { - [name: string]: unknown; - }; - content?: never; - }; - /** @description Internal Server Error */ - 500: { headers: { [name: string]: unknown; }; content: { - '*/*': components['schemas']['ErrorResponse']; + '*/*': components['schemas']['QnACheckResp']; }; }; }; }; - toggleConfirmProblemSet: { + addChat_1: { parameters: { query?: never; header?: never; - path: { - problemSetId: number; - }; + path?: never; cookie?: never; }; - requestBody?: never; + requestBody: { + content: { + 'application/json': components['schemas']['ChatCreateRequest']; + }; + }; responses: { /** @description OK */ 200: { @@ -1304,24 +2724,36 @@ export interface operations { [name: string]: unknown; }; content: { - '*/*': { - /** @enum {string} */ - data: 'CONFIRMED' | 'NOT_CONFIRMED'; - }; + '*/*': components['schemas']['QnAResp']; }; }; - /** @description Internal Server Error */ - 500: { + }; + }; + getSocialLoginUrl: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['SocialLoginReq']; + }; + }; + responses: { + /** @description OK */ + 200: { headers: { [name: string]: unknown; }; content: { - '*/*': components['schemas']['ErrorResponse']; + '*/*': components['schemas']['SocialLoginUrlResp']; }; }; }; }; - updateProblemSubmit: { + registerSocial: { parameters: { query?: never; header?: never; @@ -1330,7 +2762,7 @@ export interface operations { }; requestBody: { content: { - 'application/json': components['schemas']['ProblemSubmitUpdateRequest']; + 'application/json': components['schemas']['StudentUpdateRequest']; }; }; responses: { @@ -1340,24 +2772,36 @@ export interface operations { [name: string]: unknown; }; content: { - '*/*': { - /** @enum {string} */ - data: 'CORRECT' | 'INCORRECT' | 'IN_PROGRESS' | 'RETRY_CORRECT' | 'NOT_STARTED'; - }; + '*/*': components['schemas']['StudentResp']; }; }; - /** @description Internal Server Error */ - 500: { + }; + }; + getPreSignedUrl: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['PreSignedReq']; + }; + }; + responses: { + /** @description OK */ + 200: { headers: { [name: string]: unknown; }; content: { - '*/*': components['schemas']['ErrorResponse']; + '*/*': components['schemas']['PreSignedResp']; }; }; }; }; - createProblemSubmit: { + create_2: { parameters: { query?: never; header?: never; @@ -1366,7 +2810,7 @@ export interface operations { }; requestBody: { content: { - 'application/json': components['schemas']['ProblemSubmitCreateRequest']; + 'application/json': components['schemas']['AdminCreateRequest']; }; }; responses: { @@ -1377,18 +2821,29 @@ export interface operations { }; content?: never; }; - /** @description Internal Server Error */ - 500: { + }; + }; + gets_1: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { headers: { [name: string]: unknown; }; content: { - '*/*': components['schemas']['ErrorResponse']; + '*/*': components['schemas']['ListRespTeacherResp']; }; }; }; }; - updateChildProblemSubmit: { + create_3: { parameters: { query?: never; header?: never; @@ -1397,7 +2852,7 @@ export interface operations { }; requestBody: { content: { - 'application/json': components['schemas']['ChildProblemSubmitUpdateRequest']; + 'application/json': components['schemas']['TeacherCreateRequest']; }; }; responses: { @@ -1407,32 +2862,23 @@ export interface operations { [name: string]: unknown; }; content: { - '*/*': { - data: components['schemas']['ChildProblemSubmitUpdateResponse']; - }; - }; - }; - /** @description Internal Server Error */ - 500: { - headers: { - [name: string]: unknown; - }; - content: { - '*/*': components['schemas']['ErrorResponse']; + '*/*': components['schemas']['TeacherResp']; }; }; }; }; - createProblemSubmit_1: { + assignStudentsToTeacher: { parameters: { query?: never; header?: never; - path?: never; + path: { + teacherId: number; + }; cookie?: never; }; requestBody: { content: { - 'application/json': components['schemas']['ChildProblemSubmitCreateRequest']; + 'application/json': components['schemas']['TeacherStudentAssignReq']; }; }; responses: { @@ -1441,20 +2887,37 @@ export interface operations { headers: { [name: string]: unknown; }; - content?: never; + content: { + '*/*': components['schemas']['TeacherResp']; + }; + }; + }; + }; + search: { + parameters: { + query?: { + year?: number; + month?: number; + studentId?: number; }; - /** @description Internal Server Error */ - 500: { + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { headers: { [name: string]: unknown; }; content: { - '*/*': components['schemas']['ErrorResponse']; + '*/*': components['schemas']['ListRespPublishMetaResp']; }; }; }; }; - updateChildProblemSubmitIncorrect: { + create_4: { parameters: { query?: never; header?: never; @@ -1463,7 +2926,7 @@ export interface operations { }; requestBody: { content: { - 'application/json': components['schemas']['ChildProblemSubmitUpdateIncorrectRequest']; + 'application/json': components['schemas']['PublishCreateRequest']; }; }; responses: { @@ -1472,20 +2935,39 @@ export interface operations { headers: { [name: string]: unknown; }; - content?: never; + content: { + '*/*': components['schemas']['PublishResp']; + }; }; - /** @description Internal Server Error */ - 500: { + }; + }; + search_1: { + parameters: { + query?: { + customId?: string; + title?: string; + concepts?: number[]; + page?: number; + size?: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { headers: { [name: string]: unknown; }; content: { - '*/*': components['schemas']['ErrorResponse']; + '*/*': components['schemas']['PageRespProblemMetaResp']; }; }; }; }; - postPublish: { + createProblem: { parameters: { query?: never; header?: never; @@ -1494,7 +2976,7 @@ export interface operations { }; requestBody: { content: { - 'application/json': components['schemas']['PublishPostRequest']; + 'application/json': components['schemas']['ProblemCreateRequest']; }; }; responses: { @@ -1504,23 +2986,37 @@ export interface operations { [name: string]: unknown; }; content: { - '*/*': { - data: components['schemas']['IdResponse']; - }; + '*/*': components['schemas']['ProblemInfoResp']; }; }; - /** @description Internal Server Error */ - 500: { + }; + }; + search_2: { + parameters: { + query?: { + setTitle?: string; + problemTitle?: string; + page?: number; + size?: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { headers: { [name: string]: unknown; }; content: { - '*/*': components['schemas']['ErrorResponse']; + '*/*': components['schemas']['PageRespProblemSetResp']; }; }; }; }; - createProblem: { + create_5: { parameters: { query?: never; header?: never; @@ -1529,7 +3025,7 @@ export interface operations { }; requestBody: { content: { - 'application/json': components['schemas']['ProblemPostRequest']; + 'application/json': components['schemas']['ProblemSetCreateRequest']; }; }; responses: { @@ -1539,32 +3035,50 @@ export interface operations { [name: string]: unknown; }; content: { - '*/*': { - data: components['schemas']['ProblemPostResponse']; - }; + '*/*': components['schemas']['ProblemSetResp']; }; }; - /** @description Internal Server Error */ - 500: { + }; + }; + search_3: { + parameters: { + query?: { + query?: string; + year?: number; + month?: number; + grade?: number; + page?: number; + size?: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { headers: { [name: string]: unknown; }; content: { - '*/*': components['schemas']['ErrorResponse']; + '*/*': components['schemas']['PageRespPracticeTestResp']; }; }; }; }; - createChildProblem: { + create_6: { parameters: { query?: never; header?: never; - path: { - problemId: number; - }; + path?: never; cookie?: never; }; - requestBody?: never; + requestBody: { + content: { + 'application/json': components['schemas']['PracticeTestCreateRequest']; + }; + }; responses: { /** @description OK */ 200: { @@ -1572,30 +3086,45 @@ export interface operations { [name: string]: unknown; }; content: { - '*/*': { - data: components['schemas']['IdResponse']; - }; + '*/*': components['schemas']['PracticeTestResp']; }; }; - /** @description Internal Server Error */ - 500: { + }; + }; + getsAll_1: { + parameters: { + query: { + studentId: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { headers: { [name: string]: unknown; }; content: { - '*/*': components['schemas']['ErrorResponse']; + '*/*': components['schemas']['ListRespNoticeResp']; }; }; }; }; - createProblemSet: { + create_7: { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - requestBody?: never; + requestBody: { + content: { + 'application/json': components['schemas']['NoticeCreateRequest']; + }; + }; responses: { /** @description OK */ 200: { @@ -1603,65 +3132,108 @@ export interface operations { [name: string]: unknown; }; content: { - '*/*': { - data: components['schemas']['IdResponse']; - }; + '*/*': components['schemas']['NoticeResp']; }; }; - /** @description Internal Server Error */ - 500: { + }; + }; + search_4: { + parameters: { + query?: { + query?: string; + page?: number; + size?: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { headers: { [name: string]: unknown; }; content: { - '*/*': components['schemas']['ErrorResponse']; + '*/*': components['schemas']['PageRespConceptResp']; }; }; }; }; - socialLogin: { + create_8: { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - requestBody?: { + requestBody: { content: { - 'application/json': components['schemas']['LoginRequest']; + 'application/json': components['schemas']['ConceptCreateRequest']; }; }; responses: { - /** @description 로그인 성공 */ + /** @description OK */ 200: { headers: { [name: string]: unknown; }; content: { - 'application/json': components['schemas']['LoginResponse']; + '*/*': components['schemas']['ConceptResp']; }; }; - /** @description 유효하지 않은 소셜 액세스 토큰 */ - 401: { + }; + }; + searchCategory: { + parameters: { + query?: { + query?: string; + page?: number; + size?: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { headers: { [name: string]: unknown; }; content: { - '*/*': components['schemas']['ErrorResponse']; + '*/*': components['schemas']['PageRespConceptCategoryResp']; }; }; - /** @description Internal Server Error */ - 500: { + }; + }; + createCategory: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['ConceptCategoryCreateRequest']; + }; + }; + responses: { + /** @description OK */ + 200: { headers: { [name: string]: unknown; }; content: { - '*/*': components['schemas']['ErrorResponse']; + '*/*': components['schemas']['ConceptCategoryResp']; }; }; }; }; - adminLogin: { + login_1: { parameters: { query?: never; header?: never; @@ -1670,50 +3242,71 @@ export interface operations { }; requestBody: { content: { - 'application/json': components['schemas']['AdminLoginRequest']; + 'application/json': components['schemas']['AdminLoginReq']; }; }; responses: { - /** @description 로그인 성공 */ + /** @description OK */ 200: { headers: { - /** @description 리프레시 토큰이 담긴 HTTP Only 쿠키 */ - 'Set-Cookie'?: string; [name: string]: unknown; }; content: { - 'application/json': { - data: components['schemas']['AccessTokenResponse']; - }; + '*/*': components['schemas']['AdminTokenResp']; }; }; - /** @description 인증 실패 (잘못된 이메일 또는 비밀번호) */ - 401: { + }; + }; + search_5: { + parameters: { + query: { + studentId: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { headers: { [name: string]: unknown; }; content: { - '*/*': components['schemas']['ErrorResponse']; + '*/*': components['schemas']['ListRespPublishMetaResp']; }; }; - /** @description Internal Server Error */ - 500: { + }; + }; + searchMonthly: { + parameters: { + query: { + studentId: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { headers: { [name: string]: unknown; }; content: { - '*/*': components['schemas']['ErrorResponse']; + '*/*': components['schemas']['ListRespPublishMetaResp']; }; }; }; }; - getPublishMonth: { + getPublishById: { parameters: { query?: never; header?: never; path: { - year: number; - month: number; + id: number; }; cookie?: never; }; @@ -1725,28 +3318,15 @@ export interface operations { [name: string]: unknown; }; content: { - '*/*': { - data: components['schemas']['PublishMonthGetResponse'][]; - }; - }; - }; - /** @description Internal Server Error */ - 500: { - headers: { - [name: string]: unknown; - }; - content: { - '*/*': components['schemas']['ErrorResponse']; + '*/*': components['schemas']['PublishResp']; }; }; }; }; - search: { + getWeeklyProgress: { parameters: { - query?: { - problemCustomId?: string; - title?: string; - conceptTagIds?: number[]; + query: { + studentId: number; }; header?: never; path?: never; @@ -1760,30 +3340,20 @@ export interface operations { [name: string]: unknown; }; content: { - '*/*': { - data: components['schemas']['ProblemSearchGetResponse'][]; - }; - }; - }; - /** @description Internal Server Error */ - 500: { - headers: { - [name: string]: unknown; - }; - content: { - '*/*': components['schemas']['ErrorResponse']; + '*/*': components['schemas']['PublishStudentProgressResp']; }; }; }; }; - search_1: { + getProblemById: { parameters: { - query?: { - problemSetTitle?: string; - problemTitle?: string; + query: { + studentId: number; }; header?: never; - path?: never; + path: { + id: number; + }; cookie?: never; }; requestBody?: never; @@ -1794,30 +3364,20 @@ export interface operations { [name: string]: unknown; }; content: { - '*/*': { - data: components['schemas']['ProblemSetSearchGetResponse'][]; - }; - }; - }; - /** @description Internal Server Error */ - 500: { - headers: { - [name: string]: unknown; - }; - content: { - '*/*': components['schemas']['ErrorResponse']; + '*/*': components['schemas']['ProblemWithStudyInfoResp']; }; }; }; }; - confirmSearch: { + getChildProblemById: { parameters: { - query?: { - problemSetTitle?: string; - problemTitle?: string; + query: { + studentId: number; }; header?: never; - path?: never; + path: { + id: number; + }; cookie?: never; }; requestBody?: never; @@ -1828,23 +3388,12 @@ export interface operations { [name: string]: unknown; }; content: { - '*/*': { - data: components['schemas']['ProblemSetSearchGetResponse'][]; - }; - }; - }; - /** @description Internal Server Error */ - 500: { - headers: { - [name: string]: unknown; - }; - content: { - '*/*': components['schemas']['ErrorResponse']; + '*/*': components['schemas']['ChildProblemWithStudyInfoResp']; }; }; }; }; - getPracticeTestTags: { + getMyStudents: { parameters: { query?: never; header?: never; @@ -1859,23 +3408,12 @@ export interface operations { [name: string]: unknown; }; content: { - '*/*': { - data: components['schemas']['PracticeTestTagResponse'][]; - }; - }; - }; - /** @description Internal Server Error */ - 500: { - headers: { - [name: string]: unknown; - }; - content: { - '*/*': components['schemas']['ErrorResponse']; + '*/*': components['schemas']['ListRespStudentResp']; }; }; }; }; - getMyInfo: { + gets_2: { parameters: { query?: never; header?: never; @@ -1890,28 +3428,17 @@ export interface operations { [name: string]: unknown; }; content: { - '*/*': { - data: components['schemas']['MemberGetResponse']; - }; - }; - }; - /** @description Internal Server Error */ - 500: { - headers: { - [name: string]: unknown; - }; - content: { - '*/*': components['schemas']['ErrorResponse']; + '*/*': components['schemas']['PageRespQnAMetaResp']; }; }; }; }; - getImageUrl: { + getById_1: { parameters: { query?: never; header?: never; path: { - fileName: string; + qnaId: number; }; cookie?: never; }; @@ -1923,39 +3450,18 @@ export interface operations { [name: string]: unknown; }; content: { - '*/*': { - data: string; - }; - }; - }; - /** @description Internal Server Error */ - 500: { - headers: { - [name: string]: unknown; - }; - content: { - '*/*': components['schemas']['ErrorResponse']; + '*/*': components['schemas']['QnAResp']; }; }; }; }; - getProblemImagePresignedUrl: { + getsAvailable: { parameters: { query: { - 'image-type': - | 'MAIN_PROBLEM' - | 'MAIN_ANALYSIS' - | 'MAIN_HANDWRITING_EXPLANATION' - | 'READING_TIP' - | 'SENIOR_TIP' - | 'MAIN_PRESCRIPTION' - | 'CHILD_PROBLEM' - | 'CHILD_PRESCRIPTION'; + studentId: number; }; header?: never; - path: { - problemId: number; - }; + path?: never; cookie?: never; }; requestBody?: never; @@ -1966,23 +3472,12 @@ export interface operations { [name: string]: unknown; }; content: { - '*/*': { - data: components['schemas']['PresignedUrlResponse']; - }; - }; - }; - /** @description Internal Server Error */ - 500: { - headers: { - [name: string]: unknown; - }; - content: { - '*/*': components['schemas']['ErrorResponse']; + '*/*': components['schemas']['ListRespNoticeResp']; }; }; }; }; - getConceptTags: { + getTeacherMe: { parameters: { query?: never; header?: never; @@ -1997,29 +3492,16 @@ export interface operations { [name: string]: unknown; }; content: { - '*/*': { - data: components['schemas']['ConceptTagResponse'][]; - }; - }; - }; - /** @description Internal Server Error */ - 500: { - headers: { - [name: string]: unknown; - }; - content: { - '*/*': components['schemas']['ErrorResponse']; + '*/*': components['schemas']['TeacherResp']; }; }; }; }; - getProblemsInPublish: { + search_6: { parameters: { query?: never; header?: never; - path: { - publishId: number; - }; + path?: never; cookie?: never; }; requestBody?: never; @@ -2030,30 +3512,16 @@ export interface operations { [name: string]: unknown; }; content: { - '*/*': { - data: components['schemas']['PublishClientGetResponse']; - }; - }; - }; - /** @description Internal Server Error */ - 500: { - headers: { - [name: string]: unknown; - }; - content: { - '*/*': components['schemas']['ErrorResponse']; + '*/*': components['schemas']['ListRespPublishMetaResp']; }; }; }; }; - getProblem_1: { + searchMonthly_1: { parameters: { query?: never; header?: never; - path: { - publishId: number; - problemId: number; - }; + path?: never; cookie?: never; }; requestBody?: never; @@ -2064,30 +3532,17 @@ export interface operations { [name: string]: unknown; }; content: { - '*/*': { - data: components['schemas']['ProblemClientGetResponse']; - }; - }; - }; - /** @description Internal Server Error */ - 500: { - headers: { - [name: string]: unknown; - }; - content: { - '*/*': components['schemas']['ErrorResponse']; + '*/*': components['schemas']['ListRespPublishMetaResp']; }; }; }; }; - getChildProblem: { + getPublishById_1: { parameters: { query?: never; header?: never; path: { - publishId: number; - problemId: number; - childProblemId: number; + id: number; }; cookie?: never; }; @@ -2099,30 +3554,16 @@ export interface operations { [name: string]: unknown; }; content: { - '*/*': { - data: components['schemas']['ChildProblemClientGetResponse']; - }; - }; - }; - /** @description Internal Server Error */ - 500: { - headers: { - [name: string]: unknown; - }; - content: { - '*/*': components['schemas']['ErrorResponse']; + '*/*': components['schemas']['PublishResp']; }; }; }; }; - getProblemThumbnail: { + getWeeklyProgress_1: { parameters: { query?: never; header?: never; - path: { - publishId: number; - problemId: number; - }; + path?: never; cookie?: never; }; requestBody?: never; @@ -2133,29 +3574,17 @@ export interface operations { [name: string]: unknown; }; content: { - '*/*': { - data: components['schemas']['ProblemClientThumbnailResponse']; - }; - }; - }; - /** @description Internal Server Error */ - 500: { - headers: { - [name: string]: unknown; - }; - content: { - '*/*': components['schemas']['ErrorResponse']; + '*/*': components['schemas']['PublishStudentProgressResp']; }; }; }; }; - getChildProblems: { + getProblemById_1: { parameters: { query?: never; header?: never; path: { - publishId: number; - problemId: number; + id: number; }; cookie?: never; }; @@ -2167,29 +3596,17 @@ export interface operations { [name: string]: unknown; }; content: { - '*/*': { - data: components['schemas']['ChildProblemsClientGetResponse']; - }; - }; - }; - /** @description Internal Server Error */ - 500: { - headers: { - [name: string]: unknown; - }; - content: { - '*/*': components['schemas']['ErrorResponse']; + '*/*': components['schemas']['ProblemWithStudyInfoResp']; }; }; }; }; - getAllProblem: { + getChildProblemById_1: { parameters: { query?: never; header?: never; path: { - year: number; - month: number; + id: number; }; cookie?: never; }; @@ -2201,23 +3618,32 @@ export interface operations { [name: string]: unknown; }; content: { - '*/*': { - data: components['schemas']['AllProblemGetResponse'][]; - }; + '*/*': components['schemas']['ChildProblemWithStudyInfoResp']; }; }; - /** @description Internal Server Error */ - 500: { + }; + }; + getsAvailable_1: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { headers: { [name: string]: unknown; }; content: { - '*/*': components['schemas']['ErrorResponse']; + '*/*': components['schemas']['ListRespNoticeResp']; }; }; }; }; - getHomeFeed: { + countAvailable: { parameters: { query?: never; header?: never; @@ -2232,27 +3658,37 @@ export interface operations { [name: string]: unknown; }; content: { - '*/*': { - data: components['schemas']['HomeFeedResponse']; - }; + '*/*': components['schemas']['NoticeUnreadCountResp']; }; }; - /** @description Internal Server Error */ - 500: { + }; + }; + refresh: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { headers: { [name: string]: unknown; }; content: { - '*/*': components['schemas']['ErrorResponse']; + '*/*': components['schemas']['JwtResp']; }; }; }; }; - getCommentary: { + search_7: { parameters: { query?: { - publishId?: number; - problemId?: number; + query?: string; + page?: number; + size?: number; }; header?: never; path?: never; @@ -2266,79 +3702,101 @@ export interface operations { [name: string]: unknown; }; content: { - '*/*': { - data: components['schemas']['CommentaryGetResponse']; - }; + '*/*': components['schemas']['PageRespStudentResp']; }; }; - /** @description Internal Server Error */ - 500: { + }; + }; + getById_2: { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { headers: { [name: string]: unknown; }; content: { - '*/*': components['schemas']['ErrorResponse']; + '*/*': components['schemas']['PublishResp']; }; }; }; }; - reissueToken: { + delete_4: { parameters: { query?: never; header?: never; - path?: never; + path: { + id: number; + }; cookie?: never; }; requestBody?: never; responses: { - /** @description 토큰 재발급 성공 */ + /** @description OK */ 200: { headers: { - /** @description 새로운 리프레시 토큰이 담긴 HTTP Only 쿠키 */ - 'Set-Cookie'?: string; [name: string]: unknown; }; - content: { - 'application/json': { - data: components['schemas']['AccessTokenResponse']; - }; - }; + content?: never; }; - /** @description 유효하지 않은 리프레시 토큰 */ - 401: { - headers: { - [name: string]: unknown; - }; - content: { - '*/*': components['schemas']['ErrorResponse']; - }; + }; + }; + getsAvailable_2: { + parameters: { + query: { + studentId: number; }; - /** @description 리프레시 토큰 쿠키 없음 */ - 404: { + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { headers: { [name: string]: unknown; }; content: { - '*/*': components['schemas']['ErrorResponse']; + '*/*': components['schemas']['ListRespNoticeResp']; }; }; - /** @description Internal Server Error */ - 500: { + }; + }; + refresh_1: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { headers: { [name: string]: unknown; }; content: { - '*/*': components['schemas']['ErrorResponse']; + '*/*': components['schemas']['AdminTokenResp']; }; }; }; }; - deleteProblemSet: { + delete_5: { parameters: { query?: never; header?: never; path: { - publishId: number; + id: number; }; cookie?: never; }; @@ -2351,24 +3809,38 @@ export interface operations { }; content?: never; }; - /** @description Internal Server Error */ - 500: { + }; + }; + removeStudentFromTeacher: { + parameters: { + query?: never; + header?: never; + path: { + teacherId: number; + studentId: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { headers: { [name: string]: unknown; }; content: { - '*/*': components['schemas']['ErrorResponse']; + '*/*': components['schemas']['TeacherResp']; }; }; }; }; - deleteChildProblem: { + deleteItem: { parameters: { query?: never; header?: never; path: { + id: number; problemId: number; - childProblemId: number; }; cookie?: never; }; @@ -2376,18 +3848,11 @@ export interface operations { responses: { /** @description OK */ 200: { - headers: { - [name: string]: unknown; - }; - content?: never; - }; - /** @description Internal Server Error */ - 500: { headers: { [name: string]: unknown; }; content: { - '*/*': components['schemas']['ErrorResponse']; + '*/*': components['schemas']['ProblemSetResp']; }; }; }; From ab44970a9b9372c33326450b9723707b432414d2 Mon Sep 17 00:00:00 2001 From: chaeyoung103 Date: Mon, 14 Jul 2025 10:17:03 +0900 Subject: [PATCH 3/9] =?UTF-8?q?fix(service):=20=EC=9E=91=EC=97=85=EC=A7=84?= =?UTF-8?q?=ED=96=89=EC=9D=84=20=EC=9C=84=ED=95=9C=20authmiddleware=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EB=AC=B4=EB=A0=A5=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/service/src/apis/authMiddleware.ts | 106 ++++++++++++------------ apps/service/src/app/providers.tsx | 4 +- 2 files changed, 55 insertions(+), 55 deletions(-) diff --git a/apps/service/src/apis/authMiddleware.ts b/apps/service/src/apis/authMiddleware.ts index 73bbcf61..a2578aee 100644 --- a/apps/service/src/apis/authMiddleware.ts +++ b/apps/service/src/apis/authMiddleware.ts @@ -3,69 +3,69 @@ import { Middleware } from 'openapi-fetch'; import { getAccessToken, setAccessToken } from '@utils'; -const UNPROTECTED_ROUTES = ['/api/v1/auth/admin/login', '/api/student/auth/social/login']; +// const UNPROTECTED_ROUTES = ['/api/v1/auth/admin/login', '/api/student/auth/social/login']; -const reissueToken = async () => { - try { - const response = await fetch( - `${process.env.NEXT_PUBLIC_API_BASE_URL}/api/common/auth/refresh`, - { - method: 'GET', - credentials: 'include', - } - ); +// const reissueToken = async () => { +// try { +// const response = await fetch( +// `${process.env.NEXT_PUBLIC_API_BASE_URL}/api/common/auth/refresh`, +// { +// method: 'GET', +// credentials: 'include', +// } +// ); - if (!response.ok) throw new Error('Token reissue failed'); +// if (!response.ok) throw new Error('Token reissue failed'); - const data = await response.json(); - const accessToken = data.data.accessToken; - setAccessToken(accessToken); - return accessToken; - } catch (error) { - console.error('Reissue failed:', error); - localStorage.removeItem('accessToken'); - window.location.href = '/login'; - return null; - } -}; +// const data = await response.json(); +// const accessToken = data.data.accessToken; +// setAccessToken(accessToken); +// return accessToken; +// } catch (error) { +// console.error('Reissue failed:', error); +// localStorage.removeItem('accessToken'); +// window.location.href = '/login'; +// return null; +// } +// }; -const authMiddleware: Middleware = { - async onRequest({ schemaPath, request }: { schemaPath: string; request: Request }) { - if (UNPROTECTED_ROUTES.some((pathname) => schemaPath.startsWith(pathname))) { - return undefined; - } +// const authMiddleware: Middleware = { +// async onRequest({ schemaPath, request }: { schemaPath: string; request: Request }) { +// if (UNPROTECTED_ROUTES.some((pathname) => schemaPath.startsWith(pathname))) { +// return undefined; +// } - let accessToken = getAccessToken(); +// let accessToken = getAccessToken(); - if (!accessToken) { - accessToken = await reissueToken(); +// if (!accessToken) { +// accessToken = await reissueToken(); - if (!accessToken) { - console.error('Access token reissue failed. Logging out...'); - return request; - } - } +// if (!accessToken) { +// console.error('Access token reissue failed. Logging out...'); +// return request; +// } +// } - request.headers.set('Authorization', `Bearer ${accessToken}`); - return request; - }, +// request.headers.set('Authorization', `Bearer ${accessToken}`); +// return request; +// }, - async onResponse({ request, response }) { - if (response.status === 401) { - console.warn('Access token expired. Attempting reissue...'); +// async onResponse({ request, response }) { +// if (response.status === 401) { +// console.warn('Access token expired. Attempting reissue...'); - const newAccessToken = await reissueToken(); +// const newAccessToken = await reissueToken(); - if (!newAccessToken) { - console.error('Reissue failed. Logging out...'); - return response; - } +// if (!newAccessToken) { +// console.error('Reissue failed. Logging out...'); +// return response; +// } - request.headers.set('Authorization', `Bearer ${newAccessToken}`); - return fetch(request); - } - return response; - }, -}; +// request.headers.set('Authorization', `Bearer ${newAccessToken}`); +// return fetch(request); +// } +// return response; +// }, +// }; -export default authMiddleware; +// export default authMiddleware; diff --git a/apps/service/src/app/providers.tsx b/apps/service/src/app/providers.tsx index 828c16e8..dfab530b 100644 --- a/apps/service/src/app/providers.tsx +++ b/apps/service/src/app/providers.tsx @@ -4,11 +4,11 @@ import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; import type * as React from 'react'; import { client } from '@apis'; -import authMiddleware from '@/apis/authMiddleware'; +// import authMiddleware from '@/apis/authMiddleware'; export default function Providers({ children }: { children: React.ReactNode }) { const queryClient = new QueryClient(); - client.use(authMiddleware); + // client.use(authMiddleware); return ( From 87bb014c4d986f8a364896dac0e4be290ffada81 Mon Sep 17 00:00:00 2001 From: chaeyoung103 Date: Tue, 15 Jul 2025 10:58:54 +0900 Subject: [PATCH 4/9] =?UTF-8?q?chore(service):=20clsx=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/service/package.json | 1 + pnpm-lock.yaml | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/service/package.json b/apps/service/package.json index 06aaa09c..2cf1f7a2 100644 --- a/apps/service/package.json +++ b/apps/service/package.json @@ -15,6 +15,7 @@ "@tanstack/react-query": "^5.66.0", "@tanstack/react-query-devtools": "^5.66.0", "dayjs": "^1.11.13", + "clsx": "^2.1.1", "next": "15.1.4", "openapi-fetch": "^0.13.4", "openapi-react-query": "^0.3.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3f08613b..59a92bec 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -183,6 +183,9 @@ importers: '@tanstack/react-query-devtools': specifier: ^5.66.0 version: 5.81.5(@tanstack/react-query@5.81.5(react@19.1.0))(react@19.1.0) + clsx: + specifier: ^2.1.1 + version: 2.1.1 dayjs: specifier: ^1.11.13 version: 1.11.13 @@ -8058,7 +8061,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.30.1(jiti@2.4.2)): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.30.1(jiti@2.4.2)))(eslint@9.30.1(jiti@2.4.2)): dependencies: debug: 3.2.7 optionalDependencies: @@ -8080,7 +8083,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.30.1(jiti@2.4.2) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.30.1(jiti@2.4.2)) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.30.1(jiti@2.4.2)))(eslint@9.30.1(jiti@2.4.2)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 From d5e0fffdb6793497f7f9075f9bea9d1833e10b71 Mon Sep 17 00:00:00 2001 From: chaeyoung103 Date: Tue, 15 Jul 2025 11:13:40 +0900 Subject: [PATCH 5/9] =?UTF-8?q?chore(service):=20=EC=9E=84=EC=8B=9C=20hydr?= =?UTF-8?q?ation=20error=20=EC=A0=9C=EA=B1=B0=EB=A5=BC=20=EC=9C=84?= =?UTF-8?q?=ED=95=9C=20=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/service/src/app/layout.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/service/src/app/layout.tsx b/apps/service/src/app/layout.tsx index 10f0b628..6238ae9d 100644 --- a/apps/service/src/app/layout.tsx +++ b/apps/service/src/app/layout.tsx @@ -50,7 +50,7 @@ export default function RootLayout({ modal: React.ReactNode; }>) { return ( - + From 6f72f153d335e17d38e4c19590ad9be78e09225b Mon Sep 17 00:00:00 2001 From: chaeyoung103 Date: Tue, 15 Jul 2025 11:14:55 +0900 Subject: [PATCH 6/9] =?UTF-8?q?fix(service):=20=EB=B0=94=EB=80=90=20api?= =?UTF-8?q?=EB=A1=9C=20=EC=9D=B8=ED=95=9C=20=EC=B9=B4=EC=B9=B4=EC=98=A4=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EB=A1=9C=EC=A7=81=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apis/controller/auth/postKakaoLogin.ts | 2 +- .../src/app/api/auth/callback/kakao/page.tsx | 22 +- apps/service/src/app/login/page.tsx | 3 - apps/service/src/types/api/schema.d.ts | 266 ++++++++++++++---- 4 files changed, 228 insertions(+), 65 deletions(-) diff --git a/apps/service/src/apis/controller/auth/postKakaoLogin.ts b/apps/service/src/apis/controller/auth/postKakaoLogin.ts index e4803285..bc4be7fb 100644 --- a/apps/service/src/apis/controller/auth/postKakaoLogin.ts +++ b/apps/service/src/apis/controller/auth/postKakaoLogin.ts @@ -3,7 +3,7 @@ import { client } from '@/apis/client'; const postKakaoLogin = async () => { - const response = await client.POST('/api/student/auth/social/login', { + const response = await client.POST('/api/student/auth/login/social', { body: { provider: 'KAKAO', redirectUri: process.env.NEXT_PUBLIC_REDIRECT_URI ?? '', diff --git a/apps/service/src/app/api/auth/callback/kakao/page.tsx b/apps/service/src/app/api/auth/callback/kakao/page.tsx index 23872e19..4f772dac 100644 --- a/apps/service/src/app/api/auth/callback/kakao/page.tsx +++ b/apps/service/src/app/api/auth/callback/kakao/page.tsx @@ -1,13 +1,31 @@ 'use client'; -import { useSearchParams } from 'next/navigation'; +import { useRouter, useSearchParams } from 'next/navigation'; import { useEffect } from 'react'; +import { setAccessToken, setRefreshToken } from '@utils'; + const Page = () => { const searchParams = useSearchParams(); + const router = useRouter(); + const { success, isFirstLogin, accessToken, refreshToken } = Object.fromEntries( + searchParams.entries() + ); useEffect(() => { - console.log(searchParams); + if (!success || !accessToken) { + router.replace('/login'); + return; + } + + setAccessToken(accessToken); + setRefreshToken(refreshToken); + + if (isFirstLogin) { + router.replace('/onboarding'); + } else { + router.replace('/'); + } }, [searchParams]); return <>; diff --git a/apps/service/src/app/login/page.tsx b/apps/service/src/app/login/page.tsx index 768acfb8..1472d028 100644 --- a/apps/service/src/app/login/page.tsx +++ b/apps/service/src/app/login/page.tsx @@ -9,9 +9,6 @@ import { KakaoButton } from '@/components/login'; const Page = () => { const router = useRouter(); - const kakaoLoginUrl = `https://kauth.kakao.com/oauth/authorize?client_id=${ - process.env.NEXT_PUBLIC_REST_API_KEY - }&redirect_uri=${process.env.NEXT_PUBLIC_REDIRECT_URI}&response_type=code`; const handleLoginClick = async () => { trackEvent('kakao_login_click'); diff --git a/apps/service/src/types/api/schema.d.ts b/apps/service/src/types/api/schema.d.ts index 123796c7..03418032 100644 --- a/apps/service/src/types/api/schema.d.ts +++ b/apps/service/src/types/api/schema.d.ts @@ -306,6 +306,23 @@ export interface paths { patch?: never; trace?: never; }; + '/api/teacher/auth/refresh': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** 토큰 갱신 */ + post: operations['refresh']; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; '/api/teacher/auth/login/local': { parameters: { query?: never; @@ -409,7 +426,7 @@ export interface paths { patch?: never; trace?: never; }; - '/api/student/auth/social/login': { + '/api/student/auth/register/social': { parameters: { query?: never; header?: never; @@ -418,15 +435,15 @@ export interface paths { }; get?: never; put?: never; - /** 소셜 로그인 URL 요청 [네이버만 완료] */ - post: operations['getSocialLoginUrl']; + /** 소셜 로그인 이후, 정보 등록 */ + post: operations['registerSocial']; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - '/api/student/auth/register/social': { + '/api/student/auth/refresh': { parameters: { query?: never; header?: never; @@ -435,8 +452,25 @@ export interface paths { }; get?: never; put?: never; - /** 소셜 로그인 이후, 정보 등록 */ - post: operations['registerSocial']; + /** 토큰 갱신 */ + post: operations['refresh_1']; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/student/auth/login/social': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** 소셜 로그인 URL 요청 [네이버만 완료] */ + post: operations['getSocialLoginUrl']; delete?: never; options?: never; head?: never; @@ -638,6 +672,23 @@ export interface paths { patch?: never; trace?: never; }; + '/api/admin/auth/refresh': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** 토큰 갱신 */ + post: operations['refresh_2']; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; '/api/admin/auth/login/local': { parameters: { query?: never; @@ -655,6 +706,23 @@ export interface paths { patch?: never; trace?: never; }; + '/your-redirect-url': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** 소셜 로그인 콜백 example */ + get: operations['oauthRedirectExample']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; '/api/teacher/study/publish/weekly': { parameters: { query?: never; @@ -986,7 +1054,7 @@ export interface paths { cookie?: never; }; /** 토큰 갱신 */ - get: operations['refresh']; + get: operations['refresh_3']; put?: never; post?: never; delete?: never; @@ -1047,23 +1115,6 @@ export interface paths { patch?: never; trace?: never; }; - '/api/admin/auth/refresh': { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** 토큰 갱신 */ - get: operations['refresh_1']; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; '/api/common/upload-file/{id}': { parameters: { query?: never; @@ -1491,12 +1542,12 @@ export interface components { /** Format: int64 */ studentId: number; }; - TeacherLoginReq: { - email: string; - password: string; + RefreshReq: { + refreshToken: string; }; JwtResp: { accessToken: string; + refreshToken?: string; }; TeacherTokenResp: { /** Format: int64 */ @@ -1506,6 +1557,10 @@ export interface components { students: components['schemas']['StudentResp'][]; token?: components['schemas']['JwtResp']; }; + TeacherLoginReq: { + email: string; + password: string; + }; PointingFeedbackRequest: { /** Format: int64 */ pointingId: number; @@ -1581,6 +1636,15 @@ export interface components { id: number; isExist: boolean; }; + StudentTokenResp: { + /** Format: int64 */ + id: number; + name: string; + /** Format: int32 */ + grade: number; + isFirstLogin: boolean; + token: components['schemas']['JwtResp']; + }; SocialLoginReq: { /** @enum {string} */ provider: 'KAKAO' | 'GOOGLE'; @@ -1757,16 +1821,16 @@ export interface components { ConceptCategoryCreateRequest: { name: string; }; - AdminLoginReq: { - email: string; - password: string; - }; AdminTokenResp: { /** Format: int64 */ id: number; email: string; token: components['schemas']['JwtResp']; }; + AdminLoginReq: { + email: string; + password: string; + }; ListRespPublishMetaResp: { /** Format: int32 */ total: number; @@ -1797,7 +1861,10 @@ export interface components { }; NoticeUnreadCountResp: { /** Format: int64 */ - count?: number; + totalCount?: number; + /** Format: int64 */ + unreadCount?: number; + latestNotice?: components['schemas']['NoticeResp']; }; ListRespTeacherResp: { /** Format: int32 */ @@ -2567,6 +2634,30 @@ export interface operations { }; }; }; + refresh: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['RefreshReq']; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + '*/*': components['schemas']['TeacherTokenResp']; + }; + }; + }; + }; login: { parameters: { query?: never; @@ -2729,7 +2820,7 @@ export interface operations { }; }; }; - getSocialLoginUrl: { + registerSocial: { parameters: { query?: never; header?: never; @@ -2738,7 +2829,7 @@ export interface operations { }; requestBody: { content: { - 'application/json': components['schemas']['SocialLoginReq']; + 'application/json': components['schemas']['StudentUpdateRequest']; }; }; responses: { @@ -2748,12 +2839,12 @@ export interface operations { [name: string]: unknown; }; content: { - '*/*': components['schemas']['SocialLoginUrlResp']; + '*/*': components['schemas']['StudentResp']; }; }; }; }; - registerSocial: { + refresh_1: { parameters: { query?: never; header?: never; @@ -2762,7 +2853,7 @@ export interface operations { }; requestBody: { content: { - 'application/json': components['schemas']['StudentUpdateRequest']; + 'application/json': components['schemas']['RefreshReq']; }; }; responses: { @@ -2772,7 +2863,31 @@ export interface operations { [name: string]: unknown; }; content: { - '*/*': components['schemas']['StudentResp']; + '*/*': components['schemas']['StudentTokenResp']; + }; + }; + }; + }; + getSocialLoginUrl: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['SocialLoginReq']; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + '*/*': components['schemas']['SocialLoginUrlResp']; }; }; }; @@ -3233,6 +3348,30 @@ export interface operations { }; }; }; + refresh_2: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['RefreshReq']; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + '*/*': components['schemas']['AdminTokenResp']; + }; + }; + }; + }; login_1: { parameters: { query?: never; @@ -3257,6 +3396,35 @@ export interface operations { }; }; }; + oauthRedirectExample: { + parameters: { + query: { + /** @description 성공 여부 */ + isSuccess: boolean; + /** @description 첫 로그인 여부 */ + isFirstLogin: boolean; + /** @description 응답 메시지 */ + message: string; + /** @description accessToken */ + accessToken: string; + /** @description refreshToken */ + refreshToken: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; search_5: { parameters: { query: { @@ -3663,7 +3831,7 @@ export interface operations { }; }; }; - refresh: { + refresh_3: { parameters: { query?: never; header?: never; @@ -3771,26 +3939,6 @@ export interface operations { }; }; }; - refresh_1: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description OK */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - '*/*': components['schemas']['AdminTokenResp']; - }; - }; - }; - }; delete_5: { parameters: { query?: never; From 3b059030054c7cdafe37f6d2ffc6963a00ce4dea Mon Sep 17 00:00:00 2001 From: chaeyoung103 Date: Tue, 15 Jul 2025 11:16:31 +0900 Subject: [PATCH 7/9] =?UTF-8?q?feat(service):=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=EC=9E=90=20=EC=A0=95=EB=B3=B4=EC=9E=85=EB=A0=A5=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EB=B0=8F=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/src/apis/controller/auth/index.ts | 4 +- .../src/apis/controller/auth/postUserInfo.ts | 17 +++++ apps/service/src/app/onboarding/page.tsx | 75 +++++++++++++++++++ .../src/components/common/Inputs/Input.tsx | 2 +- .../common/Inputs/UserInfoInput.tsx | 28 +++++++ .../components/onboarding/UserInfoForm.tsx | 36 +++++++++ apps/service/src/constants/validationRules.ts | 25 +++++++ apps/service/src/types/index.ts | 1 + apps/service/src/types/onboarding.ts | 4 + 9 files changed, 190 insertions(+), 2 deletions(-) create mode 100644 apps/service/src/apis/controller/auth/postUserInfo.ts create mode 100644 apps/service/src/app/onboarding/page.tsx create mode 100644 apps/service/src/components/common/Inputs/UserInfoInput.tsx create mode 100644 apps/service/src/components/onboarding/UserInfoForm.tsx create mode 100644 apps/service/src/constants/validationRules.ts create mode 100644 apps/service/src/types/onboarding.ts diff --git a/apps/service/src/apis/controller/auth/index.ts b/apps/service/src/apis/controller/auth/index.ts index 27f91b57..216426b3 100644 --- a/apps/service/src/apis/controller/auth/index.ts +++ b/apps/service/src/apis/controller/auth/index.ts @@ -1,3 +1,5 @@ import postKakaoLogin from './postKakaoLogin'; +import postUserInfo from './postUserInfo'; +import postRefreshToken from './postRefreshToken'; -export { postKakaoLogin }; +export { postKakaoLogin, postUserInfo, postRefreshToken }; diff --git a/apps/service/src/apis/controller/auth/postUserInfo.ts b/apps/service/src/apis/controller/auth/postUserInfo.ts new file mode 100644 index 00000000..029832ad --- /dev/null +++ b/apps/service/src/apis/controller/auth/postUserInfo.ts @@ -0,0 +1,17 @@ +import { client } from '@apis'; + +const postUserInfo = async (name: string, grade: number) => { + try { + const response = await client.POST('/api/student/auth/register/social', { + body: { + name: name, + grade: grade, + }, + }); + return { isSuccess: true, data: response.data }; + } catch (error) { + return { isSuccess: false, error: error }; + } +}; + +export default postUserInfo; diff --git a/apps/service/src/app/onboarding/page.tsx b/apps/service/src/app/onboarding/page.tsx new file mode 100644 index 00000000..bae6d5d1 --- /dev/null +++ b/apps/service/src/app/onboarding/page.tsx @@ -0,0 +1,75 @@ +'use client'; + +import { useForm, FormProvider } from 'react-hook-form'; +import { useRouter } from 'next/navigation'; +import { useEffect, useState } from 'react'; + +import { Button, Header } from '@components'; +import { postUserInfo } from '@/apis/controller/auth'; +import { setName } from '@utils'; +import UserInfoForm from '@/components/onboarding/UserInfoForm'; + +const Page = () => { + type FormValues = { name: string; grade: string }; + const router = useRouter(); + const [isFormFilled, setIsFormFilled] = useState(false); + + const methods = useForm({ + mode: 'onChange', + }); + const { register, formState, getFieldState } = methods; + const handleSubmitForm = async () => { + const result = await postUserInfo( + methods.getValues('name'), + Number(methods.getValues('grade')) + ); + if (result.isSuccess) { + if (result.data) { + setName(result.data.name); + } + router.push('/'); + } else { + console.error('회원 정보 입력 실패:', result.error); + router.push('/login'); + } + }; + + useEffect(() => { + const isNameValid = getFieldState('name', formState); + const isGradeValid = getFieldState('grade', formState); + + if ( + !isNameValid.invalid && + !isGradeValid.invalid && + isNameValid.isDirty && + isGradeValid.isDirty + ) { + setIsFormFilled(true); + } else { + setIsFormFilled(false); + } + }, [formState, methods]); + + return ( + <> +
+
+ +
+

+ 포인터 + 에 오신 걸
+ 환영합니다! +

+ +
+ +
+
+ + ); +}; + +export default Page; diff --git a/apps/service/src/components/common/Inputs/Input.tsx b/apps/service/src/components/common/Inputs/Input.tsx index 6ddffc1c..5668e4ab 100644 --- a/apps/service/src/components/common/Inputs/Input.tsx +++ b/apps/service/src/components/common/Inputs/Input.tsx @@ -5,7 +5,7 @@ const Input = forwardRef return ( ); diff --git a/apps/service/src/components/common/Inputs/UserInfoInput.tsx b/apps/service/src/components/common/Inputs/UserInfoInput.tsx new file mode 100644 index 00000000..6590ac37 --- /dev/null +++ b/apps/service/src/components/common/Inputs/UserInfoInput.tsx @@ -0,0 +1,28 @@ +import { forwardRef } from 'react'; +import clsx from 'clsx'; + +import { Input } from '@components'; + +interface UserInfoInputProps { + type?: 'name' | 'grade'; + error?: boolean; +} + +const UserInfoInput = forwardRef( + ({ type, error = false, ...props }, ref) => { + return ( +
+ {type === 'name' && } + {type === 'grade' && } +

+ {type === 'name' && '2자 이상, 5자 이하로 입력해주세요'} + {type === 'grade' && '1~3 사이 숫자만 입력해주세요'} +

+
+ ); + } +); + +UserInfoInput.displayName = 'UserInfoInput'; + +export default UserInfoInput; diff --git a/apps/service/src/components/onboarding/UserInfoForm.tsx b/apps/service/src/components/onboarding/UserInfoForm.tsx new file mode 100644 index 00000000..21f4c0a1 --- /dev/null +++ b/apps/service/src/components/onboarding/UserInfoForm.tsx @@ -0,0 +1,36 @@ +import { UseFormStateReturn, UseFormRegister } from 'react-hook-form'; + +import { UserInfoFormData } from '@/types'; +import { GRADE_VALIDATION_RULES, NAME_VALIDATION_RULES } from '@/constants/validationRules'; + +import UserInfoInput from '../common/Inputs/UserInfoInput'; + +type UserInfoFormProps = { + formState: UseFormStateReturn; + register: UseFormRegister; +}; + +const UserInfoForm = ({ formState, register }: UserInfoFormProps) => { + return ( + <> +
+

이름이 무엇인가요?

+ +
+
+

몇학년인가요?

+ +
+ + ); +}; + +export default UserInfoForm; diff --git a/apps/service/src/constants/validationRules.ts b/apps/service/src/constants/validationRules.ts new file mode 100644 index 00000000..2abdb0c4 --- /dev/null +++ b/apps/service/src/constants/validationRules.ts @@ -0,0 +1,25 @@ +export const NAME_VALIDATION_RULES = { + required: { + value: true, + message: '', + }, + minLength: { + value: 2, + message: '', + }, + maxLength: { + value: 5, + message: '', + }, +}; + +export const GRADE_VALIDATION_RULES = { + required: { + value: true, + message: '', + }, + pattern: { + value: /^[1-3]$/, + message: '', + }, +}; diff --git a/apps/service/src/types/index.ts b/apps/service/src/types/index.ts index bb824842..2ead463f 100644 --- a/apps/service/src/types/index.ts +++ b/apps/service/src/types/index.ts @@ -1 +1,2 @@ export * from './component'; +export * from './onboarding'; diff --git a/apps/service/src/types/onboarding.ts b/apps/service/src/types/onboarding.ts new file mode 100644 index 00000000..bc7848b2 --- /dev/null +++ b/apps/service/src/types/onboarding.ts @@ -0,0 +1,4 @@ +export type UserInfoFormData = { + name: string; + grade: string; +}; From 237b7aa0771f2f921330c7312afa7344bd5d153b Mon Sep 17 00:00:00 2001 From: chaeyoung103 Date: Tue, 15 Jul 2025 11:17:21 +0900 Subject: [PATCH 8/9] =?UTF-8?q?fix(service):=20=EB=AF=B8=EB=93=A4=EC=9B=A8?= =?UTF-8?q?=EC=96=B4=20=EB=A6=AC=ED=94=84=EB=A0=88=EC=8B=9C=ED=86=A0?= =?UTF-8?q?=ED=81=B0=20=EC=A0=80=EC=9E=A5=20=EB=B0=A9=EC=8B=9D=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=A1=9C=EC=A7=81=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/service/src/apis/authMiddleware.ts | 137 +++++++++--------- .../apis/controller/auth/postRefreshToken.ts | 17 +++ apps/service/src/app/providers.tsx | 4 +- apps/service/src/utils/common/auth.ts | 5 +- 4 files changed, 92 insertions(+), 71 deletions(-) create mode 100644 apps/service/src/apis/controller/auth/postRefreshToken.ts diff --git a/apps/service/src/apis/authMiddleware.ts b/apps/service/src/apis/authMiddleware.ts index a2578aee..33de2eaf 100644 --- a/apps/service/src/apis/authMiddleware.ts +++ b/apps/service/src/apis/authMiddleware.ts @@ -1,71 +1,72 @@ 'use client'; import { Middleware } from 'openapi-fetch'; -import { getAccessToken, setAccessToken } from '@utils'; - -// const UNPROTECTED_ROUTES = ['/api/v1/auth/admin/login', '/api/student/auth/social/login']; - -// const reissueToken = async () => { -// try { -// const response = await fetch( -// `${process.env.NEXT_PUBLIC_API_BASE_URL}/api/common/auth/refresh`, -// { -// method: 'GET', -// credentials: 'include', -// } -// ); - -// if (!response.ok) throw new Error('Token reissue failed'); - -// const data = await response.json(); -// const accessToken = data.data.accessToken; -// setAccessToken(accessToken); -// return accessToken; -// } catch (error) { -// console.error('Reissue failed:', error); -// localStorage.removeItem('accessToken'); -// window.location.href = '/login'; -// return null; -// } -// }; - -// const authMiddleware: Middleware = { -// async onRequest({ schemaPath, request }: { schemaPath: string; request: Request }) { -// if (UNPROTECTED_ROUTES.some((pathname) => schemaPath.startsWith(pathname))) { -// return undefined; -// } - -// let accessToken = getAccessToken(); - -// if (!accessToken) { -// accessToken = await reissueToken(); - -// if (!accessToken) { -// console.error('Access token reissue failed. Logging out...'); -// return request; -// } -// } - -// request.headers.set('Authorization', `Bearer ${accessToken}`); -// return request; -// }, - -// async onResponse({ request, response }) { -// if (response.status === 401) { -// console.warn('Access token expired. Attempting reissue...'); - -// const newAccessToken = await reissueToken(); - -// if (!newAccessToken) { -// console.error('Reissue failed. Logging out...'); -// return response; -// } - -// request.headers.set('Authorization', `Bearer ${newAccessToken}`); -// return fetch(request); -// } -// return response; -// }, -// }; - -// export default authMiddleware; +import { getAccessToken, setAccessToken, setName, setRefreshToken } from '@utils'; +import { postRefreshToken } from '@/apis/controller/auth'; + +const UNPROTECTED_ROUTES = ['/api/student/auth/social/login', '/api/common/auth/refresh']; + +const reissueToken = async () => { + let accessToken = getAccessToken(); + + if (accessToken) { + return accessToken; + } + + const result = await postRefreshToken(); + + if (!result.isSuccess || !result.data) { + console.error('액세스토큰 갱신 실패:', result.error); + localStorage.removeItem('accessToken'); + localStorage.removeItem('refreshToken'); + window.location.href = '/login'; + return null; + } + + if (result.data?.token.accessToken) { + setAccessToken(result.data.token.accessToken); + accessToken = result.data.token.accessToken; + } + if (result.data?.token.refreshToken) { + setRefreshToken(result.data.token.refreshToken); + } + if (result.data?.name) { + setName(result.data.name); + } + return accessToken; +}; + +const authMiddleware: Middleware = { + async onRequest({ schemaPath, request }: { schemaPath: string; request: Request }) { + if (UNPROTECTED_ROUTES.some((pathname) => schemaPath.startsWith(pathname))) { + return undefined; + } + + const accessToken = await reissueToken(); + + if (accessToken) { + request.headers.set('Authorization', `Bearer ${accessToken}`); + } + + return request; + }, + + async onResponse({ request, response }) { + if (response.status === 401) { + console.warn('Access token expired. Attempting reissue...'); + + const newAccessToken = await reissueToken(); + + if (!newAccessToken) { + console.error('Reissue failed. Logging out...'); + return response; + } + + request.headers.set('Authorization', `Bearer ${newAccessToken}`); + return fetch(request); + } + return response; + }, +}; + +export default authMiddleware; diff --git a/apps/service/src/apis/controller/auth/postRefreshToken.ts b/apps/service/src/apis/controller/auth/postRefreshToken.ts new file mode 100644 index 00000000..251b73a7 --- /dev/null +++ b/apps/service/src/apis/controller/auth/postRefreshToken.ts @@ -0,0 +1,17 @@ +'use client'; + +const postRefreshToken = async () => { + try { + const res = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/api/common/auth/refresh`, { + method: 'GET', + }); + if (!res.ok) throw new Error('Refresh failed'); + + const data = await res.json(); + return { isSuccess: true, data }; + } catch (e) { + return { isSuccess: false, error: e }; + } +}; + +export default postRefreshToken; diff --git a/apps/service/src/app/providers.tsx b/apps/service/src/app/providers.tsx index dfab530b..828c16e8 100644 --- a/apps/service/src/app/providers.tsx +++ b/apps/service/src/app/providers.tsx @@ -4,11 +4,11 @@ import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; import type * as React from 'react'; import { client } from '@apis'; -// import authMiddleware from '@/apis/authMiddleware'; +import authMiddleware from '@/apis/authMiddleware'; export default function Providers({ children }: { children: React.ReactNode }) { const queryClient = new QueryClient(); - // client.use(authMiddleware); + client.use(authMiddleware); return ( diff --git a/apps/service/src/utils/common/auth.ts b/apps/service/src/utils/common/auth.ts index 8aeb2b5e..3ffebfad 100644 --- a/apps/service/src/utils/common/auth.ts +++ b/apps/service/src/utils/common/auth.ts @@ -1,6 +1,9 @@ const getAccessToken = () => localStorage.getItem('accessToken'); const setAccessToken = (accessToken: string) => localStorage.setItem('accessToken', accessToken); +const getRefreshToken = () => localStorage.getItem('refreshToken'); +const setRefreshToken = (refreshToken: string) => + localStorage.setItem('refreshToken', refreshToken); const getName = () => localStorage.getItem('name'); const setName = (name: string) => localStorage.setItem('name', name); -export { getAccessToken, setAccessToken, getName, setName }; +export { getAccessToken, setAccessToken, getRefreshToken, setRefreshToken, getName, setName }; From e55dcb58cb8799e29c7dfc7eecc31996df59a17f Mon Sep 17 00:00:00 2001 From: chaeyoung103 Date: Tue, 15 Jul 2025 11:17:52 +0900 Subject: [PATCH 9/9] =?UTF-8?q?fix(service):=20=ED=97=A4=EB=8D=94=EC=97=90?= =?UTF-8?q?=20=EB=B2=84=ED=8A=BC=20=EC=97=86=EB=8A=94=20=EB=B2=84=EC=A0=84?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/service/src/components/common/Header.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/service/src/components/common/Header.tsx b/apps/service/src/components/common/Header.tsx index d6a43b91..40569dde 100644 --- a/apps/service/src/components/common/Header.tsx +++ b/apps/service/src/components/common/Header.tsx @@ -6,7 +6,7 @@ import { IcHome, IcLeft } from '@svg'; interface HeaderProps { title: string; - iconType?: 'home' | 'back'; + iconType?: 'home' | 'back' | 'none'; } const Header = ({ title, iconType = 'home' }: HeaderProps) => { @@ -22,6 +22,7 @@ const Header = ({ title, iconType = 'home' }: HeaderProps) => {
{iconType === 'home' && } {iconType === 'back' && router.back()} />} + {iconType === 'none' &&
}

{title}