diff --git a/docs/policy/campus-and-learning.md b/docs/policy/campus-and-learning.md index 2f93516..8e43ed7 100644 --- a/docs/policy/campus-and-learning.md +++ b/docs/policy/campus-and-learning.md @@ -10,6 +10,8 @@ - 단일 캠퍼스형은 생성 즉시 운영 가능하다. - 복합 캠퍼스형은 교사 생성 후 관리자 승인 절차를 거친 뒤 운영 가능하다. - 관리자(Back Office)도 캠퍼스를 직접 생성할 수 있다. +- 관리자 생성 시 캠퍼스 마스터 역할의 교사는 회원 가입이 완료된 상태여야 하며, 필요하면 관리자 화면에서 회원 등록을 병행한다. +- 관리자 생성 시 등록 정보는 캠퍼스 명칭, 직접 유입 개인화 도메인, 로고, 캠퍼스 사용 교재를 포함한다. ## 캠퍼스 유형 - 단일 캠퍼스형: 1인 교사 공부방 또는 단일 학원형 @@ -31,6 +33,9 @@ - 동일 학생이어도 교재가 바뀌면 새 강좌 생성 - 동일 교재여도 학생 구성이 바뀌면 새 강좌 생성 - 강좌 복사 기능을 제공하며 수업 중 강좌도 복사 가능하다 +- 동일 캠퍼스 소속 교사는 본인이 개설한 강좌 외에 타 교사의 강좌도 검색·복사할 수 있다 +- 동일 강좌 ID 안에서는 교재 교체, 교재 추가, 전체 학생 변경이 불가하며, 교재 또는 학생 구성이 크게 바뀌면 신규 강좌 ID를 생성하는 흐름으로 본다 +- 개별 학생/수강생 상태 변경과 운영 중 학생 추가 초대는 가능하다 - Live 상태 강좌 수는 제한될 수 있고 종료 강좌를 포함한 총 강좌 수는 무제한이다 ## 강좌 생명주기 @@ -45,6 +50,17 @@ - 운영 중 학생 상태를 이용 중지 또는 수강 종료로 변경할 수 있다 - 학생이 운영 중간에 가입하면 이전 과제/평가도 미수행 상태로 제시한다 - 상태 변경 시 학생과 학부모에게 알림이 발송된다 +- 수업 코스는 교재 Unit 기반으로 정렬하고, 수업 자료, 온라인 콘텐츠, 과제, 평가를 선별 연동한다 +- 수업 일정과 과제/평가 출제 일정은 타임라인 기반으로 관리한다 + +## 캠퍼스/강좌 상태 관리 +- 온라인 캠퍼스, 가맹 캠퍼스, 클래스, 강좌, 회원의 캠퍼스 가입 상태는 별도 상태 체계로 관리한다 +- 온라인 캠퍼스에는 승인 대기, 운영 중, 운영 일시 정지, 운영 종료 상태가 있다 +- 클래스/가맹 캠퍼스에는 운영 중, 운영 일시 정지, 운영 종료 상태가 있다 +- 강좌에는 강의 준비 중, 강의 중, 강의 일시 정지, 강의 종료 상태가 있다 +- 교사 가입 상태에는 정상, 권한 일시 중지, 종료(탈퇴)가 있다 +- 학생/수강생의 강좌 가입 상태에는 수강 대기 중, 수강 중, 권한 일시 중지, 수강 종료, 종료(탈퇴)가 있다 +- 상위 조직이 일시 정지되면 하위 조직 상태에도 반영된다 ## 교사 업무 흐름 - 캠퍼스 생성 또는 초대 가입 @@ -58,7 +74,9 @@ ## 학생/수강생 학습 흐름 - 초대 URL/초대 코드 기반 강좌 가입 또는 학생 회원 자기주도학습 시작 +- 예습 패키지 수행: 음원, 동영상, 단어 학습, 온라인 플래시 카드, 워드 리스트 - 수업 참여 +- 온라인 학습 콘텐츠 수행 - 과제 수행 및 제출 - 평가 수행 및 제출 - 복습 및 자기주도학습 @@ -73,11 +91,22 @@ ## SMS 정책 - 캠퍼스 초대, 학습 알림, 보고서 발송 등에 SMS를 병행 사용할 수 있다 -- SMS 수량은 캠퍼스 마스터가 구매/충전하는 구조다 -- 발송 성공 시 수량이 차감된다 -- 백오피스에서 수량 충전 기능을 제공한다 +- 초대 시 이메일 발송은 필수, 휴대폰 SMS 발송은 선택으로 본다 +- SMS 발송은 개발 후 서비스 오픈 시 프로세스 Hidden 처리될 수 있다 + +## 초대 코드 정책 +- 초대 코드는 캠퍼스에 초대되는 회원의 가입 일치 여부를 판단하고, 캠퍼스별 교재 단위 이용자 수를 관리하는 수단이다 +- 교사용 초대 코드와 학생용 초대 코드는 생성·사용 로직을 이원화한다 +- 학생 초대 코드는 교재별 수량 관리 대상이며, 강좌에 학생을 초대할 때 잔여 수량 내에서 생성한다 +- 학생 초대 코드 상태는 미사용, 발송, 발송 실패, 재발송, 강좌 가입, 수업 시작으로 관리한다 +- 초대 코드 수량은 전체 수량, 대기 수량, 사용 수량, 잔여 수량으로 표현한다 +- 대기 수량은 발송/재발송 상태의 합계이고, 사용 수량은 강좌 가입/수업 시작 상태의 합계다 +- 학생이 초대 코드를 분실하면 교사가 동일 초대 코드를 재발송할 수 있어야 한다 +- 강좌 생성 후 학생별 초대 코드 목록은 Excel 목록형 또는 PDF 인쇄형으로 다운로드할 수 있다 +- 해외 임대 서비스에서는 유통업체 또는 관리자 화면을 통해 캠퍼스별·교재별 초대 코드 수량을 생성/증감 관리한다 ## 개발 해석 포인트 - 캠퍼스, 클래스, 강좌는 이름이 비슷하지만 역할이 다르므로 테이블/DTO/응답 모델을 명확히 분리해야 한다 - 강좌는 실제 학습 데이터와 과제/평가의 기준 단위이므로, API 설계 시 강좌 식별자를 중심으로 묶는 것이 자연스럽다 - 초대, 가입, 탈퇴, 종료, 상태 변경은 회원 상태와 강좌 소속 상태를 구분해 처리해야 한다 +- 초대 코드는 단순 인증 토큰이 아니라 교재별 이용 수량과 연결된 운영 데이터이므로 상태 이력과 수량 집계를 함께 고려해야 한다 diff --git a/docs/policy/content-and-lms.md b/docs/policy/content-and-lms.md index 8a3fb92..cf99ebb 100644 --- a/docs/policy/content-and-lms.md +++ b/docs/policy/content-and-lms.md @@ -1,10 +1,12 @@ # 정책서: 콘텐츠/LMS ## 콘텐츠 범주 -- 수업 자료: PPT, Lesson Plan, Syllabus, Scope & Sequence, Teacher Guide, Script, Worksheet, Word Test 등 -- 평가 자료: Test Sheet, 단원 평가, 중간/최종 평가, 평가 문항, 내신 수행평가 자료 -- 멀티미디어: MP3, MP4, 온라인 PPT, 온라인 클래스, 플래시 카드 -- 솔루션형 콘텐츠: 교사용 e-Book, 온라인 평가, 온라인 OMR 평가, Voca*List, 클래스 카드, 레벨 테스트 +- 교사 전용 자료: Lesson Plan, Syllabus, Scope & Sequence, 강의 계획서, Teacher Guide, 수업 PPT, 정답 및 해설, 워크시트 +- 교재 부가 자료: 본문 스크립트, 한글 해석/번역, 단어 리스트, 플래시 카드 +- 평가 자료: Word Test, Test Sheet, 단원 평가, 중간/최종 평가, 온라인 평가, 온라인 OMR 평가지 +- 멀티미디어: MP3, MP4 +- 온라인 학습 자료: 온라인 학습 콘텐츠, 온라인 플래시 카드, 온라인 클래스, 클래스 카드 +- 솔루션형 콘텐츠: 교사용 e-Book, 온라인 수업 PPT, 문항 출제 마법사, 레벨 테스트, 내신 수행 평가 자료실, Voca*List - 교사 보유 자료: PDF, MP3, MP4 업로드 자료 ## 콘텐츠 생산과 활용 @@ -12,17 +14,35 @@ - 교사는 수업 뷰어에서 문서형 자료, URL, 온라인 콘텐츠, 과제, 평가를 통합적으로 사용한다 - 학생은 온라인 학습 콘텐츠 뷰어와 과제/평가 뷰어에서 콘텐츠를 수행한다 - 평가지는 별도 생성 솔루션(Test Generator)으로 출제, 편집, 출력한다 +- CMS는 교재 카테고리(SKILL), 시리즈, 교재, 목차(Unit), 문서 자료, 멀티미디어, 온라인 학습 DB, 평가 문항 DB, eBook, 온라인 OMR, 온라인 평가, 온라인 플래시 카드, 제휴 자료를 통합 관리한다 +- 콘텐츠는 원천 콘텐츠 생산, 2차 콘텐츠 저작, 사용자 활용 지원 서비스의 흐름으로 관리한다 +- A*List에서는 다운로드, 단독 웹 뷰어, 온라인 응시 기능을 중심으로 제공하고, A★캠퍼스에서는 수업 뷰어/과제 뷰어 연동, 강좌 단위 배포, LMS 저장을 중심으로 제공한다 +- 온라인 응시 기능은 A*List에서 교사 편의와 A★캠퍼스 체험을 위한 기능으로 제공될 수 있으며, 동일 URL로 복수 학생이 접속하되 DB 저장을 하지 않는 흐름이 있다 +- A★캠퍼스의 온라인 평가/온라인 OMR/레벨테스트는 강좌 또는 학생 단위로 배포·응시·채점·피드백·LMS 저장을 수행한다 ## 콘텐츠 메타데이터 - 콘텐츠는 교재, 시리즈, 레벨, 학년, 난이도, CEFR, Lexile, AR 등 학습 레벨 메타데이터를 가진다 - 교재 레벨 메타는 백오피스/CMS에서 별도로 관리한다 - 메타데이터는 검색, 교재 노출, 학습 추천, 보고서 분류에 영향을 줄 수 있다 +- 교재-콘텐츠 관리 위계는 교재 카테고리(SKILL), 시리즈, 레벨, 유닛(Unit), 레슨(Lesson), 콘텐츠 분류 카테고리, 콘텐츠로 구성한다 +- 부속 교재는 Workbook, Teacher's Book, 책 속의 책 등 물리적으로 분책된 교재를 관리하며, ISBN이 다른 경우 연관 교재 세트 개념으로 구성할 수 있다 +- 유닛에는 권장 수업 차시 수를 둘 수 있으며, 기본 커리큘럼과 수업 코스 설계에 활용한다 +- 개별 파일 기준 속성은 콘텐츠 타입, 파일 포맷, 교재 범위, 작성 언어, 제공 권한 범위를 포함한다 +- 문항/활동형 콘텐츠는 개별 활동 또는 개별 문항 단위로 SKILL, Sub SKILL, 난이도, 활동 유형, 문항 유형, 구조 메타를 적용한다 ## 콘텐츠 접근 권한 - 교사: 대부분의 수업 자료와 교사용 자료, 수업 뷰어 기능 접근 가능 - 학생: 제한된 수업 자료와 멀티미디어, 온라인 학습 콘텐츠 접근 가능 - 수강생: 강좌 기반 학습 활동 위주로 제한된 권한을 가진다 - 학생용 I/F에서는 기본 권한 자료와 교사가 추가 허용한 자료를 함께 노출할 수 있다 +- 접근 권한 표기는 `●`, `▲`, `☆`, `X` 기준으로 해석한다 +- `●`: 해당 회원 유형이 기본 접근/사용 가능 +- `▲`: 수강생이 초대받은 강좌 교재 범위 안에서 접근/사용 가능 +- `☆`: 학생/수강생이 강좌 소속이고 교사가 수업 설계 중 허용 또는 전송한 경우 접근/사용 가능 +- `X`: 해당 인터페이스에서 직접 제공하지 않거나 구버전/별도 경로로 취급 +- 학생은 본문 스크립트, 한글 해석/번역, 단어 리스트, 멀티미디어, 온라인 학습 콘텐츠, 온라인 플래시 카드 등에 기본 접근할 수 있다 +- 수강생은 초대받은 강좌 교재 범위 안에서 본문 스크립트, 번역, 단어 리스트, 온라인 학습, 온라인 플래시 카드 등에 접근한다 +- 교사용 자료와 평가 자료 상당수는 교사가 공유하거나 강좌 과제/평가로 전송한 경우에만 학생/수강생에게 노출된다 ## 주요 솔루션과 기능 - 교수자용 수업 뷰어 @@ -38,13 +58,29 @@ - 문항 검색, 문항 수/유형 설정, 사용자 문항 추가, PDF 생성, 출력 - 학습 분석 보고서 - 레벨테스트, 교재별, Unit별 보고서 생성 +- 온라인 OMR 평가지 + - 문서 포맷 시험지를 OMR 타입 온라인 평가 콘텐츠로 2차 가공 + - 문항 타입, 정답, SKILL 메타를 입력하고 온라인 평가 뷰어로 응시 +- 온라인 수업 PPT + - 수업 PPT를 HTML 포맷으로 변환하여 온라인 수업 PPT 뷰어와 통합 수업 뷰어에 연동 +- 교사용 eBook + - PDF 기반 eBook 생성, 복수 책 구성, Audio/Video, 정답, 온라인 PPT, URL 연동 +- 레벨 테스트 + - 시험지 분류, 시험지 레벨, 문항 구성, 분석 가이드, 추천 교재, 시험 시간 설정, LMS 연동 +- 온라인 학습 콘텐츠 + - Unit 단위로 SKILL 영역을 구성하고, SKILL별 모듈을 선택·순서화한다 + - 모듈은 하나 이상의 Activity 또는 Test 문항으로 구성한다 +- 온라인 플래시 카드 + - 단어, 뜻, 예문, 음원, 이미지를 관리하고 수동/자동 단어 보기, 암기/미암기 관리, 내 단어장 등록을 제공한다 ## 학습 관리 대상 - 레벨테스트 +- 예습 패키지 - 온라인 학습 콘텐츠 - 온라인 평가 - 온라인 과제 - 클래스 과제 +- 셀프 스터디(더 공부하기) ## 학습 관리 영역 - 정량 관리 @@ -57,19 +93,42 @@ - 학습 진도 - 전체 성취도 - SKILL별 성취도 + - Sub SKILL별 성취도 + - 단위 학습 성취도 - 성취도 변화 추이 - 산출물 관리 - 오답 노트 - 학습 산출물 데이터 - 유사 문항 학습 - 포트폴리오 +- 정성 관리 + - 교사 평가 텍스트 + - 평가 항목별 5점 척도 평가 + - SKILL별 정성 평가 + - 학습 태도 및 성장도 평가 + +## 학습 관리 데이터 구조 +- 정량적 학습 관리는 학습 뷰어와 LMS DB 데이터로 자동 축적한다 +- 정성적 학습 관리는 교사가 대시보드 통계나 과제 산출물을 보고 입력한 평가로 관리한다 +- 학습 수행 데이터는 단위 학습 일자, 시작 시간, 종료 시간, 수행 여부, 학습 항목, 학습 시간, 수행률, 빈도 등을 포함한다 +- 학습 성취 데이터는 문항 제출 답안, 정오 판정, 파일 산출물, 단위 성취도, SKILL별 성취도, 문항 유형별 성취도, 취약 SKILL 등을 포함한다 +- 학생 개인 기록 외에 강좌, 클래스, 캠퍼스 단위 평균과 순위 데이터를 생성할 수 있다 +- 레벨테스트 및 타 교재 학습 이력에 기반해 성취도 추이와 학습 포트폴리오를 구성한다 +- 학습 관리 구조는 교재, Unit, Lesson, 학습 모듈, Activity/Test 문항으로 이어진다 +- 단일형 콘텐츠는 온라인 평가/과제처럼 하나의 콘텐츠 수행으로 학습이 완료되는 단위다 +- 과정형 콘텐츠는 온라인 학습/온라인 클래스처럼 복수 모듈이 순차 학습 Flow를 이루는 콘텐츠다 ## 시각화 및 보고 - 대시보드 I/F와 학습 분석 보고서가 주요 표현 수단이다 - 학생별/강좌별 현황, 성취도, 수행률, 레벨테스트 결과 시각화가 중요하다 - 교사 화면에서는 강좌별 관리와 학생별 관리가 모두 필요하다 +- 레벨테스트 분석 보고서는 총점, SKILL/Sub SKILL별 성취율, 강점/취약 스킬, 추천 교재, 문항별 채점표를 포함한다 +- 단원/중간/기말 평가 보고서는 평가 성취도, 문항별 채점표, SKILL별 성취도, 학습 시간 그래프, 온라인 학습/과제 성취도 추이를 포함한다 +- 교재 학습 분석 보고서는 강의 종료 또는 교재 학습 완료 시 생성하며, 학습 수행 분석, 학습 성취도 분석, 교사 정성 평가를 포함한다 ## 개발 해석 포인트 - 콘텐츠 자체 메타데이터와 학습 수행 데이터는 분리 저장하는 편이 좋다 - 학습 관리 영역은 단순 진도율이 아니라 성취도·산출물·포트폴리오까지 포함하므로 조회 API가 넓어질 가능성이 높다 - 학생용 권한은 `회원 유형`, `강좌 소속 여부`, `교사 공유 여부`의 조합으로 판단해야 한다 +- 콘텐츠 접근 권한은 콘텐츠 타입별로 다르므로 공통 boolean 하나보다 콘텐츠-회원유형-강좌소속-공유여부 조합 정책으로 모델링하는 편이 안전하다 +- 학습 데이터는 개인, 강좌, 클래스, 캠퍼스 단위 집계가 필요하므로 원천 이벤트/결과 데이터와 집계 데이터를 분리하는 설계가 유리하다 diff --git a/docs/policy/index.md b/docs/policy/index.md index 6f5e5b1..cfeba7d 100644 --- a/docs/policy/index.md +++ b/docs/policy/index.md @@ -1,6 +1,6 @@ # 정책서 요약 인덱스 -원본 정책서: `D:/work/project/202601_alist/document/00_정책서/이퍼블릭_AList 교수학습지원 통합 플랫폼 구축_정책 설계_Ver 0.29.pdf` +원본 정책서: `D:/work/project/202601_alist/document/00_정책서/이퍼블릭_AList 교수학습지원 통합 플랫폼 구축_정책 설계_Ver 0.32.pdf` ## 문서 구성 - [서비스 개요](overview.md) @@ -12,7 +12,8 @@ ## 빠른 요약 - 플랫폼은 메인 서비스, 캠퍼스 메인, 교사용 캠퍼스, 학생용 캠퍼스, 백오피스/CMS로 분리된다. - 핵심 도메인은 회원, 캠퍼스, 클래스, 강좌, 콘텐츠, 과제/평가, 학습 관리다. -- 정책서상 주요 제약은 회원 유형별 권한 차등, 캠퍼스 소속 제한, 강좌-교재 1:1 관계, 캠퍼스 개인화 도메인, 레거시 병행 운영이다. +- 정책서상 주요 제약은 회원 유형별 권한 차등, 캠퍼스 소속 제한, 강좌-교재 1:1 관계, 초대 코드 상태/수량 관리, 캠퍼스 개인화 도메인, 레거시 병행 운영이다. +- `Ver 0.32`에서는 콘텐츠 생산/활용 구조, 콘텐츠 접근 권한, 학습 관리 데이터, 학습 분석 보고서, 인터페이스 메뉴 범위가 더 구체화되었다. - 실제 개발 판단에서는 이 문서 묶음을 기능 정의서보다 `도메인 정책서`로 취급하는 것이 적합하다. ## 개발 시 우선 참고 포인트 diff --git a/docs/policy/interfaces-and-operations.md b/docs/policy/interfaces-and-operations.md index 33be6eb..bf1ae18 100644 --- a/docs/policy/interfaces-and-operations.md +++ b/docs/policy/interfaces-and-operations.md @@ -8,26 +8,35 @@ - 캠퍼스 교사용(`class.a-campus.co.kr`) - 강좌/학생/학습/알림/수업준비/캠퍼스 관리 중심 - 캠퍼스 학생용(`student.a-campus.co.kr`) - - 오늘 학습, 셀프 스터디, 나의 학습, 알림방 중심 + - 나의 학습, 과제·평가, 자습하기, 복습하기, 학습 통계·리포트, 소통방 중심 - 백오피스/CMS(`admin.alist.co.kr`) - 운영 관리, 교재/콘텐츠/CMS, 파트너, 홍보, 문의, 통계 관리 ## 교사용 I/F 핵심 메뉴 -- 강좌 관리 -- 학생 관리 -- 학습 관리 -- 소통방 -- 수업 준비 +- 수업(강좌) 관리 +- 학생·학습 관리 +- 교재 수업 준비 +- 소통방: 알림장, 1:1 메시지 - 마이페이지 - 역할에 따라 가맹 캠퍼스 관리, 캠퍼스 관리, 공지 관리 등 추가 메뉴 노출 +- 캠퍼스 마스터 전용 메뉴와 가맹 캠퍼스장 전용 메뉴는 타 역할에서 Hidden 처리한다 +- 교사용 I/F는 강좌 생성, 강좌 복사, 학생 초대, 수업 요일 지정, 수업 하기, 과제 출제, 평가 출제, 레벨테스트 보내기 등을 CTA로 제공한다 +- 학생·학습 관리는 학습 기간, 누적/일자별 학습 시간, 예습 패키지 수행률, 온라인 학습 수행률/성취도, 과제/평가 수행률, 학습 분석 보고서 생성을 포함한다 ## 학생용 I/F 핵심 메뉴 -- 오늘 학습 -- 셀프 스터디 - 나의 학습 +- 과제·평가 +- 자습하기 +- 더 공부하기 +- 복습하기 +- 학습 통계·리포트 - 알림방 - 마이페이지 - 학생만 `학습 교재 추가`, `교재 추가` 같은 자기주도학습 확장 기능 사용 가능 +- 수강생은 학생 정회원 전용 자기주도학습 메뉴가 Hidden 처리된다 +- 학생용 I/F는 오늘 학습, 수업 캘린더, 내 학습 시간 기록, 온라인 학습 결과, 공지·알림, 1:1 메시지를 Dashboard로 제공한다 +- 과제·평가 메뉴는 수업(강좌) 목록, 과제 목록, 평가 목록, 레벨테스트 목록과 과제/평가/레벨테스트 수행 CTA를 포함한다 +- 학습 통계·리포트는 교재별 학습 기간, 총 학습 시간, 일자별 학습 시간, 온라인 학습/과제/평가 점수, SKILL별 평균, 교사 정성 평가, 동일 교재 학습자 비교를 포함한다 ## 백오피스/CMS 핵심 메뉴 - 관리자/권한 관리 @@ -39,6 +48,23 @@ - 교재 관리, 캠퍼스 서비스 교재 관리 - 콘텐츠 저작/관리, 온라인 콘텐츠 관리, 제휴사 콘텐츠 관리 - 학습 관리, 통계 관리 +- 캠퍼스 초대 코드 관리와 교재별 초대 코드 수량 관리를 포함한다 +- 해외 임대 서비스 전용 메뉴는 국내 서비스에서 Hidden 처리될 수 있다 + +## 메인 서비스 메뉴 정책 +- 메인 서비스는 `교재/수업 자료`, `스마트 수업 지원`, `지원 센터`, `마이페이지`를 중심으로 구성한다 +- `교재/수업 자료`는 Coursebook, Phonics, Readers, Reading, Listening, Speaking, Writing, Grammar, Vocabulary, Adult, Oxford 등 SKILL/브랜드 기준 메뉴를 포함한다 +- `스마트 수업 지원`은 A★캠퍼스, Apps, A★List Player, Voca*List, 문항 출제 마법사, 온라인 클래스 등으로 구성한다 +- `지원 센터`는 세미나, Oxford 파트너 서비스, 교재 활용 동영상, 이벤트, 커뮤니티 게시판, FAQ, 공지사항, 1:1 문의 등을 포함한다 +- `마이페이지`는 나의 정보, 나의 교재/자료, 시험지 보관함, 나의 테스트 등을 포함한다 +- 시험지 보관함은 교사 회원에게만 제공한다 +- 2Depth 메뉴의 가감은 Back Office에서 운영 가능해야 한다 + +## 캠퍼스 메인 메뉴 정책 +- 캠퍼스 메인은 서비스 소개, 이용방법, 체험하기, 수업교재, 고객센터로 구성한다 +- 수업교재는 교재 소개와 제공 자료 목록을 보여주되 Viewing/Download는 제공하지 않고, 캠퍼스 마스터가 생성한 온라인 캠퍼스 교재로 등록할 수 있게 한다 +- 체험하기는 선생님 수업 뷰어, 과제 출제, 평가 출제, 학생 온라인 학습 체험을 제공한다 +- 서비스 문의하기는 비회원도 작성할 수 있으며, 비로그인 작성 시 임시 비밀번호, 이름, 이메일을 입력한다 ## 도메인 정책 - 메인 서비스와 캠퍼스 서비스 도메인을 분리한다 diff --git a/docs/policy/membership-and-auth.md b/docs/policy/membership-and-auth.md index e133f4b..3435acb 100644 --- a/docs/policy/membership-and-auth.md +++ b/docs/policy/membership-and-auth.md @@ -19,6 +19,7 @@ - 학생 가입: 서비스 직접 방문, 부모 동의 인증 포함 휴대폰 인증, 이메일 인증 - 수강생 가입: 캠퍼스 초대 URL + 초대 코드 기반 가입, 학생용 I/F 접근 계정 생성 - 관리자 계정: 개발 또는 마스터 관리자 등록 방식 +- 해외 임대 서비스는 휴대폰 인증 프로세스를 생략하고 이메일 인증을 필수 프로세스로 둘 수 있다 ## 인터페이스 접근 권한 - 관리자: 백오피스/CMS 접근, 메뉴별 권한 차등 @@ -27,6 +28,14 @@ - 수강생: 학생용 I/F 중심 접근, 메인 서비스 권한 없음 또는 제한 - 학생/수강생은 같은 학생용 I/F를 사용하지만 권한 범위는 다르다 +## 회원 유형별 서비스 활용 +- 교사: A*List 전체 자료 접근, 다운로드/Web Viewing, 교사용 eBook, 온라인 PPT, 문항 출제 마법사, 레벨테스트, 세미나 등 교사 지원 서비스 이용 +- 교사: A★캠퍼스에서 캠퍼스 생성 또는 초대 가입 후 캠퍼스 운영, 강좌 생성, 수업 코스 설계, 과제/평가 출제, 학생별 학습 관리 수행 +- 학생: A*List의 학생 접근 허용 자료 Viewing, 멀티미디어, 일부 온라인 콘텐츠 이용 +- 학생: `student.a-campus.co.kr/home`에서 교재 선택 기반 자기주도학습 가능 +- 학생: 강좌에 소속되면 예습 패키지, 온라인 학습, 과제/평가, 복습, 레벨테스트 응시 등 강좌 기반 학습 활동 가능 +- 수강생: 초대받은 강좌 기반 학습 활동만 이용 가능하며, 학생처럼 교재를 자유 선택하는 자기주도학습 권한은 제한된다 + ## 캠퍼스 소속 정책 - 교사 회원은 온라인 캠퍼스 역할과 무관하게 Live 상태에서 단수의 캠퍼스에만 소속 가능 - 수강생은 Live 상태에서 단수의 캠퍼스에만 소속 가능 @@ -61,7 +70,10 @@ ## 회원 전환 및 ID 병합 - 수강생은 모바일 본인 인증 또는 부모 인증 후 학생으로 전환 가능 - 전환 시 클래스/강좌 정보와 학습 이력은 유지한다 +- 해외 임대 및 국내 제휴 등 분리 독립 시스템 이용자는 수강생에서 학생으로 전환할 수 없는 예외가 있을 수 있다 - 학생/수강생은 ID 병합 기능 제공 대상이다 +- ID 병합 시 병합한 ID의 강좌 정보와 학습 이력을 승계하고, 병합 대상 ID의 상태값을 변경한다 +- ID 병합 대상 확인에는 비밀번호, 이름, 휴대폰 인증 또는 이메일 인증 정보가 사용된다 - 교사는 ID 병합 대상이 아니다 ## 레거시 회원 정책 diff --git a/docs/policy/overview.md b/docs/policy/overview.md index ec020b2..bd646ae 100644 --- a/docs/policy/overview.md +++ b/docs/policy/overview.md @@ -10,6 +10,7 @@ - A★캠퍼스: 교사·학생이 실제 교수·학습 활동을 수행하는 온라인 캠퍼스 - 백오피스/CMS: 플랫폼 운영, 콘텐츠 관리, 파트너/교재/회원/통계 관리 - 해외 확장: 영어 홍보 사이트, 해외 임대(이식) 서비스, 제휴/유통업체 운영 +- 통합 솔루션: 교사용 통합 수업 뷰어, 학생용 통합 과제 뷰어, 온라인 학습 솔루션, 학습 분석 보고서 제공 ## 주요 인터페이스 - `www.alist.co.kr`: 메인 서비스 @@ -22,8 +23,8 @@ ## 역할별 큰 흐름 - 교사: 회원 가입 후 캠퍼스 생성 또는 캠퍼스 초대 가입, 교재 선택, 수업 설계, 학생 초대, 과제/평가 관리, 학습 관리 수행 -- 학생: 본인 인증 기반 정회원으로 가입 가능, 자기주도학습과 캠퍼스 강좌 활동 수행 -- 수강생: 캠퍼스 초대 기반 준회원으로 가입하며 강좌 기반 학습 활동 위주로 이용 +- 학생: 본인 인증 기반 정회원으로 가입 가능, 일부 A*List 콘텐츠 이용, 자기주도학습과 캠퍼스 강좌 활동 수행 +- 수강생: 캠퍼스 초대 기반 준회원으로 가입하며 강좌 기반 학습 활동 위주로 이용하고, 교재 자유 선택은 제한된다 - 관리자/유통업체: 캠퍼스 생성 승인, 교재/콘텐츠 관리, 파트너 및 운영 정책 관리 ## 서비스 환경 정책 @@ -36,3 +37,4 @@ - 이 플랫폼은 단순 콘텐츠 사이트가 아니라 `회원 + 캠퍼스 + 강좌 + 학습 데이터`를 중심으로 동작하는 LMS 성격이 강하다. - 교사용과 학생용 인터페이스가 명확히 분리되어 있으므로 메뉴, 권한, 응답 데이터도 같은 기준으로 나눠 설계해야 한다. - 해외 임대/제휴 확장을 전제로 하므로 도메인, 캠퍼스 개인화, 메뉴 노출, 권한 모델에 고정값을 박아 넣지 않는 편이 안전하다. +- A*List와 A★캠퍼스는 같은 콘텐츠를 공유하더라도 다운로드, 온라인 뷰어, 강좌 단위 배포, LMS 저장 여부가 다를 수 있으므로 I/F별 활용 정책을 분리해야 한다. diff --git a/src/main/java/com/alist/api/config/OpenApiConfig.java b/src/main/java/com/alist/api/config/OpenApiConfig.java index b5e8da7..00c6091 100644 --- a/src/main/java/com/alist/api/config/OpenApiConfig.java +++ b/src/main/java/com/alist/api/config/OpenApiConfig.java @@ -39,12 +39,15 @@ public class OpenApiConfig { @Bean public GroupedOpenApi frontApi() { return GroupedOpenApi.builder() - .group("front") + .group("100. front") .pathsToMatch("/**") .pathsToExclude( - "/admin/**", - "/error", - "/actuator/**" + "/admin/**" + , "/cors/**" + , "/files/**" + , "/tusFiles/**" + , "/error" + , "/actuator/**" ) .build(); } @@ -52,8 +55,20 @@ public class OpenApiConfig { @Bean public GroupedOpenApi adminApi() { return GroupedOpenApi.builder() - .group("admin") + .group("500. admin") .pathsToMatch("/admin/**") .build(); } + + @Bean + public GroupedOpenApi commonApi() { + return GroupedOpenApi.builder() + .group("900. common") + .pathsToMatch( + "/cors/**" + , "/files/**" + , "/tusFiles/**" + ) + .build(); + } } diff --git a/src/main/java/com/alist/api/config/cache/CorsAllowedOriginsCache.java b/src/main/java/com/alist/api/config/cache/CorsAllowedOriginsCache.java index 1afd3b3..a8b4407 100644 --- a/src/main/java/com/alist/api/config/cache/CorsAllowedOriginsCache.java +++ b/src/main/java/com/alist/api/config/cache/CorsAllowedOriginsCache.java @@ -1,7 +1,7 @@ package com.alist.api.config.cache; -import com.alist.api.modules.auth.vo.CorsOriginVo; -import com.alist.api.modules.auth.mapper.CorsMapper; +import com.alist.api.modules.cors.vo.CorsOriginVo; +import com.alist.api.modules.cors.mapper.CorsMapper; import jakarta.annotation.PostConstruct; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; diff --git a/src/main/java/com/alist/api/config/jwt/JwtAuthenticationFilter.java b/src/main/java/com/alist/api/config/jwt/JwtAuthenticationFilter.java index 60568ca..ee72c78 100644 --- a/src/main/java/com/alist/api/config/jwt/JwtAuthenticationFilter.java +++ b/src/main/java/com/alist/api/config/jwt/JwtAuthenticationFilter.java @@ -28,7 +28,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { try { String requestUri = request.getRequestURI(); boolean adminRequest = requestUri.startsWith("/admin/"); - boolean sharedRequest = requestUri.startsWith("/tusFiles/") || requestUri.startsWith("/files/"); + boolean sharedRequest = requestUri.startsWith("/tusFiles/") || requestUri.startsWith("/files/") || requestUri.startsWith("/cors/"); String token = resolveToken(request, adminRequest, sharedRequest); if (token != null && jwtTokenProvider.validateToken(token)) { diff --git a/src/main/java/com/alist/api/modules/admin/auth/AdminAuthController.java b/src/main/java/com/alist/api/modules/admin/auth/AdminAuthController.java index 101be09..8e0ef4a 100644 --- a/src/main/java/com/alist/api/modules/admin/auth/AdminAuthController.java +++ b/src/main/java/com/alist/api/modules/admin/auth/AdminAuthController.java @@ -21,7 +21,7 @@ import java.util.HashMap; import java.util.Map; @Tag( - name = "50. Admin 로그인" + name = "501. Admin 로그인" , description = "ADMIN 사이트 로그인입니다. 로그인시 토큰 발급이 함께됩니다." ) @RestController diff --git a/src/main/java/com/alist/api/modules/admin/member/AdminMemberController.java b/src/main/java/com/alist/api/modules/admin/member/AdminMemberController.java index 766e136..6dc7539 100644 --- a/src/main/java/com/alist/api/modules/admin/member/AdminMemberController.java +++ b/src/main/java/com/alist/api/modules/admin/member/AdminMemberController.java @@ -2,12 +2,9 @@ package com.alist.api.modules.admin.member; import com.alist.api.common.response.ApiResponse; import com.alist.api.common.response.ApiResponseCode; -import com.alist.api.modules.admin.member.form.AdminMemberSearchForm; -import com.alist.api.modules.admin.member.form.AdminMemberUpdateForm; +import com.alist.api.modules.admin.member.form.*; import com.alist.api.modules.admin.member.service.AdminMemberService; -import com.alist.api.modules.admin.member.vo.AdminMemberDetailVo; -import com.alist.api.modules.admin.member.vo.AdminMemberListVo; -import com.alist.api.modules.admin.member.vo.AdminMemberUpdateVo; +import com.alist.api.modules.admin.member.vo.*; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; @@ -15,7 +12,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -@Tag(name = "51. 회원 관리", description = "관리자 회원 관리 API") +@Tag(name = "502. 회원 관리", description = "관리자 회원 관리 API") @Slf4j @RestController @RequestMapping("/admin/member") @@ -76,4 +73,55 @@ public class AdminMemberController { return ApiResponse.entity(result, ApiResponseCode.CODE_2005, "회원 수정"); } + + @Operation( + summary = "회원 휴면 상태 변경", + description = "관리자가 회원 PK 기준으로 휴면 상태를 변경합니다. Y: 휴면, N: 정상" + ) + @PatchMapping("/dormant") + public ResponseEntity> updateAdminMemberDormantStatus( + @Valid @RequestBody AdminMemberDormantStatusForm adminMemberDormantStatusForm + ) { + AdminMemberDormantStatusVo result = adminMemberService.updateAdminMemberDormantStatus(adminMemberDormantStatusForm.toAdminMemberDormantStatusUpdateDto()); + + if (!result.isDormantStatusUpdated()) { + return ApiResponse.entity(result, ApiResponseCode.CODE_2003); + } + + return ApiResponse.entity(result, ApiResponseCode.CODE_2005, "회원 휴면 상태 변경"); + } + + @Operation( + summary = "회원 탈퇴 상태 변경", + description = "관리자가 회원 PK 기준으로 탈퇴 상태를 변경합니다. N: 정상, P: 탈퇴대기, Y: 탈퇴완료" + ) + @PatchMapping("/withdraw") + public ResponseEntity> updateAdminMemberWithdrawStatus( + @Valid @RequestBody AdminMemberWithdrawStatusForm adminMemberWithdrawStatusForm + ) { + AdminMemberWithdrawStatusVo result = adminMemberService.updateAdminMemberWithdrawStatus(adminMemberWithdrawStatusForm.toAdminMemberWithdrawStatusUpdateDto()); + + if (!result.isWithdrawStatusUpdated()) { + return ApiResponse.entity(result, ApiResponseCode.CODE_2003); + } + + return ApiResponse.entity(result, ApiResponseCode.CODE_2005, "회원 탈퇴 상태 변경"); + } + + @Operation( + summary = "회원 계정 삭제", + description = "관리자가 회원 PK 기준으로 계정을 삭제 처리합니다. 실제 삭제가 아닌 del_yn 변경 방식입니다." + ) + @PatchMapping("/delete") + public ResponseEntity> deleteAdminMember( + @Valid @RequestBody AdminMemberDeleteForm adminMemberDeleteForm + ) { + AdminMemberDeleteVo result = adminMemberService.deleteAdminMember(adminMemberDeleteForm.toAdminMemberDeleteDto()); + + if (!result.isMemberDeleted()) { + return ApiResponse.entity(result, ApiResponseCode.CODE_2003); + } + + return ApiResponse.entity(result, ApiResponseCode.CODE_2005, "회원 계정 삭제"); + } } diff --git a/src/main/java/com/alist/api/modules/admin/member/dto/AdminMemberDeleteDto.java b/src/main/java/com/alist/api/modules/admin/member/dto/AdminMemberDeleteDto.java new file mode 100644 index 0000000..8ec7c14 --- /dev/null +++ b/src/main/java/com/alist/api/modules/admin/member/dto/AdminMemberDeleteDto.java @@ -0,0 +1,10 @@ +package com.alist.api.modules.admin.member.dto; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class AdminMemberDeleteDto { + private Integer userIdx; +} diff --git a/src/main/java/com/alist/api/modules/admin/member/dto/AdminMemberDormantStatusDto.java b/src/main/java/com/alist/api/modules/admin/member/dto/AdminMemberDormantStatusDto.java new file mode 100644 index 0000000..8af6a21 --- /dev/null +++ b/src/main/java/com/alist/api/modules/admin/member/dto/AdminMemberDormantStatusDto.java @@ -0,0 +1,11 @@ +package com.alist.api.modules.admin.member.dto; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class AdminMemberDormantStatusDto { + private Integer userIdx; + private String dormantYn; +} diff --git a/src/main/java/com/alist/api/modules/admin/member/dto/AdminMemberWithdrawStatusDto.java b/src/main/java/com/alist/api/modules/admin/member/dto/AdminMemberWithdrawStatusDto.java new file mode 100644 index 0000000..220b14b --- /dev/null +++ b/src/main/java/com/alist/api/modules/admin/member/dto/AdminMemberWithdrawStatusDto.java @@ -0,0 +1,11 @@ +package com.alist.api.modules.admin.member.dto; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class AdminMemberWithdrawStatusDto { + private Integer userIdx; + private String withdrawStatus; +} diff --git a/src/main/java/com/alist/api/modules/admin/member/form/AdminMemberDeleteForm.java b/src/main/java/com/alist/api/modules/admin/member/form/AdminMemberDeleteForm.java new file mode 100644 index 0000000..bfb2677 --- /dev/null +++ b/src/main/java/com/alist/api/modules/admin/member/form/AdminMemberDeleteForm.java @@ -0,0 +1,22 @@ +package com.alist.api.modules.admin.member.form; + +import com.alist.api.modules.admin.member.dto.AdminMemberDeleteDto; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.Setter; + +@Schema(description = "관리자 회원 계정 삭제 요청 폼") +@Getter +@Setter +public class AdminMemberDeleteForm { + @Schema(description = "회원 PK", example = "15", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "회원 PK는 필수입니다.") + private Integer userIdx; + + public AdminMemberDeleteDto toAdminMemberDeleteDto() { + AdminMemberDeleteDto adminMemberDeleteDto = new AdminMemberDeleteDto(); + adminMemberDeleteDto.setUserIdx(userIdx); + return adminMemberDeleteDto; + } +} diff --git a/src/main/java/com/alist/api/modules/admin/member/form/AdminMemberDormantStatusForm.java b/src/main/java/com/alist/api/modules/admin/member/form/AdminMemberDormantStatusForm.java new file mode 100644 index 0000000..6769933 --- /dev/null +++ b/src/main/java/com/alist/api/modules/admin/member/form/AdminMemberDormantStatusForm.java @@ -0,0 +1,30 @@ +package com.alist.api.modules.admin.member.form; + +import com.alist.api.modules.admin.member.dto.AdminMemberDormantStatusDto; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; +import lombok.Getter; +import lombok.Setter; + +@Schema(description = "관리자 회원 휴면 상태 변경 요청 폼") +@Getter +@Setter +public class AdminMemberDormantStatusForm { + @Schema(description = "회원 PK", example = "15", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "회원 PK는 필수입니다.") + private Integer userIdx; + + @Schema(description = "휴면 여부. Y: 휴면, N: 정상", example = "Y", requiredMode = Schema.RequiredMode.REQUIRED) + @NotBlank(message = "휴면 여부는 필수입니다.") + @Pattern(regexp = "^[YN]$", message = "휴면 여부는 Y 또는 N만 가능합니다.") + private String dormantYn; + + public AdminMemberDormantStatusDto toAdminMemberDormantStatusUpdateDto() { + AdminMemberDormantStatusDto adminMemberDormantStatusDto = new AdminMemberDormantStatusDto(); + adminMemberDormantStatusDto.setUserIdx(userIdx); + adminMemberDormantStatusDto.setDormantYn(dormantYn); + return adminMemberDormantStatusDto; + } +} diff --git a/src/main/java/com/alist/api/modules/admin/member/form/AdminMemberWithdrawStatusForm.java b/src/main/java/com/alist/api/modules/admin/member/form/AdminMemberWithdrawStatusForm.java new file mode 100644 index 0000000..644d2ad --- /dev/null +++ b/src/main/java/com/alist/api/modules/admin/member/form/AdminMemberWithdrawStatusForm.java @@ -0,0 +1,30 @@ +package com.alist.api.modules.admin.member.form; + +import com.alist.api.modules.admin.member.dto.AdminMemberWithdrawStatusDto; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; +import lombok.Getter; +import lombok.Setter; + +@Schema(description = "관리자 회원 탈퇴 상태 변경 요청 폼") +@Getter +@Setter +public class AdminMemberWithdrawStatusForm { + @Schema(description = "회원 PK", example = "15", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "회원 PK는 필수입니다.") + private Integer userIdx; + + @Schema(description = "탈퇴 상태. N: 정상, P: 탈퇴대기, Y: 탈퇴완료", example = "P", requiredMode = Schema.RequiredMode.REQUIRED) + @NotBlank(message = "탈퇴 상태는 필수입니다.") + @Pattern(regexp = "^[NPY]$", message = "탈퇴 상태는 N, P, Y만 가능합니다.") + private String withdrawStatus; + + public AdminMemberWithdrawStatusDto toAdminMemberWithdrawStatusUpdateDto() { + AdminMemberWithdrawStatusDto adminMemberWithdrawStatusDto = new AdminMemberWithdrawStatusDto(); + adminMemberWithdrawStatusDto.setUserIdx(userIdx); + adminMemberWithdrawStatusDto.setWithdrawStatus(withdrawStatus); + return adminMemberWithdrawStatusDto; + } +} diff --git a/src/main/java/com/alist/api/modules/admin/member/mapper/AdminMemberMapper.java b/src/main/java/com/alist/api/modules/admin/member/mapper/AdminMemberMapper.java index c217100..aa6bb7c 100644 --- a/src/main/java/com/alist/api/modules/admin/member/mapper/AdminMemberMapper.java +++ b/src/main/java/com/alist/api/modules/admin/member/mapper/AdminMemberMapper.java @@ -1,7 +1,6 @@ package com.alist.api.modules.admin.member.mapper; -import com.alist.api.modules.admin.member.dto.AdminMemberSearchDto; -import com.alist.api.modules.admin.member.dto.AdminMemberUpdateDto; +import com.alist.api.modules.admin.member.dto.*; import com.alist.api.modules.admin.member.vo.AdminMemberDetailVo; import com.alist.api.modules.admin.member.vo.AdminMemberVo; import org.apache.ibatis.annotations.Mapper; @@ -17,4 +16,10 @@ public interface AdminMemberMapper { AdminMemberDetailVo selectAdminMemberDetail(Integer userIdx); int updateAdminMember(AdminMemberUpdateDto adminMemberUpdateDto); + + int updateAdminMemberDormantStatus(AdminMemberDormantStatusDto adminMemberDormantStatusDto); + + int updateAdminMemberWithdrawStatus(AdminMemberWithdrawStatusDto adminMemberWithdrawStatusDto); + + int deleteAdminMember(AdminMemberDeleteDto adminMemberDeleteDto); } diff --git a/src/main/java/com/alist/api/modules/admin/member/service/AdminMemberService.java b/src/main/java/com/alist/api/modules/admin/member/service/AdminMemberService.java index eb72c34..bf7fa6c 100644 --- a/src/main/java/com/alist/api/modules/admin/member/service/AdminMemberService.java +++ b/src/main/java/com/alist/api/modules/admin/member/service/AdminMemberService.java @@ -1,12 +1,8 @@ package com.alist.api.modules.admin.member.service; -import com.alist.api.modules.admin.member.dto.AdminMemberSearchDto; -import com.alist.api.modules.admin.member.dto.AdminMemberUpdateDto; +import com.alist.api.modules.admin.member.dto.*; import com.alist.api.modules.admin.member.mapper.AdminMemberMapper; -import com.alist.api.modules.admin.member.vo.AdminMemberDetailVo; -import com.alist.api.modules.admin.member.vo.AdminMemberListVo; -import com.alist.api.modules.admin.member.vo.AdminMemberUpdateVo; -import com.alist.api.modules.admin.member.vo.AdminMemberVo; +import com.alist.api.modules.admin.member.vo.*; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -45,4 +41,31 @@ public class AdminMemberService { result.setMemberUpdated(updateCount > 0); return result; } + + @Transactional + public AdminMemberDormantStatusVo updateAdminMemberDormantStatus(AdminMemberDormantStatusDto adminMemberDormantStatusDto) { + int updateCount = adminMemberMapper.updateAdminMemberDormantStatus(adminMemberDormantStatusDto); + + AdminMemberDormantStatusVo result = new AdminMemberDormantStatusVo(); + result.setDormantStatusUpdated(updateCount > 0); + return result; + } + + @Transactional + public AdminMemberWithdrawStatusVo updateAdminMemberWithdrawStatus(AdminMemberWithdrawStatusDto adminMemberWithdrawStatusDto) { + int updateCount = adminMemberMapper.updateAdminMemberWithdrawStatus(adminMemberWithdrawStatusDto); + + AdminMemberWithdrawStatusVo result = new AdminMemberWithdrawStatusVo(); + result.setWithdrawStatusUpdated(updateCount > 0); + return result; + } + + @Transactional + public AdminMemberDeleteVo deleteAdminMember(AdminMemberDeleteDto adminMemberDeleteDto) { + int updateCount = adminMemberMapper.deleteAdminMember(adminMemberDeleteDto); + + AdminMemberDeleteVo result = new AdminMemberDeleteVo(); + result.setMemberDeleted(updateCount > 0); + return result; + } } diff --git a/src/main/java/com/alist/api/modules/admin/member/vo/AdminMemberDeleteVo.java b/src/main/java/com/alist/api/modules/admin/member/vo/AdminMemberDeleteVo.java new file mode 100644 index 0000000..6b68edb --- /dev/null +++ b/src/main/java/com/alist/api/modules/admin/member/vo/AdminMemberDeleteVo.java @@ -0,0 +1,13 @@ +package com.alist.api.modules.admin.member.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; +import lombok.Setter; + +@Schema(description = "관리자 회원 계정 삭제 응답") +@Getter +@Setter +public class AdminMemberDeleteVo { + @Schema(description = "회원 계정 삭제 여부", example = "true") + private boolean memberDeleted; +} diff --git a/src/main/java/com/alist/api/modules/admin/member/vo/AdminMemberDormantStatusVo.java b/src/main/java/com/alist/api/modules/admin/member/vo/AdminMemberDormantStatusVo.java new file mode 100644 index 0000000..db32d42 --- /dev/null +++ b/src/main/java/com/alist/api/modules/admin/member/vo/AdminMemberDormantStatusVo.java @@ -0,0 +1,13 @@ +package com.alist.api.modules.admin.member.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; +import lombok.Setter; + +@Schema(description = "관리자 회원 휴면 상태 변경 응답") +@Getter +@Setter +public class AdminMemberDormantStatusVo { + @Schema(description = "휴면 상태 변경 여부", example = "true") + private boolean dormantStatusUpdated; +} diff --git a/src/main/java/com/alist/api/modules/admin/member/vo/AdminMemberWithdrawStatusVo.java b/src/main/java/com/alist/api/modules/admin/member/vo/AdminMemberWithdrawStatusVo.java new file mode 100644 index 0000000..a34f4b7 --- /dev/null +++ b/src/main/java/com/alist/api/modules/admin/member/vo/AdminMemberWithdrawStatusVo.java @@ -0,0 +1,13 @@ +package com.alist.api.modules.admin.member.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; +import lombok.Setter; + +@Schema(description = "관리자 회원 탈퇴 상태 변경 응답") +@Getter +@Setter +public class AdminMemberWithdrawStatusVo { + @Schema(description = "탈퇴 상태 변경 여부", example = "true") + private boolean withdrawStatusUpdated; +} diff --git a/src/main/java/com/alist/api/modules/admin/user/AdminUserController.java b/src/main/java/com/alist/api/modules/admin/user/AdminUserController.java index 8e1f4b4..0177c5e 100644 --- a/src/main/java/com/alist/api/modules/admin/user/AdminUserController.java +++ b/src/main/java/com/alist/api/modules/admin/user/AdminUserController.java @@ -15,7 +15,7 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -@Tag(name = "51. ADMIN 사용자 관리", description = "ADMIN 사용자 회원가입 및 관리 API") +@Tag(name = "503. ADMIN 사용자 관리", description = "ADMIN 사용자 회원가입 및 관리 API") @RestController @Slf4j @RequestMapping("/admin/user") diff --git a/src/main/java/com/alist/api/modules/auth/AuthController.java b/src/main/java/com/alist/api/modules/auth/AuthController.java index 3282769..f8afa1a 100644 --- a/src/main/java/com/alist/api/modules/auth/AuthController.java +++ b/src/main/java/com/alist/api/modules/auth/AuthController.java @@ -11,6 +11,7 @@ import com.alist.api.modules.auth.form.ApikeyLoginForm; import com.alist.api.modules.auth.form.TokenForm; import com.alist.api.modules.auth.service.SsoService; import com.alist.api.modules.auth.service.AuthService; +import com.alist.api.modules.auth.vo.TokenVo; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; @@ -23,7 +24,7 @@ import org.springframework.web.bind.annotation.*; import java.util.HashMap; import java.util.Map; -@Tag(name = "02. 엑세스 토큰 발급", description = "엑세스 토큰 발급") +@Tag(name = "102. 엑세스 토큰 발급", description = "엑세스 토큰 발급") @RestController @RequestMapping("/auth") public class AuthController { @@ -62,11 +63,11 @@ public class AuthController { description = "엑세스 토큰이 발급됩니다. (테스트용 실사용X)" ) @PostMapping("/token") - public ResponseEntity> token(@Valid @RequestBody TokenForm tokenForm) { - TokenDto tokenDto = new TokenDto(); - tokenDto.setAccessToken(jwtTokenProvider.createToken(tokenForm.toTokenDto().getId())); + public ResponseEntity> token(@Valid @RequestBody TokenForm tokenForm) { + TokenVo tokenVo = new TokenVo(); + tokenVo.setAccessToken(jwtTokenProvider.createToken(tokenForm.toTokenDto().getId())); - return ApiResponse.entity(tokenDto, ApiResponseCode.CODE_2001, "엑세스 토큰"); + return ApiResponse.entity(tokenVo, ApiResponseCode.CODE_2001, "엑세스 토큰"); } @Operation( diff --git a/src/main/java/com/alist/api/modules/auth/SsoController.java b/src/main/java/com/alist/api/modules/auth/SsoController.java index 8f6140c..50d6bbc 100644 --- a/src/main/java/com/alist/api/modules/auth/SsoController.java +++ b/src/main/java/com/alist/api/modules/auth/SsoController.java @@ -23,7 +23,7 @@ import java.net.URI; import java.util.HashMap; import java.util.Map; -@Tag(name = "01. SSO 인증", description = "공통 로그인, 인가 코드 발급, 코드 교환, 로그아웃 관련 API") +@Tag(name = "101. SSO 인증", description = "공통 로그인, 인가 코드 발급, 코드 교환, 로그아웃 관련 API") @RestController @RequestMapping("/sso") public class SsoController { diff --git a/src/main/java/com/alist/api/modules/auth/vo/TokenVo.java b/src/main/java/com/alist/api/modules/auth/vo/TokenVo.java new file mode 100644 index 0000000..c44128e --- /dev/null +++ b/src/main/java/com/alist/api/modules/auth/vo/TokenVo.java @@ -0,0 +1,22 @@ +package com.alist.api.modules.auth.vo; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; +import lombok.Setter; + +@Schema(description = "테스트용 토큰 발급 응답") +@Getter +@Setter +public class TokenVo { + @Schema(description = "아이디") + private String id; + @Schema(description = "엑세스 토큰") + private String accessToken; + @Schema(description = "리플레시 토큰") + private String refreshToken; + + @JsonIgnore + @Schema(hidden = true) + private int resultCode; +} diff --git a/src/main/java/com/alist/api/modules/auth/CorsAllowController.java b/src/main/java/com/alist/api/modules/cors/CorsAllowController.java similarity index 91% rename from src/main/java/com/alist/api/modules/auth/CorsAllowController.java rename to src/main/java/com/alist/api/modules/cors/CorsAllowController.java index 294a5e6..11e5a1c 100644 --- a/src/main/java/com/alist/api/modules/auth/CorsAllowController.java +++ b/src/main/java/com/alist/api/modules/cors/CorsAllowController.java @@ -1,4 +1,4 @@ -package com.alist.api.modules.auth; +package com.alist.api.modules.cors; import com.alist.api.common.response.ApiResponse; import com.alist.api.common.response.ApiResponseCode; @@ -13,7 +13,7 @@ import org.springframework.web.bind.annotation.RestController; import java.util.List; -@Tag(name = "97. CRS 갱신", description = "테스트 CORS 설정 관리 API") +@Tag(name = "901. CRS 갱신", description = "테스트 CORS 설정 관리 API") @RestController @RequestMapping("/cors") @RequiredArgsConstructor diff --git a/src/main/java/com/alist/api/modules/auth/mapper/CorsMapper.java b/src/main/java/com/alist/api/modules/cors/mapper/CorsMapper.java similarity index 75% rename from src/main/java/com/alist/api/modules/auth/mapper/CorsMapper.java rename to src/main/java/com/alist/api/modules/cors/mapper/CorsMapper.java index 5f70680..a8f02b9 100644 --- a/src/main/java/com/alist/api/modules/auth/mapper/CorsMapper.java +++ b/src/main/java/com/alist/api/modules/cors/mapper/CorsMapper.java @@ -1,6 +1,6 @@ -package com.alist.api.modules.auth.mapper; +package com.alist.api.modules.cors.mapper; -import com.alist.api.modules.auth.vo.CorsOriginVo; +import com.alist.api.modules.cors.vo.CorsOriginVo; import org.apache.ibatis.annotations.Mapper; import java.util.List; diff --git a/src/main/java/com/alist/api/modules/auth/vo/CorsOriginVo.java b/src/main/java/com/alist/api/modules/cors/vo/CorsOriginVo.java similarity index 76% rename from src/main/java/com/alist/api/modules/auth/vo/CorsOriginVo.java rename to src/main/java/com/alist/api/modules/cors/vo/CorsOriginVo.java index 32ad1df..3a009b8 100644 --- a/src/main/java/com/alist/api/modules/auth/vo/CorsOriginVo.java +++ b/src/main/java/com/alist/api/modules/cors/vo/CorsOriginVo.java @@ -1,4 +1,4 @@ -package com.alist.api.modules.auth.vo; +package com.alist.api.modules.cors.vo; import lombok.Getter; import lombok.Setter; diff --git a/src/main/java/com/alist/api/modules/file/FileController.java b/src/main/java/com/alist/api/modules/file/FileController.java index caa2a60..b1cb775 100644 --- a/src/main/java/com/alist/api/modules/file/FileController.java +++ b/src/main/java/com/alist/api/modules/file/FileController.java @@ -17,7 +17,7 @@ import java.util.ArrayList; import java.util.List; @Tag( - name = "98. 파일 업로드", + name = "902. 파일 업로드", description = "DB 기록 없이 파일을 저장하고 uploadPath를 반환하는 단순 업로드 API입니다. SunEditor 이미지는 file-domain URL을 반환합니다." ) @RestController diff --git a/src/main/java/com/alist/api/modules/tusFile/TusFileController.java b/src/main/java/com/alist/api/modules/tusFile/TusFileController.java index 509c344..e37444b 100644 --- a/src/main/java/com/alist/api/modules/tusFile/TusFileController.java +++ b/src/main/java/com/alist/api/modules/tusFile/TusFileController.java @@ -26,7 +26,7 @@ import java.nio.file.Path; @Slf4j @Tag( - name = "99. TUS 업로드" + name = "903. TUS 업로드" , description = "TUS 기반 파일 업로드 초기화, 권한 검증, 상태 조회, 훅 처리, 취소 API" ) @RestController diff --git a/src/main/java/com/alist/api/modules/user/UserController.java b/src/main/java/com/alist/api/modules/user/UserController.java index 1dbcd9a..03c1d7f 100644 --- a/src/main/java/com/alist/api/modules/user/UserController.java +++ b/src/main/java/com/alist/api/modules/user/UserController.java @@ -12,7 +12,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -@Tag(name = "05. 사용자 관리", description = "사용자 회원가입 및 관리 API") +@Tag(name = "103. 사용자 관리", description = "사용자 회원가입 및 관리 API") @Slf4j @RestController @RequestMapping("/user") @@ -126,10 +126,10 @@ public class UserController { description = "로그인한 사용자의 탈퇴 요청 또는 탈퇴 요청 철회를 처리합니다. P: 탈퇴대기, N: 탈퇴요청 철회" ) @PatchMapping("/withdraw") - public ResponseEntity> updateWithdrawStatus( - @Valid @RequestBody UserWithdrawStatusUpdateForm userWithdrawStatusUpdateForm + public ResponseEntity> updateWithdrawStatus( + @Valid @RequestBody UserWithdrawStatusForm userWithdrawStatusForm ) { - UserWithdrawStatusUpdateVo result = userService.updateWithdrawStatus(userWithdrawStatusUpdateForm.toUserWithdrawStatusUpdateDto()); + UserWithdrawStatusVo result = userService.updateWithdrawStatus(userWithdrawStatusForm.toUserWithdrawStatusUpdateDto()); if (!result.isWithdrawStatusUpdated()) { if (result.getResultCode() == 4003 || result.getResultCode() == 4001) { @@ -141,4 +141,25 @@ public class UserController { return ApiResponse.entity(result, ApiResponseCode.CODE_2005, "탈퇴 상태 변경"); } + + @Operation( + summary = "사용자 휴면 상태 변경", + description = "로그인한 사용자의 휴면 처리 또는 휴면 해지를 처리합니다. Y: 휴면 회원, N: 휴면 해지" + ) + @PatchMapping("/dormant") + public ResponseEntity> updateDormantStatus( + @Valid @RequestBody UserDormantStatusForm userDormantStatusForm + ) { + UserDormantStatusVo result = userService.updateDormantStatus(userDormantStatusForm.toUserDormantStatusUpdateDto()); + + if (!result.isDormantStatusUpdated()) { + if (result.getResultCode() == 4003 || result.getResultCode() == 4001) { + return ApiResponse.entity(result, ApiResponseCode.CODE_4001); + } + + return ApiResponse.entity(result, ApiResponseCode.CODE_2003); + } + + return ApiResponse.entity(result, ApiResponseCode.CODE_2005, "휴면 상태 변경"); + } } \ No newline at end of file diff --git a/src/main/java/com/alist/api/modules/user/dto/UserDormantStatusDto.java b/src/main/java/com/alist/api/modules/user/dto/UserDormantStatusDto.java new file mode 100644 index 0000000..00ec939 --- /dev/null +++ b/src/main/java/com/alist/api/modules/user/dto/UserDormantStatusDto.java @@ -0,0 +1,11 @@ +package com.alist.api.modules.user.dto; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class UserDormantStatusDto { + private Integer userTokenIdx; + private String dormantYn; +} diff --git a/src/main/java/com/alist/api/modules/user/dto/UserWithdrawStatusUpdateDto.java b/src/main/java/com/alist/api/modules/user/dto/UserWithdrawStatusDto.java similarity index 80% rename from src/main/java/com/alist/api/modules/user/dto/UserWithdrawStatusUpdateDto.java rename to src/main/java/com/alist/api/modules/user/dto/UserWithdrawStatusDto.java index 9acd217..ce6f2f8 100644 --- a/src/main/java/com/alist/api/modules/user/dto/UserWithdrawStatusUpdateDto.java +++ b/src/main/java/com/alist/api/modules/user/dto/UserWithdrawStatusDto.java @@ -5,7 +5,7 @@ import lombok.Setter; @Getter @Setter -public class UserWithdrawStatusUpdateDto { +public class UserWithdrawStatusDto { private Integer userTokenIdx; private String withdrawStatus; } diff --git a/src/main/java/com/alist/api/modules/user/form/UserDormantStatusForm.java b/src/main/java/com/alist/api/modules/user/form/UserDormantStatusForm.java new file mode 100644 index 0000000..04039cd --- /dev/null +++ b/src/main/java/com/alist/api/modules/user/form/UserDormantStatusForm.java @@ -0,0 +1,28 @@ +package com.alist.api.modules.user.form; + +import com.alist.api.modules.user.dto.UserDormantStatusDto; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import lombok.Getter; +import lombok.Setter; + +@Schema(description = "사용자 휴면 상태 변경 요청 폼") +@Getter +@Setter +public class UserDormantStatusForm { + @Schema( + description = "휴면 여부. Y: 휴면 회원, N: 휴면 해지", + example = "Y", + requiredMode = Schema.RequiredMode.REQUIRED + ) + @NotBlank(message = "휴면 여부는 필수입니다.") + @Pattern(regexp = "^[YN]$", message = "휴면 여부는 Y 또는 N만 가능합니다.") + private String dormantYn; + + public UserDormantStatusDto toUserDormantStatusUpdateDto() { + UserDormantStatusDto userDormantStatusDto = new UserDormantStatusDto(); + userDormantStatusDto.setDormantYn(dormantYn); + return userDormantStatusDto; + } +} diff --git a/src/main/java/com/alist/api/modules/user/form/UserWithdrawStatusUpdateForm.java b/src/main/java/com/alist/api/modules/user/form/UserWithdrawStatusForm.java similarity index 63% rename from src/main/java/com/alist/api/modules/user/form/UserWithdrawStatusUpdateForm.java rename to src/main/java/com/alist/api/modules/user/form/UserWithdrawStatusForm.java index c8e8b67..2d88aa8 100644 --- a/src/main/java/com/alist/api/modules/user/form/UserWithdrawStatusUpdateForm.java +++ b/src/main/java/com/alist/api/modules/user/form/UserWithdrawStatusForm.java @@ -1,6 +1,6 @@ package com.alist.api.modules.user.form; -import com.alist.api.modules.user.dto.UserWithdrawStatusUpdateDto; +import com.alist.api.modules.user.dto.UserWithdrawStatusDto; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Pattern; @@ -10,7 +10,7 @@ import lombok.Setter; @Schema(description = "사용자 탈퇴 상태 변경 요청 폼") @Getter @Setter -public class UserWithdrawStatusUpdateForm { +public class UserWithdrawStatusForm { @Schema( description = "탈퇴 상태. P: 탈퇴대기, N: 탈퇴요청 철회", example = "P", @@ -20,9 +20,9 @@ public class UserWithdrawStatusUpdateForm { @Pattern(regexp = "^[PN]$", message = "탈퇴 상태는 P 또는 N만 가능합니다.") private String withdrawStatus; - public UserWithdrawStatusUpdateDto toUserWithdrawStatusUpdateDto() { - UserWithdrawStatusUpdateDto userWithdrawStatusUpdateDto = new UserWithdrawStatusUpdateDto(); - userWithdrawStatusUpdateDto.setWithdrawStatus(withdrawStatus); - return userWithdrawStatusUpdateDto; + public UserWithdrawStatusDto toUserWithdrawStatusUpdateDto() { + UserWithdrawStatusDto userWithdrawStatusDto = new UserWithdrawStatusDto(); + userWithdrawStatusDto.setWithdrawStatus(withdrawStatus); + return userWithdrawStatusDto; } } diff --git a/src/main/java/com/alist/api/modules/user/mapper/UserMapper.java b/src/main/java/com/alist/api/modules/user/mapper/UserMapper.java index 45f4633..2f630e6 100644 --- a/src/main/java/com/alist/api/modules/user/mapper/UserMapper.java +++ b/src/main/java/com/alist/api/modules/user/mapper/UserMapper.java @@ -18,5 +18,7 @@ public interface UserMapper { int updateUserProfile(UserProfileUpdateDto userProfileUpdateDto); - int updateWithdrawStatus(UserWithdrawStatusUpdateDto userWithdrawStatusUpdateDto); + int updateWithdrawStatus(UserWithdrawStatusDto userWithdrawStatusDto); + + int updateDormantStatus(UserDormantStatusDto userDormantStatusDto); } diff --git a/src/main/java/com/alist/api/modules/user/service/UserService.java b/src/main/java/com/alist/api/modules/user/service/UserService.java index af408d1..29bd023 100644 --- a/src/main/java/com/alist/api/modules/user/service/UserService.java +++ b/src/main/java/com/alist/api/modules/user/service/UserService.java @@ -229,19 +229,19 @@ public class UserService { } @Transactional - public UserWithdrawStatusUpdateVo updateWithdrawStatus(UserWithdrawStatusUpdateDto userWithdrawStatusUpdateDto) { - UserWithdrawStatusUpdateVo result = new UserWithdrawStatusUpdateVo(); + public UserWithdrawStatusVo updateWithdrawStatus(UserWithdrawStatusDto userWithdrawStatusDto) { + UserWithdrawStatusVo result = new UserWithdrawStatusVo(); Integer userTokenIdx = SecurityUtil.getLoginUserTokenIdx(); - userWithdrawStatusUpdateDto.setUserTokenIdx(userTokenIdx); + userWithdrawStatusDto.setUserTokenIdx(userTokenIdx); - if (userTokenIdx == null || userWithdrawStatusUpdateDto.getWithdrawStatus() == null) { + if (userTokenIdx == null || userWithdrawStatusDto.getWithdrawStatus() == null) { result.setWithdrawStatusUpdated(false); result.setResultCode(4003); return result; } - String withdrawStatus = userWithdrawStatusUpdateDto.getWithdrawStatus(); + String withdrawStatus = userWithdrawStatusDto.getWithdrawStatus(); if (!"P".equals(withdrawStatus) && !"N".equals(withdrawStatus)) { result.setWithdrawStatusUpdated(false); @@ -249,11 +249,40 @@ public class UserService { return result; } - int updateCount = userMapper.updateWithdrawStatus(userWithdrawStatusUpdateDto); + int updateCount = userMapper.updateWithdrawStatus(userWithdrawStatusDto); result.setWithdrawStatusUpdated(updateCount > 0); result.setResultCode(updateCount > 0 ? 2005 : 2003); return result; } + + @Transactional + public UserDormantStatusVo updateDormantStatus(UserDormantStatusDto userDormantStatusDto) { + UserDormantStatusVo result = new UserDormantStatusVo(); + + Integer userTokenIdx = SecurityUtil.getLoginUserTokenIdx(); + userDormantStatusDto.setUserTokenIdx(userTokenIdx); + + if (userTokenIdx == null || userDormantStatusDto.getDormantYn() == null) { + result.setDormantStatusUpdated(false); + result.setResultCode(4003); + return result; + } + + String dormantYn = userDormantStatusDto.getDormantYn(); + + if (!"Y".equals(dormantYn) && !"N".equals(dormantYn)) { + result.setDormantStatusUpdated(false); + result.setResultCode(4001); + return result; + } + + int updateCount = userMapper.updateDormantStatus(userDormantStatusDto); + + result.setDormantStatusUpdated(updateCount > 0); + result.setResultCode(updateCount > 0 ? 2005 : 2003); + + return result; + } } diff --git a/src/main/java/com/alist/api/modules/user/vo/UserDormantStatusVo.java b/src/main/java/com/alist/api/modules/user/vo/UserDormantStatusVo.java new file mode 100644 index 0000000..2f2c37b --- /dev/null +++ b/src/main/java/com/alist/api/modules/user/vo/UserDormantStatusVo.java @@ -0,0 +1,18 @@ +package com.alist.api.modules.user.vo; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; +import lombok.Setter; + +@Schema(description = "사용자 휴면 상태 변경 응답") +@Getter +@Setter +public class UserDormantStatusVo { + @Schema(description = "휴면 상태 변경 여부", example = "true") + private boolean dormantStatusUpdated; + + @JsonIgnore + @Schema(hidden = true) + private Integer resultCode; +} diff --git a/src/main/java/com/alist/api/modules/user/vo/UserWithdrawStatusUpdateVo.java b/src/main/java/com/alist/api/modules/user/vo/UserWithdrawStatusVo.java similarity index 91% rename from src/main/java/com/alist/api/modules/user/vo/UserWithdrawStatusUpdateVo.java rename to src/main/java/com/alist/api/modules/user/vo/UserWithdrawStatusVo.java index 633db4a..bee4e08 100644 --- a/src/main/java/com/alist/api/modules/user/vo/UserWithdrawStatusUpdateVo.java +++ b/src/main/java/com/alist/api/modules/user/vo/UserWithdrawStatusVo.java @@ -8,7 +8,7 @@ import lombok.Setter; @Schema(description = "사용자 탈퇴 상태 변경 응답") @Getter @Setter -public class UserWithdrawStatusUpdateVo { +public class UserWithdrawStatusVo { @Schema(description = "탈퇴 상태 변경 여부", example = "true") private boolean withdrawStatusUpdated; diff --git a/src/main/resources/mapper/User/UserMapper.xml b/src/main/resources/mapper/User/UserMapper.xml index 92f2b7c..4142c49 100644 --- a/src/main/resources/mapper/User/UserMapper.xml +++ b/src/main/resources/mapper/User/UserMapper.xml @@ -41,11 +41,11 @@ INNER JOIN ALISTLMS.test_user_token ATUT ON ATUT.user_idx = ATU.user_idx SET ATU.withdraw_status = #{withdrawStatus} , ATU.withdraw_at = - CASE - WHEN #{withdrawStatus} = 'P' THEN now() - WHEN #{withdrawStatus} = 'N' THEN NULL - ELSE ATU.withdraw_at - END + CASE + WHEN #{withdrawStatus} = 'P' THEN now() + WHEN #{withdrawStatus} = 'N' THEN NULL + ELSE ATU.withdraw_at + END , ATU.update_at = now() WHERE ATUT.user_token_idx = #{userTokenIdx} AND ATU.del_yn = 1 @@ -69,4 +69,21 @@ AND ATU.del_yn = 1 + + /*UserMapper.updateDormantStatus*/ + UPDATE ALISTLMS.test_user ATU + INNER JOIN ALISTLMS.test_user_token ATUT ON ATUT.user_idx = ATU.user_idx + SET ATU.dormant_yn = #{dormantYn} + , ATU.dormant_at = + CASE + WHEN #{dormantYn} = 'Y' THEN now() + WHEN #{dormantYn} = 'N' THEN NULL + ELSE ATU.dormant_at + END + , ATU.update_at = now() + WHERE ATUT.user_token_idx = #{userTokenIdx} + AND ATU.del_yn = 1 + AND ATU.withdraw_status != 'Y' + + diff --git a/src/main/resources/mapper/admin/member/AdminMemberMapper.xml b/src/main/resources/mapper/admin/member/AdminMemberMapper.xml index caaa600..d949f81 100644 --- a/src/main/resources/mapper/admin/member/AdminMemberMapper.xml +++ b/src/main/resources/mapper/admin/member/AdminMemberMapper.xml @@ -48,6 +48,44 @@ AND ATU.del_yn = 1 + + /*AdminMemberMapper.updateAdminMemberDormantStatus*/ + UPDATE ALISTLMS.test_user + SET dormant_yn = #{dormantYn} + , dormant_at = + CASE + WHEN #{dormantYn} = 'Y' THEN now() + WHEN #{dormantYn} = 'N' THEN NULL + ELSE dormant_at + END + , update_at = now() + WHERE user_idx = #{userIdx} + AND del_yn = 1 + + + + /*AdminMemberMapper.updateAdminMemberWithdrawStatus*/ + UPDATE ALISTLMS.test_user + SET withdraw_status = #{withdrawStatus} + , withdraw_at = + CASE + WHEN #{withdrawStatus} IN ('P', 'Y') THEN now() + WHEN #{withdrawStatus} = 'N' THEN NULL + ELSE withdraw_at + END + , update_at = now() + WHERE user_idx = #{userIdx} + AND del_yn = 1 + + + /*AdminMemberMapper.deleteAdminMember*/ + UPDATE ALISTLMS.test_user + SET del_yn = 0 + , update_at = now() + WHERE user_idx = #{userIdx} + AND del_yn = 1 + + + +