[api] 관리자 휴면/탈퇴/삭제 추가

This commit is contained in:
2026-05-20 17:48:07 +09:00
parent b58a321482
commit ada6dfc7b4
43 changed files with 642 additions and 82 deletions
+32 -3
View File
@@ -10,6 +10,8 @@
- 단일 캠퍼스형은 생성 즉시 운영 가능하다. - 단일 캠퍼스형은 생성 즉시 운영 가능하다.
- 복합 캠퍼스형은 교사 생성 후 관리자 승인 절차를 거친 뒤 운영 가능하다. - 복합 캠퍼스형은 교사 생성 후 관리자 승인 절차를 거친 뒤 운영 가능하다.
- 관리자(Back Office)도 캠퍼스를 직접 생성할 수 있다. - 관리자(Back Office)도 캠퍼스를 직접 생성할 수 있다.
- 관리자 생성 시 캠퍼스 마스터 역할의 교사는 회원 가입이 완료된 상태여야 하며, 필요하면 관리자 화면에서 회원 등록을 병행한다.
- 관리자 생성 시 등록 정보는 캠퍼스 명칭, 직접 유입 개인화 도메인, 로고, 캠퍼스 사용 교재를 포함한다.
## 캠퍼스 유형 ## 캠퍼스 유형
- 단일 캠퍼스형: 1인 교사 공부방 또는 단일 학원형 - 단일 캠퍼스형: 1인 교사 공부방 또는 단일 학원형
@@ -31,6 +33,9 @@
- 동일 학생이어도 교재가 바뀌면 새 강좌 생성 - 동일 학생이어도 교재가 바뀌면 새 강좌 생성
- 동일 교재여도 학생 구성이 바뀌면 새 강좌 생성 - 동일 교재여도 학생 구성이 바뀌면 새 강좌 생성
- 강좌 복사 기능을 제공하며 수업 중 강좌도 복사 가능하다 - 강좌 복사 기능을 제공하며 수업 중 강좌도 복사 가능하다
- 동일 캠퍼스 소속 교사는 본인이 개설한 강좌 외에 타 교사의 강좌도 검색·복사할 수 있다
- 동일 강좌 ID 안에서는 교재 교체, 교재 추가, 전체 학생 변경이 불가하며, 교재 또는 학생 구성이 크게 바뀌면 신규 강좌 ID를 생성하는 흐름으로 본다
- 개별 학생/수강생 상태 변경과 운영 중 학생 추가 초대는 가능하다
- Live 상태 강좌 수는 제한될 수 있고 종료 강좌를 포함한 총 강좌 수는 무제한이다 - Live 상태 강좌 수는 제한될 수 있고 종료 강좌를 포함한 총 강좌 수는 무제한이다
## 강좌 생명주기 ## 강좌 생명주기
@@ -45,6 +50,17 @@
- 운영 중 학생 상태를 이용 중지 또는 수강 종료로 변경할 수 있다 - 운영 중 학생 상태를 이용 중지 또는 수강 종료로 변경할 수 있다
- 학생이 운영 중간에 가입하면 이전 과제/평가도 미수행 상태로 제시한다 - 학생이 운영 중간에 가입하면 이전 과제/평가도 미수행 상태로 제시한다
- 상태 변경 시 학생과 학부모에게 알림이 발송된다 - 상태 변경 시 학생과 학부모에게 알림이 발송된다
- 수업 코스는 교재 Unit 기반으로 정렬하고, 수업 자료, 온라인 콘텐츠, 과제, 평가를 선별 연동한다
- 수업 일정과 과제/평가 출제 일정은 타임라인 기반으로 관리한다
## 캠퍼스/강좌 상태 관리
- 온라인 캠퍼스, 가맹 캠퍼스, 클래스, 강좌, 회원의 캠퍼스 가입 상태는 별도 상태 체계로 관리한다
- 온라인 캠퍼스에는 승인 대기, 운영 중, 운영 일시 정지, 운영 종료 상태가 있다
- 클래스/가맹 캠퍼스에는 운영 중, 운영 일시 정지, 운영 종료 상태가 있다
- 강좌에는 강의 준비 중, 강의 중, 강의 일시 정지, 강의 종료 상태가 있다
- 교사 가입 상태에는 정상, 권한 일시 중지, 종료(탈퇴)가 있다
- 학생/수강생의 강좌 가입 상태에는 수강 대기 중, 수강 중, 권한 일시 중지, 수강 종료, 종료(탈퇴)가 있다
- 상위 조직이 일시 정지되면 하위 조직 상태에도 반영된다
## 교사 업무 흐름 ## 교사 업무 흐름
- 캠퍼스 생성 또는 초대 가입 - 캠퍼스 생성 또는 초대 가입
@@ -58,7 +74,9 @@
## 학생/수강생 학습 흐름 ## 학생/수강생 학습 흐름
- 초대 URL/초대 코드 기반 강좌 가입 또는 학생 회원 자기주도학습 시작 - 초대 URL/초대 코드 기반 강좌 가입 또는 학생 회원 자기주도학습 시작
- 예습 패키지 수행: 음원, 동영상, 단어 학습, 온라인 플래시 카드, 워드 리스트
- 수업 참여 - 수업 참여
- 온라인 학습 콘텐츠 수행
- 과제 수행 및 제출 - 과제 수행 및 제출
- 평가 수행 및 제출 - 평가 수행 및 제출
- 복습 및 자기주도학습 - 복습 및 자기주도학습
@@ -73,11 +91,22 @@
## SMS 정책 ## SMS 정책
- 캠퍼스 초대, 학습 알림, 보고서 발송 등에 SMS를 병행 사용할 수 있다 - 캠퍼스 초대, 학습 알림, 보고서 발송 등에 SMS를 병행 사용할 수 있다
- SMS 수량은 캠퍼스 마스터가 구매/충전하는 구조 - 초대 시 이메일 발송은 필수, 휴대폰 SMS 발송은 선택으로 본
- 발송 성공 시 수량이 차감된 - SMS 발송은 개발 후 서비스 오픈 시 프로세스 Hidden 처리될 수 있
- 백오피스에서 수량 충전 기능을 제공한다
## 초대 코드 정책
- 초대 코드는 캠퍼스에 초대되는 회원의 가입 일치 여부를 판단하고, 캠퍼스별 교재 단위 이용자 수를 관리하는 수단이다
- 교사용 초대 코드와 학생용 초대 코드는 생성·사용 로직을 이원화한다
- 학생 초대 코드는 교재별 수량 관리 대상이며, 강좌에 학생을 초대할 때 잔여 수량 내에서 생성한다
- 학생 초대 코드 상태는 미사용, 발송, 발송 실패, 재발송, 강좌 가입, 수업 시작으로 관리한다
- 초대 코드 수량은 전체 수량, 대기 수량, 사용 수량, 잔여 수량으로 표현한다
- 대기 수량은 발송/재발송 상태의 합계이고, 사용 수량은 강좌 가입/수업 시작 상태의 합계다
- 학생이 초대 코드를 분실하면 교사가 동일 초대 코드를 재발송할 수 있어야 한다
- 강좌 생성 후 학생별 초대 코드 목록은 Excel 목록형 또는 PDF 인쇄형으로 다운로드할 수 있다
- 해외 임대 서비스에서는 유통업체 또는 관리자 화면을 통해 캠퍼스별·교재별 초대 코드 수량을 생성/증감 관리한다
## 개발 해석 포인트 ## 개발 해석 포인트
- 캠퍼스, 클래스, 강좌는 이름이 비슷하지만 역할이 다르므로 테이블/DTO/응답 모델을 명확히 분리해야 한다 - 캠퍼스, 클래스, 강좌는 이름이 비슷하지만 역할이 다르므로 테이블/DTO/응답 모델을 명확히 분리해야 한다
- 강좌는 실제 학습 데이터와 과제/평가의 기준 단위이므로, API 설계 시 강좌 식별자를 중심으로 묶는 것이 자연스럽다 - 강좌는 실제 학습 데이터와 과제/평가의 기준 단위이므로, API 설계 시 강좌 식별자를 중심으로 묶는 것이 자연스럽다
- 초대, 가입, 탈퇴, 종료, 상태 변경은 회원 상태와 강좌 소속 상태를 구분해 처리해야 한다 - 초대, 가입, 탈퇴, 종료, 상태 변경은 회원 상태와 강좌 소속 상태를 구분해 처리해야 한다
- 초대 코드는 단순 인증 토큰이 아니라 교재별 이용 수량과 연결된 운영 데이터이므로 상태 이력과 수량 집계를 함께 고려해야 한다
+63 -4
View File
@@ -1,10 +1,12 @@
# 정책서: 콘텐츠/LMS # 정책서: 콘텐츠/LMS
## 콘텐츠 범주 ## 콘텐츠 범주
- 수업 자료: PPT, Lesson Plan, Syllabus, Scope & Sequence, Teacher Guide, Script, Worksheet, Word Test 등 - 교사 전용 자료: Lesson Plan, Syllabus, Scope & Sequence, 강의 계획서, Teacher Guide, 수업 PPT, 정답 및 해설, 워크시트
- 가 자료: Test Sheet, 단원 평가, 중간/최종 평가, 평가 문항, 내신 수행평가 자료 - 교재 부가 자료: 본문 스크립트, 한글 해석/번역, 단어 리스트, 플래시 카드
- 멀티미디어: MP3, MP4, 온라인 PPT, 온라인 클래스, 플래시 카드 - 평가 자료: Word Test, Test Sheet, 단원 평가, 중간/최종 평가, 온라인 평가, 온라인 OMR 평가지
- 솔루션형 콘텐츠: 교사용 e-Book, 온라인 평가, 온라인 OMR 평가, Voca*List, 클래스 카드, 레벨 테스트 - 멀티미디어: MP3, MP4
- 온라인 학습 자료: 온라인 학습 콘텐츠, 온라인 플래시 카드, 온라인 클래스, 클래스 카드
- 솔루션형 콘텐츠: 교사용 e-Book, 온라인 수업 PPT, 문항 출제 마법사, 레벨 테스트, 내신 수행 평가 자료실, Voca*List
- 교사 보유 자료: PDF, MP3, MP4 업로드 자료 - 교사 보유 자료: PDF, MP3, MP4 업로드 자료
## 콘텐츠 생산과 활용 ## 콘텐츠 생산과 활용
@@ -12,17 +14,35 @@
- 교사는 수업 뷰어에서 문서형 자료, URL, 온라인 콘텐츠, 과제, 평가를 통합적으로 사용한다 - 교사는 수업 뷰어에서 문서형 자료, URL, 온라인 콘텐츠, 과제, 평가를 통합적으로 사용한다
- 학생은 온라인 학습 콘텐츠 뷰어와 과제/평가 뷰어에서 콘텐츠를 수행한다 - 학생은 온라인 학습 콘텐츠 뷰어와 과제/평가 뷰어에서 콘텐츠를 수행한다
- 평가지는 별도 생성 솔루션(Test Generator)으로 출제, 편집, 출력한다 - 평가지는 별도 생성 솔루션(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 등 학습 레벨 메타데이터를 가진다 - 콘텐츠는 교재, 시리즈, 레벨, 학년, 난이도, CEFR, Lexile, AR 등 학습 레벨 메타데이터를 가진다
- 교재 레벨 메타는 백오피스/CMS에서 별도로 관리한다 - 교재 레벨 메타는 백오피스/CMS에서 별도로 관리한다
- 메타데이터는 검색, 교재 노출, 학습 추천, 보고서 분류에 영향을 줄 수 있다 - 메타데이터는 검색, 교재 노출, 학습 추천, 보고서 분류에 영향을 줄 수 있다
- 교재-콘텐츠 관리 위계는 교재 카테고리(SKILL), 시리즈, 레벨, 유닛(Unit), 레슨(Lesson), 콘텐츠 분류 카테고리, 콘텐츠로 구성한다
- 부속 교재는 Workbook, Teacher's Book, 책 속의 책 등 물리적으로 분책된 교재를 관리하며, ISBN이 다른 경우 연관 교재 세트 개념으로 구성할 수 있다
- 유닛에는 권장 수업 차시 수를 둘 수 있으며, 기본 커리큘럼과 수업 코스 설계에 활용한다
- 개별 파일 기준 속성은 콘텐츠 타입, 파일 포맷, 교재 범위, 작성 언어, 제공 권한 범위를 포함한다
- 문항/활동형 콘텐츠는 개별 활동 또는 개별 문항 단위로 SKILL, Sub SKILL, 난이도, 활동 유형, 문항 유형, 구조 메타를 적용한다
## 콘텐츠 접근 권한 ## 콘텐츠 접근 권한
- 교사: 대부분의 수업 자료와 교사용 자료, 수업 뷰어 기능 접근 가능 - 교사: 대부분의 수업 자료와 교사용 자료, 수업 뷰어 기능 접근 가능
- 학생: 제한된 수업 자료와 멀티미디어, 온라인 학습 콘텐츠 접근 가능 - 학생: 제한된 수업 자료와 멀티미디어, 온라인 학습 콘텐츠 접근 가능
- 수강생: 강좌 기반 학습 활동 위주로 제한된 권한을 가진다 - 수강생: 강좌 기반 학습 활동 위주로 제한된 권한을 가진다
- 학생용 I/F에서는 기본 권한 자료와 교사가 추가 허용한 자료를 함께 노출할 수 있다 - 학생용 I/F에서는 기본 권한 자료와 교사가 추가 허용한 자료를 함께 노출할 수 있다
- 접근 권한 표기는 `●`, `▲`, `☆`, `X` 기준으로 해석한다
- `●`: 해당 회원 유형이 기본 접근/사용 가능
- `▲`: 수강생이 초대받은 강좌 교재 범위 안에서 접근/사용 가능
- `☆`: 학생/수강생이 강좌 소속이고 교사가 수업 설계 중 허용 또는 전송한 경우 접근/사용 가능
- `X`: 해당 인터페이스에서 직접 제공하지 않거나 구버전/별도 경로로 취급
- 학생은 본문 스크립트, 한글 해석/번역, 단어 리스트, 멀티미디어, 온라인 학습 콘텐츠, 온라인 플래시 카드 등에 기본 접근할 수 있다
- 수강생은 초대받은 강좌 교재 범위 안에서 본문 스크립트, 번역, 단어 리스트, 온라인 학습, 온라인 플래시 카드 등에 접근한다
- 교사용 자료와 평가 자료 상당수는 교사가 공유하거나 강좌 과제/평가로 전송한 경우에만 학생/수강생에게 노출된다
## 주요 솔루션과 기능 ## 주요 솔루션과 기능
- 교수자용 수업 뷰어 - 교수자용 수업 뷰어
@@ -38,13 +58,29 @@
- 문항 검색, 문항 수/유형 설정, 사용자 문항 추가, PDF 생성, 출력 - 문항 검색, 문항 수/유형 설정, 사용자 문항 추가, PDF 생성, 출력
- 학습 분석 보고서 - 학습 분석 보고서
- 레벨테스트, 교재별, Unit별 보고서 생성 - 레벨테스트, 교재별, 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별 성취도 - SKILL별 성취도
- Sub SKILL별 성취도
- 단위 학습 성취도
- 성취도 변화 추이 - 성취도 변화 추이
- 산출물 관리 - 산출물 관리
- 오답 노트 - 오답 노트
- 학습 산출물 데이터 - 학습 산출물 데이터
- 유사 문항 학습 - 유사 문항 학습
- 포트폴리오 - 포트폴리오
- 정성 관리
- 교사 평가 텍스트
- 평가 항목별 5점 척도 평가
- SKILL별 정성 평가
- 학습 태도 및 성장도 평가
## 학습 관리 데이터 구조
- 정량적 학습 관리는 학습 뷰어와 LMS DB 데이터로 자동 축적한다
- 정성적 학습 관리는 교사가 대시보드 통계나 과제 산출물을 보고 입력한 평가로 관리한다
- 학습 수행 데이터는 단위 학습 일자, 시작 시간, 종료 시간, 수행 여부, 학습 항목, 학습 시간, 수행률, 빈도 등을 포함한다
- 학습 성취 데이터는 문항 제출 답안, 정오 판정, 파일 산출물, 단위 성취도, SKILL별 성취도, 문항 유형별 성취도, 취약 SKILL 등을 포함한다
- 학생 개인 기록 외에 강좌, 클래스, 캠퍼스 단위 평균과 순위 데이터를 생성할 수 있다
- 레벨테스트 및 타 교재 학습 이력에 기반해 성취도 추이와 학습 포트폴리오를 구성한다
- 학습 관리 구조는 교재, Unit, Lesson, 학습 모듈, Activity/Test 문항으로 이어진다
- 단일형 콘텐츠는 온라인 평가/과제처럼 하나의 콘텐츠 수행으로 학습이 완료되는 단위다
- 과정형 콘텐츠는 온라인 학습/온라인 클래스처럼 복수 모듈이 순차 학습 Flow를 이루는 콘텐츠다
## 시각화 및 보고 ## 시각화 및 보고
- 대시보드 I/F와 학습 분석 보고서가 주요 표현 수단이다 - 대시보드 I/F와 학습 분석 보고서가 주요 표현 수단이다
- 학생별/강좌별 현황, 성취도, 수행률, 레벨테스트 결과 시각화가 중요하다 - 학생별/강좌별 현황, 성취도, 수행률, 레벨테스트 결과 시각화가 중요하다
- 교사 화면에서는 강좌별 관리와 학생별 관리가 모두 필요하다 - 교사 화면에서는 강좌별 관리와 학생별 관리가 모두 필요하다
- 레벨테스트 분석 보고서는 총점, SKILL/Sub SKILL별 성취율, 강점/취약 스킬, 추천 교재, 문항별 채점표를 포함한다
- 단원/중간/기말 평가 보고서는 평가 성취도, 문항별 채점표, SKILL별 성취도, 학습 시간 그래프, 온라인 학습/과제 성취도 추이를 포함한다
- 교재 학습 분석 보고서는 강의 종료 또는 교재 학습 완료 시 생성하며, 학습 수행 분석, 학습 성취도 분석, 교사 정성 평가를 포함한다
## 개발 해석 포인트 ## 개발 해석 포인트
- 콘텐츠 자체 메타데이터와 학습 수행 데이터는 분리 저장하는 편이 좋다 - 콘텐츠 자체 메타데이터와 학습 수행 데이터는 분리 저장하는 편이 좋다
- 학습 관리 영역은 단순 진도율이 아니라 성취도·산출물·포트폴리오까지 포함하므로 조회 API가 넓어질 가능성이 높다 - 학습 관리 영역은 단순 진도율이 아니라 성취도·산출물·포트폴리오까지 포함하므로 조회 API가 넓어질 가능성이 높다
- 학생용 권한은 `회원 유형`, `강좌 소속 여부`, `교사 공유 여부`의 조합으로 판단해야 한다 - 학생용 권한은 `회원 유형`, `강좌 소속 여부`, `교사 공유 여부`의 조합으로 판단해야 한다
- 콘텐츠 접근 권한은 콘텐츠 타입별로 다르므로 공통 boolean 하나보다 콘텐츠-회원유형-강좌소속-공유여부 조합 정책으로 모델링하는 편이 안전하다
- 학습 데이터는 개인, 강좌, 클래스, 캠퍼스 단위 집계가 필요하므로 원천 이벤트/결과 데이터와 집계 데이터를 분리하는 설계가 유리하다
+3 -2
View File
@@ -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) - [서비스 개요](overview.md)
@@ -12,7 +12,8 @@
## 빠른 요약 ## 빠른 요약
- 플랫폼은 메인 서비스, 캠퍼스 메인, 교사용 캠퍼스, 학생용 캠퍼스, 백오피스/CMS로 분리된다. - 플랫폼은 메인 서비스, 캠퍼스 메인, 교사용 캠퍼스, 학생용 캠퍼스, 백오피스/CMS로 분리된다.
- 핵심 도메인은 회원, 캠퍼스, 클래스, 강좌, 콘텐츠, 과제/평가, 학습 관리다. - 핵심 도메인은 회원, 캠퍼스, 클래스, 강좌, 콘텐츠, 과제/평가, 학습 관리다.
- 정책서상 주요 제약은 회원 유형별 권한 차등, 캠퍼스 소속 제한, 강좌-교재 1:1 관계, 캠퍼스 개인화 도메인, 레거시 병행 운영이다. - 정책서상 주요 제약은 회원 유형별 권한 차등, 캠퍼스 소속 제한, 강좌-교재 1:1 관계, 초대 코드 상태/수량 관리, 캠퍼스 개인화 도메인, 레거시 병행 운영이다.
- `Ver 0.32`에서는 콘텐츠 생산/활용 구조, 콘텐츠 접근 권한, 학습 관리 데이터, 학습 분석 보고서, 인터페이스 메뉴 범위가 더 구체화되었다.
- 실제 개발 판단에서는 이 문서 묶음을 기능 정의서보다 `도메인 정책서`로 취급하는 것이 적합하다. - 실제 개발 판단에서는 이 문서 묶음을 기능 정의서보다 `도메인 정책서`로 취급하는 것이 적합하다.
## 개발 시 우선 참고 포인트 ## 개발 시 우선 참고 포인트
+34 -8
View File
@@ -8,26 +8,35 @@
- 캠퍼스 교사용(`class.a-campus.co.kr`) - 캠퍼스 교사용(`class.a-campus.co.kr`)
- 강좌/학생/학습/알림/수업준비/캠퍼스 관리 중심 - 강좌/학생/학습/알림/수업준비/캠퍼스 관리 중심
- 캠퍼스 학생용(`student.a-campus.co.kr`) - 캠퍼스 학생용(`student.a-campus.co.kr`)
- 오늘 학습, 셀프 스터디, 나의 학습, 알림방 중심 - 나의 학습, 과제·평가, 자습하기, 복습하기, 학습 통계·리포트, 소통방 중심
- 백오피스/CMS(`admin.alist.co.kr`) - 백오피스/CMS(`admin.alist.co.kr`)
- 운영 관리, 교재/콘텐츠/CMS, 파트너, 홍보, 문의, 통계 관리 - 운영 관리, 교재/콘텐츠/CMS, 파트너, 홍보, 문의, 통계 관리
## 교사용 I/F 핵심 메뉴 ## 교사용 I/F 핵심 메뉴
- 강좌 관리 - 수업(강좌) 관리
- 학생 관리 - 학생·학습 관리
- 학습 관리 - 교재 수업 준비
- 소통방 - 소통방: 알림장, 1:1 메시지
- 수업 준비
- 마이페이지 - 마이페이지
- 역할에 따라 가맹 캠퍼스 관리, 캠퍼스 관리, 공지 관리 등 추가 메뉴 노출 - 역할에 따라 가맹 캠퍼스 관리, 캠퍼스 관리, 공지 관리 등 추가 메뉴 노출
- 캠퍼스 마스터 전용 메뉴와 가맹 캠퍼스장 전용 메뉴는 타 역할에서 Hidden 처리한다
- 교사용 I/F는 강좌 생성, 강좌 복사, 학생 초대, 수업 요일 지정, 수업 하기, 과제 출제, 평가 출제, 레벨테스트 보내기 등을 CTA로 제공한다
- 학생·학습 관리는 학습 기간, 누적/일자별 학습 시간, 예습 패키지 수행률, 온라인 학습 수행률/성취도, 과제/평가 수행률, 학습 분석 보고서 생성을 포함한다
## 학생용 I/F 핵심 메뉴 ## 학생용 I/F 핵심 메뉴
- 오늘 학습
- 셀프 스터디
- 나의 학습 - 나의 학습
- 과제·평가
- 자습하기
- 더 공부하기
- 복습하기
- 학습 통계·리포트
- 알림방 - 알림방
- 마이페이지 - 마이페이지
- 학생만 `학습 교재 추가`, `교재 추가` 같은 자기주도학습 확장 기능 사용 가능 - 학생만 `학습 교재 추가`, `교재 추가` 같은 자기주도학습 확장 기능 사용 가능
- 수강생은 학생 정회원 전용 자기주도학습 메뉴가 Hidden 처리된다
- 학생용 I/F는 오늘 학습, 수업 캘린더, 내 학습 시간 기록, 온라인 학습 결과, 공지·알림, 1:1 메시지를 Dashboard로 제공한다
- 과제·평가 메뉴는 수업(강좌) 목록, 과제 목록, 평가 목록, 레벨테스트 목록과 과제/평가/레벨테스트 수행 CTA를 포함한다
- 학습 통계·리포트는 교재별 학습 기간, 총 학습 시간, 일자별 학습 시간, 온라인 학습/과제/평가 점수, SKILL별 평균, 교사 정성 평가, 동일 교재 학습자 비교를 포함한다
## 백오피스/CMS 핵심 메뉴 ## 백오피스/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는 제공하지 않고, 캠퍼스 마스터가 생성한 온라인 캠퍼스 교재로 등록할 수 있게 한다
- 체험하기는 선생님 수업 뷰어, 과제 출제, 평가 출제, 학생 온라인 학습 체험을 제공한다
- 서비스 문의하기는 비회원도 작성할 수 있으며, 비로그인 작성 시 임시 비밀번호, 이름, 이메일을 입력한다
## 도메인 정책 ## 도메인 정책
- 메인 서비스와 캠퍼스 서비스 도메인을 분리한다 - 메인 서비스와 캠퍼스 서비스 도메인을 분리한다
+12
View File
@@ -19,6 +19,7 @@
- 학생 가입: 서비스 직접 방문, 부모 동의 인증 포함 휴대폰 인증, 이메일 인증 - 학생 가입: 서비스 직접 방문, 부모 동의 인증 포함 휴대폰 인증, 이메일 인증
- 수강생 가입: 캠퍼스 초대 URL + 초대 코드 기반 가입, 학생용 I/F 접근 계정 생성 - 수강생 가입: 캠퍼스 초대 URL + 초대 코드 기반 가입, 학생용 I/F 접근 계정 생성
- 관리자 계정: 개발 또는 마스터 관리자 등록 방식 - 관리자 계정: 개발 또는 마스터 관리자 등록 방식
- 해외 임대 서비스는 휴대폰 인증 프로세스를 생략하고 이메일 인증을 필수 프로세스로 둘 수 있다
## 인터페이스 접근 권한 ## 인터페이스 접근 권한
- 관리자: 백오피스/CMS 접근, 메뉴별 권한 차등 - 관리자: 백오피스/CMS 접근, 메뉴별 권한 차등
@@ -27,6 +28,14 @@
- 수강생: 학생용 I/F 중심 접근, 메인 서비스 권한 없음 또는 제한 - 수강생: 학생용 I/F 중심 접근, 메인 서비스 권한 없음 또는 제한
- 학생/수강생은 같은 학생용 I/F를 사용하지만 권한 범위는 다르다 - 학생/수강생은 같은 학생용 I/F를 사용하지만 권한 범위는 다르다
## 회원 유형별 서비스 활용
- 교사: A*List 전체 자료 접근, 다운로드/Web Viewing, 교사용 eBook, 온라인 PPT, 문항 출제 마법사, 레벨테스트, 세미나 등 교사 지원 서비스 이용
- 교사: A★캠퍼스에서 캠퍼스 생성 또는 초대 가입 후 캠퍼스 운영, 강좌 생성, 수업 코스 설계, 과제/평가 출제, 학생별 학습 관리 수행
- 학생: A*List의 학생 접근 허용 자료 Viewing, 멀티미디어, 일부 온라인 콘텐츠 이용
- 학생: `student.a-campus.co.kr/home`에서 교재 선택 기반 자기주도학습 가능
- 학생: 강좌에 소속되면 예습 패키지, 온라인 학습, 과제/평가, 복습, 레벨테스트 응시 등 강좌 기반 학습 활동 가능
- 수강생: 초대받은 강좌 기반 학습 활동만 이용 가능하며, 학생처럼 교재를 자유 선택하는 자기주도학습 권한은 제한된다
## 캠퍼스 소속 정책 ## 캠퍼스 소속 정책
- 교사 회원은 온라인 캠퍼스 역할과 무관하게 Live 상태에서 단수의 캠퍼스에만 소속 가능 - 교사 회원은 온라인 캠퍼스 역할과 무관하게 Live 상태에서 단수의 캠퍼스에만 소속 가능
- 수강생은 Live 상태에서 단수의 캠퍼스에만 소속 가능 - 수강생은 Live 상태에서 단수의 캠퍼스에만 소속 가능
@@ -61,7 +70,10 @@
## 회원 전환 및 ID 병합 ## 회원 전환 및 ID 병합
- 수강생은 모바일 본인 인증 또는 부모 인증 후 학생으로 전환 가능 - 수강생은 모바일 본인 인증 또는 부모 인증 후 학생으로 전환 가능
- 전환 시 클래스/강좌 정보와 학습 이력은 유지한다 - 전환 시 클래스/강좌 정보와 학습 이력은 유지한다
- 해외 임대 및 국내 제휴 등 분리 독립 시스템 이용자는 수강생에서 학생으로 전환할 수 없는 예외가 있을 수 있다
- 학생/수강생은 ID 병합 기능 제공 대상이다 - 학생/수강생은 ID 병합 기능 제공 대상이다
- ID 병합 시 병합한 ID의 강좌 정보와 학습 이력을 승계하고, 병합 대상 ID의 상태값을 변경한다
- ID 병합 대상 확인에는 비밀번호, 이름, 휴대폰 인증 또는 이메일 인증 정보가 사용된다
- 교사는 ID 병합 대상이 아니다 - 교사는 ID 병합 대상이 아니다
## 레거시 회원 정책 ## 레거시 회원 정책
+4 -2
View File
@@ -10,6 +10,7 @@
- A★캠퍼스: 교사·학생이 실제 교수·학습 활동을 수행하는 온라인 캠퍼스 - A★캠퍼스: 교사·학생이 실제 교수·학습 활동을 수행하는 온라인 캠퍼스
- 백오피스/CMS: 플랫폼 운영, 콘텐츠 관리, 파트너/교재/회원/통계 관리 - 백오피스/CMS: 플랫폼 운영, 콘텐츠 관리, 파트너/교재/회원/통계 관리
- 해외 확장: 영어 홍보 사이트, 해외 임대(이식) 서비스, 제휴/유통업체 운영 - 해외 확장: 영어 홍보 사이트, 해외 임대(이식) 서비스, 제휴/유통업체 운영
- 통합 솔루션: 교사용 통합 수업 뷰어, 학생용 통합 과제 뷰어, 온라인 학습 솔루션, 학습 분석 보고서 제공
## 주요 인터페이스 ## 주요 인터페이스
- `www.alist.co.kr`: 메인 서비스 - `www.alist.co.kr`: 메인 서비스
@@ -22,8 +23,8 @@
## 역할별 큰 흐름 ## 역할별 큰 흐름
- 교사: 회원 가입 후 캠퍼스 생성 또는 캠퍼스 초대 가입, 교재 선택, 수업 설계, 학생 초대, 과제/평가 관리, 학습 관리 수행 - 교사: 회원 가입 후 캠퍼스 생성 또는 캠퍼스 초대 가입, 교재 선택, 수업 설계, 학생 초대, 과제/평가 관리, 학습 관리 수행
- 학생: 본인 인증 기반 정회원으로 가입 가능, 자기주도학습과 캠퍼스 강좌 활동 수행 - 학생: 본인 인증 기반 정회원으로 가입 가능, 일부 A*List 콘텐츠 이용, 자기주도학습과 캠퍼스 강좌 활동 수행
- 수강생: 캠퍼스 초대 기반 준회원으로 가입하며 강좌 기반 학습 활동 위주로 이용 - 수강생: 캠퍼스 초대 기반 준회원으로 가입하며 강좌 기반 학습 활동 위주로 이용하고, 교재 자유 선택은 제한된다
- 관리자/유통업체: 캠퍼스 생성 승인, 교재/콘텐츠 관리, 파트너 및 운영 정책 관리 - 관리자/유통업체: 캠퍼스 생성 승인, 교재/콘텐츠 관리, 파트너 및 운영 정책 관리
## 서비스 환경 정책 ## 서비스 환경 정책
@@ -36,3 +37,4 @@
- 이 플랫폼은 단순 콘텐츠 사이트가 아니라 `회원 + 캠퍼스 + 강좌 + 학습 데이터`를 중심으로 동작하는 LMS 성격이 강하다. - 이 플랫폼은 단순 콘텐츠 사이트가 아니라 `회원 + 캠퍼스 + 강좌 + 학습 데이터`를 중심으로 동작하는 LMS 성격이 강하다.
- 교사용과 학생용 인터페이스가 명확히 분리되어 있으므로 메뉴, 권한, 응답 데이터도 같은 기준으로 나눠 설계해야 한다. - 교사용과 학생용 인터페이스가 명확히 분리되어 있으므로 메뉴, 권한, 응답 데이터도 같은 기준으로 나눠 설계해야 한다.
- 해외 임대/제휴 확장을 전제로 하므로 도메인, 캠퍼스 개인화, 메뉴 노출, 권한 모델에 고정값을 박아 넣지 않는 편이 안전하다. - 해외 임대/제휴 확장을 전제로 하므로 도메인, 캠퍼스 개인화, 메뉴 노출, 권한 모델에 고정값을 박아 넣지 않는 편이 안전하다.
- A*List와 A★캠퍼스는 같은 콘텐츠를 공유하더라도 다운로드, 온라인 뷰어, 강좌 단위 배포, LMS 저장 여부가 다를 수 있으므로 I/F별 활용 정책을 분리해야 한다.
@@ -39,12 +39,15 @@ public class OpenApiConfig {
@Bean @Bean
public GroupedOpenApi frontApi() { public GroupedOpenApi frontApi() {
return GroupedOpenApi.builder() return GroupedOpenApi.builder()
.group("front") .group("100. front")
.pathsToMatch("/**") .pathsToMatch("/**")
.pathsToExclude( .pathsToExclude(
"/admin/**", "/admin/**"
"/error", , "/cors/**"
"/actuator/**" , "/files/**"
, "/tusFiles/**"
, "/error"
, "/actuator/**"
) )
.build(); .build();
} }
@@ -52,8 +55,20 @@ public class OpenApiConfig {
@Bean @Bean
public GroupedOpenApi adminApi() { public GroupedOpenApi adminApi() {
return GroupedOpenApi.builder() return GroupedOpenApi.builder()
.group("admin") .group("500. admin")
.pathsToMatch("/admin/**") .pathsToMatch("/admin/**")
.build(); .build();
} }
@Bean
public GroupedOpenApi commonApi() {
return GroupedOpenApi.builder()
.group("900. common")
.pathsToMatch(
"/cors/**"
, "/files/**"
, "/tusFiles/**"
)
.build();
}
} }
@@ -1,7 +1,7 @@
package com.alist.api.config.cache; package com.alist.api.config.cache;
import com.alist.api.modules.auth.vo.CorsOriginVo; import com.alist.api.modules.cors.vo.CorsOriginVo;
import com.alist.api.modules.auth.mapper.CorsMapper; import com.alist.api.modules.cors.mapper.CorsMapper;
import jakarta.annotation.PostConstruct; import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@@ -28,7 +28,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
try { try {
String requestUri = request.getRequestURI(); String requestUri = request.getRequestURI();
boolean adminRequest = requestUri.startsWith("/admin/"); 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); String token = resolveToken(request, adminRequest, sharedRequest);
if (token != null && jwtTokenProvider.validateToken(token)) { if (token != null && jwtTokenProvider.validateToken(token)) {
@@ -21,7 +21,7 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
@Tag( @Tag(
name = "50. Admin 로그인" name = "501. Admin 로그인"
, description = "ADMIN 사이트 로그인입니다. 로그인시 토큰 발급이 함께됩니다." , description = "ADMIN 사이트 로그인입니다. 로그인시 토큰 발급이 함께됩니다."
) )
@RestController @RestController
@@ -2,12 +2,9 @@ package com.alist.api.modules.admin.member;
import com.alist.api.common.response.ApiResponse; import com.alist.api.common.response.ApiResponse;
import com.alist.api.common.response.ApiResponseCode; import com.alist.api.common.response.ApiResponseCode;
import com.alist.api.modules.admin.member.form.AdminMemberSearchForm; import com.alist.api.modules.admin.member.form.*;
import com.alist.api.modules.admin.member.form.AdminMemberUpdateForm;
import com.alist.api.modules.admin.member.service.AdminMemberService; 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.*;
import com.alist.api.modules.admin.member.vo.AdminMemberListVo;
import com.alist.api.modules.admin.member.vo.AdminMemberUpdateVo;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid; import jakarta.validation.Valid;
@@ -15,7 +12,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@Tag(name = "51. 회원 관리", description = "관리자 회원 관리 API") @Tag(name = "502. 회원 관리", description = "관리자 회원 관리 API")
@Slf4j @Slf4j
@RestController @RestController
@RequestMapping("/admin/member") @RequestMapping("/admin/member")
@@ -76,4 +73,55 @@ public class AdminMemberController {
return ApiResponse.entity(result, ApiResponseCode.CODE_2005, "회원 수정"); return ApiResponse.entity(result, ApiResponseCode.CODE_2005, "회원 수정");
} }
@Operation(
summary = "회원 휴면 상태 변경",
description = "관리자가 회원 PK 기준으로 휴면 상태를 변경합니다. Y: 휴면, N: 정상"
)
@PatchMapping("/dormant")
public ResponseEntity<ApiResponse<AdminMemberDormantStatusVo>> 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<ApiResponse<AdminMemberWithdrawStatusVo>> 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<ApiResponse<AdminMemberDeleteVo>> 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, "회원 계정 삭제");
}
} }
@@ -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;
}
@@ -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;
}
@@ -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;
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -1,7 +1,6 @@
package com.alist.api.modules.admin.member.mapper; 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.*;
import com.alist.api.modules.admin.member.dto.AdminMemberUpdateDto;
import com.alist.api.modules.admin.member.vo.AdminMemberDetailVo; import com.alist.api.modules.admin.member.vo.AdminMemberDetailVo;
import com.alist.api.modules.admin.member.vo.AdminMemberVo; import com.alist.api.modules.admin.member.vo.AdminMemberVo;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
@@ -17,4 +16,10 @@ public interface AdminMemberMapper {
AdminMemberDetailVo selectAdminMemberDetail(Integer userIdx); AdminMemberDetailVo selectAdminMemberDetail(Integer userIdx);
int updateAdminMember(AdminMemberUpdateDto adminMemberUpdateDto); int updateAdminMember(AdminMemberUpdateDto adminMemberUpdateDto);
int updateAdminMemberDormantStatus(AdminMemberDormantStatusDto adminMemberDormantStatusDto);
int updateAdminMemberWithdrawStatus(AdminMemberWithdrawStatusDto adminMemberWithdrawStatusDto);
int deleteAdminMember(AdminMemberDeleteDto adminMemberDeleteDto);
} }
@@ -1,12 +1,8 @@
package com.alist.api.modules.admin.member.service; 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.*;
import com.alist.api.modules.admin.member.dto.AdminMemberUpdateDto;
import com.alist.api.modules.admin.member.mapper.AdminMemberMapper; 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.*;
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 org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@@ -45,4 +41,31 @@ public class AdminMemberService {
result.setMemberUpdated(updateCount > 0); result.setMemberUpdated(updateCount > 0);
return result; 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;
}
} }
@@ -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;
}
@@ -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;
}
@@ -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;
}
@@ -15,7 +15,7 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
@Tag(name = "51. ADMIN 사용자 관리", description = "ADMIN 사용자 회원가입 및 관리 API") @Tag(name = "503. ADMIN 사용자 관리", description = "ADMIN 사용자 회원가입 및 관리 API")
@RestController @RestController
@Slf4j @Slf4j
@RequestMapping("/admin/user") @RequestMapping("/admin/user")
@@ -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.form.TokenForm;
import com.alist.api.modules.auth.service.SsoService; import com.alist.api.modules.auth.service.SsoService;
import com.alist.api.modules.auth.service.AuthService; 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.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
@@ -23,7 +24,7 @@ import org.springframework.web.bind.annotation.*;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@Tag(name = "02. 엑세스 토큰 발급", description = "엑세스 토큰 발급") @Tag(name = "102. 엑세스 토큰 발급", description = "엑세스 토큰 발급")
@RestController @RestController
@RequestMapping("/auth") @RequestMapping("/auth")
public class AuthController { public class AuthController {
@@ -62,11 +63,11 @@ public class AuthController {
description = "엑세스 토큰이 발급됩니다. (테스트용 실사용X)" description = "엑세스 토큰이 발급됩니다. (테스트용 실사용X)"
) )
@PostMapping("/token") @PostMapping("/token")
public ResponseEntity<ApiResponse<TokenDto>> token(@Valid @RequestBody TokenForm tokenForm) { public ResponseEntity<ApiResponse<TokenVo>> token(@Valid @RequestBody TokenForm tokenForm) {
TokenDto tokenDto = new TokenDto(); TokenVo tokenVo = new TokenVo();
tokenDto.setAccessToken(jwtTokenProvider.createToken(tokenForm.toTokenDto().getId())); tokenVo.setAccessToken(jwtTokenProvider.createToken(tokenForm.toTokenDto().getId()));
return ApiResponse.entity(tokenDto, ApiResponseCode.CODE_2001, "엑세스 토큰"); return ApiResponse.entity(tokenVo, ApiResponseCode.CODE_2001, "엑세스 토큰");
} }
@Operation( @Operation(
@@ -23,7 +23,7 @@ import java.net.URI;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@Tag(name = "01. SSO 인증", description = "공통 로그인, 인가 코드 발급, 코드 교환, 로그아웃 관련 API") @Tag(name = "101. SSO 인증", description = "공통 로그인, 인가 코드 발급, 코드 교환, 로그아웃 관련 API")
@RestController @RestController
@RequestMapping("/sso") @RequestMapping("/sso")
public class SsoController { public class SsoController {
@@ -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;
}
@@ -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.ApiResponse;
import com.alist.api.common.response.ApiResponseCode; import com.alist.api.common.response.ApiResponseCode;
@@ -13,7 +13,7 @@ import org.springframework.web.bind.annotation.RestController;
import java.util.List; import java.util.List;
@Tag(name = "97. CRS 갱신", description = "테스트 CORS 설정 관리 API") @Tag(name = "901. CRS 갱신", description = "테스트 CORS 설정 관리 API")
@RestController @RestController
@RequestMapping("/cors") @RequestMapping("/cors")
@RequiredArgsConstructor @RequiredArgsConstructor
@@ -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 org.apache.ibatis.annotations.Mapper;
import java.util.List; import java.util.List;
@@ -1,4 +1,4 @@
package com.alist.api.modules.auth.vo; package com.alist.api.modules.cors.vo;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
@@ -17,7 +17,7 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
@Tag( @Tag(
name = "98. 파일 업로드", name = "902. 파일 업로드",
description = "DB 기록 없이 파일을 저장하고 uploadPath를 반환하는 단순 업로드 API입니다. SunEditor 이미지는 file-domain URL을 반환합니다." description = "DB 기록 없이 파일을 저장하고 uploadPath를 반환하는 단순 업로드 API입니다. SunEditor 이미지는 file-domain URL을 반환합니다."
) )
@RestController @RestController
@@ -26,7 +26,7 @@ import java.nio.file.Path;
@Slf4j @Slf4j
@Tag( @Tag(
name = "99. TUS 업로드" name = "903. TUS 업로드"
, description = "TUS 기반 파일 업로드 초기화, 권한 검증, 상태 조회, 훅 처리, 취소 API" , description = "TUS 기반 파일 업로드 초기화, 권한 검증, 상태 조회, 훅 처리, 취소 API"
) )
@RestController @RestController
@@ -12,7 +12,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@Tag(name = "05. 사용자 관리", description = "사용자 회원가입 및 관리 API") @Tag(name = "103. 사용자 관리", description = "사용자 회원가입 및 관리 API")
@Slf4j @Slf4j
@RestController @RestController
@RequestMapping("/user") @RequestMapping("/user")
@@ -126,10 +126,10 @@ public class UserController {
description = "로그인한 사용자의 탈퇴 요청 또는 탈퇴 요청 철회를 처리합니다. P: 탈퇴대기, N: 탈퇴요청 철회" description = "로그인한 사용자의 탈퇴 요청 또는 탈퇴 요청 철회를 처리합니다. P: 탈퇴대기, N: 탈퇴요청 철회"
) )
@PatchMapping("/withdraw") @PatchMapping("/withdraw")
public ResponseEntity<ApiResponse<UserWithdrawStatusUpdateVo>> updateWithdrawStatus( public ResponseEntity<ApiResponse<UserWithdrawStatusVo>> updateWithdrawStatus(
@Valid @RequestBody UserWithdrawStatusUpdateForm userWithdrawStatusUpdateForm @Valid @RequestBody UserWithdrawStatusForm userWithdrawStatusForm
) { ) {
UserWithdrawStatusUpdateVo result = userService.updateWithdrawStatus(userWithdrawStatusUpdateForm.toUserWithdrawStatusUpdateDto()); UserWithdrawStatusVo result = userService.updateWithdrawStatus(userWithdrawStatusForm.toUserWithdrawStatusUpdateDto());
if (!result.isWithdrawStatusUpdated()) { if (!result.isWithdrawStatusUpdated()) {
if (result.getResultCode() == 4003 || result.getResultCode() == 4001) { if (result.getResultCode() == 4003 || result.getResultCode() == 4001) {
@@ -141,4 +141,25 @@ public class UserController {
return ApiResponse.entity(result, ApiResponseCode.CODE_2005, "탈퇴 상태 변경"); return ApiResponse.entity(result, ApiResponseCode.CODE_2005, "탈퇴 상태 변경");
} }
@Operation(
summary = "사용자 휴면 상태 변경",
description = "로그인한 사용자의 휴면 처리 또는 휴면 해지를 처리합니다. Y: 휴면 회원, N: 휴면 해지"
)
@PatchMapping("/dormant")
public ResponseEntity<ApiResponse<UserDormantStatusVo>> 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, "휴면 상태 변경");
}
} }
@@ -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;
}
@@ -5,7 +5,7 @@ import lombok.Setter;
@Getter @Getter
@Setter @Setter
public class UserWithdrawStatusUpdateDto { public class UserWithdrawStatusDto {
private Integer userTokenIdx; private Integer userTokenIdx;
private String withdrawStatus; private String withdrawStatus;
} }
@@ -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;
}
}
@@ -1,6 +1,6 @@
package com.alist.api.modules.user.form; 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 io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern; import jakarta.validation.constraints.Pattern;
@@ -10,7 +10,7 @@ import lombok.Setter;
@Schema(description = "사용자 탈퇴 상태 변경 요청 폼") @Schema(description = "사용자 탈퇴 상태 변경 요청 폼")
@Getter @Getter
@Setter @Setter
public class UserWithdrawStatusUpdateForm { public class UserWithdrawStatusForm {
@Schema( @Schema(
description = "탈퇴 상태. P: 탈퇴대기, N: 탈퇴요청 철회", description = "탈퇴 상태. P: 탈퇴대기, N: 탈퇴요청 철회",
example = "P", example = "P",
@@ -20,9 +20,9 @@ public class UserWithdrawStatusUpdateForm {
@Pattern(regexp = "^[PN]$", message = "탈퇴 상태는 P 또는 N만 가능합니다.") @Pattern(regexp = "^[PN]$", message = "탈퇴 상태는 P 또는 N만 가능합니다.")
private String withdrawStatus; private String withdrawStatus;
public UserWithdrawStatusUpdateDto toUserWithdrawStatusUpdateDto() { public UserWithdrawStatusDto toUserWithdrawStatusUpdateDto() {
UserWithdrawStatusUpdateDto userWithdrawStatusUpdateDto = new UserWithdrawStatusUpdateDto(); UserWithdrawStatusDto userWithdrawStatusDto = new UserWithdrawStatusDto();
userWithdrawStatusUpdateDto.setWithdrawStatus(withdrawStatus); userWithdrawStatusDto.setWithdrawStatus(withdrawStatus);
return userWithdrawStatusUpdateDto; return userWithdrawStatusDto;
} }
} }
@@ -18,5 +18,7 @@ public interface UserMapper {
int updateUserProfile(UserProfileUpdateDto userProfileUpdateDto); int updateUserProfile(UserProfileUpdateDto userProfileUpdateDto);
int updateWithdrawStatus(UserWithdrawStatusUpdateDto userWithdrawStatusUpdateDto); int updateWithdrawStatus(UserWithdrawStatusDto userWithdrawStatusDto);
int updateDormantStatus(UserDormantStatusDto userDormantStatusDto);
} }
@@ -229,19 +229,19 @@ public class UserService {
} }
@Transactional @Transactional
public UserWithdrawStatusUpdateVo updateWithdrawStatus(UserWithdrawStatusUpdateDto userWithdrawStatusUpdateDto) { public UserWithdrawStatusVo updateWithdrawStatus(UserWithdrawStatusDto userWithdrawStatusDto) {
UserWithdrawStatusUpdateVo result = new UserWithdrawStatusUpdateVo(); UserWithdrawStatusVo result = new UserWithdrawStatusVo();
Integer userTokenIdx = SecurityUtil.getLoginUserTokenIdx(); 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.setWithdrawStatusUpdated(false);
result.setResultCode(4003); result.setResultCode(4003);
return result; return result;
} }
String withdrawStatus = userWithdrawStatusUpdateDto.getWithdrawStatus(); String withdrawStatus = userWithdrawStatusDto.getWithdrawStatus();
if (!"P".equals(withdrawStatus) && !"N".equals(withdrawStatus)) { if (!"P".equals(withdrawStatus) && !"N".equals(withdrawStatus)) {
result.setWithdrawStatusUpdated(false); result.setWithdrawStatusUpdated(false);
@@ -249,11 +249,40 @@ public class UserService {
return result; return result;
} }
int updateCount = userMapper.updateWithdrawStatus(userWithdrawStatusUpdateDto); int updateCount = userMapper.updateWithdrawStatus(userWithdrawStatusDto);
result.setWithdrawStatusUpdated(updateCount > 0); result.setWithdrawStatusUpdated(updateCount > 0);
result.setResultCode(updateCount > 0 ? 2005 : 2003); result.setResultCode(updateCount > 0 ? 2005 : 2003);
return result; 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;
}
} }
@@ -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;
}
@@ -8,7 +8,7 @@ import lombok.Setter;
@Schema(description = "사용자 탈퇴 상태 변경 응답") @Schema(description = "사용자 탈퇴 상태 변경 응답")
@Getter @Getter
@Setter @Setter
public class UserWithdrawStatusUpdateVo { public class UserWithdrawStatusVo {
@Schema(description = "탈퇴 상태 변경 여부", example = "true") @Schema(description = "탈퇴 상태 변경 여부", example = "true")
private boolean withdrawStatusUpdated; private boolean withdrawStatusUpdated;
+22 -5
View File
@@ -41,11 +41,11 @@
INNER JOIN ALISTLMS.test_user_token ATUT ON ATUT.user_idx = ATU.user_idx INNER JOIN ALISTLMS.test_user_token ATUT ON ATUT.user_idx = ATU.user_idx
SET ATU.withdraw_status = #{withdrawStatus} SET ATU.withdraw_status = #{withdrawStatus}
, ATU.withdraw_at = , ATU.withdraw_at =
CASE CASE
WHEN #{withdrawStatus} = 'P' THEN now() WHEN #{withdrawStatus} = 'P' THEN now()
WHEN #{withdrawStatus} = 'N' THEN NULL WHEN #{withdrawStatus} = 'N' THEN NULL
ELSE ATU.withdraw_at ELSE ATU.withdraw_at
END END
, ATU.update_at = now() , ATU.update_at = now()
WHERE ATUT.user_token_idx = #{userTokenIdx} WHERE ATUT.user_token_idx = #{userTokenIdx}
AND ATU.del_yn = 1 AND ATU.del_yn = 1
@@ -69,4 +69,21 @@
AND ATU.del_yn = 1 AND ATU.del_yn = 1
</select> </select>
<update id="updateDormantStatus">
/*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'
</update>
</mapper> </mapper>
@@ -48,6 +48,44 @@
AND ATU.del_yn = 1 AND ATU.del_yn = 1
</update> </update>
<update id="updateAdminMemberDormantStatus">
/*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
</update>
<update id="updateAdminMemberWithdrawStatus">
/*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
</update>
<update id="deleteAdminMember">
/*AdminMemberMapper.deleteAdminMember*/
UPDATE ALISTLMS.test_user
SET del_yn = 0
, update_at = now()
WHERE user_idx = #{userIdx}
AND del_yn = 1
</update>
<select id="selectAdminMemberCount" resultType="int"> <select id="selectAdminMemberCount" resultType="int">
/*AdminMemberMapper.selectAdminMemberCount*/ /*AdminMemberMapper.selectAdminMemberCount*/
SELECT COUNT(*) SELECT COUNT(*)
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.alist.api.modules.auth.mapper.CorsMapper"> <mapper namespace="com.alist.api.modules.cors.mapper.CorsMapper">
<select id="selectCorsAllowedList" resultType="com.alist.api.modules.auth.vo.CorsOriginVo"> <select id="selectCorsAllowedList" resultType="com.alist.api.modules.cors.vo.CorsOriginVo">
/* CorsMapper.selectCorsAllowedList */ /* CorsMapper.selectCorsAllowedList */
SELECT allowed_origin SELECT allowed_origin
FROM ALISTLMS.test_cors_allowed_list FROM ALISTLMS.test_cors_allowed_list