[api] 회원 목록 & 수정 & 상세 추가 / 각 vo dto contorller schema 추가

This commit is contained in:
2026-05-19 16:17:05 +09:00
parent 4cc6078484
commit d5483b790d
65 changed files with 1190 additions and 25 deletions
+1
View File
@@ -9,6 +9,7 @@
- 소스코드 변경 요청에서는 바로 구현하지 말고 `코드로 보여주기` 또는 `직접 작성하기` 중 원하는 방식을 먼저 확인한다.
- Controller 는 요청/응답 조립과 인증 주체 확인에 집중하고, 복잡한 로직과 DB 처리는 Service 로 넘긴다.
- 응답은 `ApiResponse<T>``ApiResponseCode` 조합을 우선 사용한다.
- 신규/수정되는 Controller, Form, VO에는 Swagger 테스트 편의성을 위해 `@Tag`, `@Operation`, `@Schema` 설명과 예시를 남긴다.
- 변경 후에는 가능하면 테스트 또는 최소 실행 검증 결과를 남기고, 미실행 항목은 사유를 분명히 적는다.
## 문서 인덱스
+17
View File
@@ -20,6 +20,23 @@
- 외부 응답에 노출되면 안 되는 내부 필드는 응답에 사용될 수 있는 객체에서 `@JsonIgnore`로 숨긴다.
- Form 에서 DTO 로 변환할 때는 검증과 `trim()`, 기본값 치환까지 같이 처리하는 현재 패턴을 우선 따른다.
## Swagger/OpenAPI 문서화 규칙
- 신규 또는 수정되는 Controller, Form, VO에는 Swagger 테스트 편의성을 위한 설명을 반드시 남긴다.
- Controller 클래스에는 `@Tag`를 사용해 API 그룹명과 설명을 작성한다.
- Controller 메서드에는 `@Operation`으로 `summary``description`을 작성한다.
- `@RequestBody` Form 클래스에는 클래스 레벨 `@Schema(description = "...")`를 작성한다.
- `@ModelAttribute` 검색 Form 필드에도 `@Schema`를 작성해 Swagger query parameter 설명과 예시가 보이게 한다.
- Form 필드에는 `@Schema``description`, `example`을 작성한다.
- 필수 입력값은 Jakarta Validation 어노테이션과 `@Schema(requiredMode = Schema.RequiredMode.REQUIRED)`를 함께 사용한다.
- 코드값 필드는 허용 값을 `description`에 명시한다. 예: `userType: A 관리자, T 교사, S 학생`, `withdrawStatus: N 정상, P 탈퇴대기, Y 탈퇴완료`.
- VO 클래스에는 클래스 레벨 `@Schema(description = "...")`를 작성한다.
- VO 필드에는 `@Schema`로 응답 값의 의미를 작성한다.
- 토큰, 임시 비밀번호처럼 Swagger 테스트에 필요한 응답 필드는 VO에 명시하되 `description`에 용도를 적는다.
- 비밀번호, DB 조회용 내부 식별자, `resultCode`처럼 외부 응답에 노출하지 않는 필드는 `@JsonIgnore`를 사용하고 필요하면 `@Schema(hidden = true)`도 함께 사용한다.
- 클라이언트가 직접 전달하지 않는 내부 계산 필드나 서버 전용 getter는 `@Schema(hidden = true)`로 숨긴다.
- 페이징 요청에서 클라이언트는 `page`, `size`만 전달하고, `limit`, `offset`은 Swagger에 노출하지 않는다.
- 페이징 응답의 `rowStartNum`처럼 계산 방식이 필요한 값은 프론트 사용 방법까지 `description`에 남긴다.
## Mapper/MyBatis 규칙
- Mapper 메서드명은 SQL 동작이 드러나도록 `select`, `insert`, `update` 접두어를 사용한다.
- 삭제가 물리 삭제가 아니라 상태 변경이면 `delete` 대신 목적이 드러나는 `update...Canceled`, `update...DelYn` 같은 이름을 우선한다.
@@ -0,0 +1,32 @@
package com.alist.api.common.paging;
import com.alist.api.common.utils.PagingUtil;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Setter;
@Setter
public class PageRequest {
@Schema(description = "페이지 번호", example = "1")
private Integer page;
@Schema(description = "페이지 크기", example = "20")
private Integer size;
public Integer getPage() {
return PagingUtil.getPage(page);
}
public Integer getSize() {
return PagingUtil.getSize(size);
}
@Schema(hidden = true)
public Integer getLimit() {
return PagingUtil.getLimit(size);
}
@Schema(hidden = true)
public Integer getOffset() {
return PagingUtil.getOffset(page, size);
}
}
@@ -0,0 +1,36 @@
package com.alist.api.common.paging;
import com.alist.api.common.utils.PagingUtil;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class PageResponse {
@Schema(description = "현재 페이지 번호", example = "1")
private int page;
@Schema(description = "페이지당 조회 개수", example = "20")
private int size;
@Schema(description = "전체 데이터 개수", example = "100")
private int totalCount;
@Schema(description = "전체 페이지 수", example = "5")
private int totalPage;
@Schema(
description = "현재 페이지 첫 번째 row 번호. 각 row 번호는 rowStartNum - memberList index로 계산합니다.",
example = "100"
)
private int rowStartNum;
public void setPaging(PageRequest pageRequest, int totalCount) {
this.page = pageRequest.getPage();
this.size = pageRequest.getSize();
this.totalCount = totalCount;
this.totalPage = PagingUtil.getTotalPage(totalCount, pageRequest.getSize());
this.rowStartNum = PagingUtil.getRowStartNum(totalCount, pageRequest.getPage(), pageRequest.getSize());
}
}
@@ -0,0 +1,44 @@
package com.alist.api.common.utils;
public final class PagingUtil {
private static final int DEFAULT_PAGE = 1;
private static final int DEFAULT_SIZE = 10;
private static final int MAX_SIZE = 100;
private PagingUtil() {
}
public static int getPage(Integer page) {
return page == null || page < 1 ? DEFAULT_PAGE : page;
}
public static int getSize(Integer size) {
if (size == null || size < 1) {
return DEFAULT_SIZE;
}
return Math.min(size, MAX_SIZE);
}
public static int getLimit(Integer size) {
return getSize(size);
}
public static int getOffset(Integer page, Integer size) {
return (getPage(page) - 1) * getSize(size);
}
public static int getTotalPage(int totalCount, Integer size) {
return totalCount <= 0 ? 0 : (int) Math.ceil((double) totalCount / getSize(size));
}
public static int getRowStartNum(int totalCount, Integer page, Integer size) {
if (totalCount <= 0) {
return 0;
}
int offset = getOffset(page, size);
int rowStartNum = totalCount - offset;
return Math.max(rowStartNum, 0);
}
}
@@ -4,8 +4,10 @@ import com.alist.api.common.response.ApiResponse;
import com.alist.api.common.response.ApiResponseCode;
import com.alist.api.common.utils.SecurityUtil;
import com.alist.api.common.utils.SessionUtil;
import com.alist.api.modules.admin.auth.form.AdminApiKeyLoginForm;
import com.alist.api.modules.admin.auth.form.AdminLoginForm;
import com.alist.api.modules.admin.auth.service.AdminAuthService;
import com.alist.api.modules.admin.auth.vo.AdminApiKeyLoginVo;
import com.alist.api.modules.admin.auth.vo.AdminLoginVo;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -50,6 +52,23 @@ public class AdminAuthController {
return ApiResponse.entity(result, ApiResponseCode.CODE_2001, "관리자 로그인");
}
@Operation(
summary = "admin API Key 로그인",
description = "Admin API Key로 인증하고 JSON으로 토큰을 반환합니다. Swagger 테스트용입니다."
)
@PostMapping("/apiKeyLogin")
public ResponseEntity<ApiResponse<AdminApiKeyLoginVo>> adminApiKeyLogin(
@Valid @RequestBody AdminApiKeyLoginForm adminApiKeyLoginForm
) {
AdminApiKeyLoginVo result = adminAuthService.adminApiKeyLogin(adminApiKeyLoginForm.toAdminApiKeyLoginDto());
if (result.getResultCode() == 2003) {
return ApiResponse.entity(result, ApiResponseCode.CODE_2003);
}
return ApiResponse.entity(result, ApiResponseCode.CODE_2001, "관리자 엑세스 토큰");
}
@Operation(
summary = "admin 리프레시토큰 발급"
, description = "엑세스 토큰 만료시 리프레시 토큰 발급 용도 api 입니다."
@@ -0,0 +1,16 @@
package com.alist.api.modules.admin.auth.dto;
import lombok.Getter;
import lombok.Setter;
import java.time.Instant;
@Getter
@Setter
public class AdminApiKeyLoginDto {
private String userApiKey;
private Integer userIdx;
private Integer userTokenIdx;
private String refreshToken;
private Instant expiresAt;
}
@@ -0,0 +1,26 @@
package com.alist.api.modules.admin.auth.form;
import com.alist.api.modules.admin.auth.dto.AdminApiKeyLoginDto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Getter;
import lombok.Setter;
@Schema(description = "Admin API Key 로그인 요청 폼")
@Getter
@Setter
public class AdminApiKeyLoginForm {
@Schema(
description = "Admin API Key",
example = "ypdz9hl7WAp2D03HC5h9koEC49R6LAkLSuoSdgEwVQA",
requiredMode = Schema.RequiredMode.REQUIRED
)
@NotBlank(message = "apiKey를 입력해주세요.")
private String userApiKey;
public AdminApiKeyLoginDto toAdminApiKeyLoginDto() {
AdminApiKeyLoginDto adminApiKeyLoginDto = new AdminApiKeyLoginDto();
adminApiKeyLoginDto.setUserApiKey(userApiKey.trim());
return adminApiKeyLoginDto;
}
}
@@ -1,17 +1,29 @@
package com.alist.api.modules.admin.auth.form;
import com.alist.api.modules.admin.auth.dto.AdminLoginDto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Getter;
import lombok.Setter;
@Schema(description = "Admin 로그인 요청 폼")
@Getter
@Setter
public class AdminLoginForm {
@NotBlank
@Schema(
description = "관리자 아이디",
example = "admin",
requiredMode = Schema.RequiredMode.REQUIRED
)
@NotBlank(message = "아이디는 필수입니다.")
private String id;
@NotBlank
@Schema(
description = "관리자 비밀번호",
example = "password",
requiredMode = Schema.RequiredMode.REQUIRED
)
@NotBlank(message = "비밀번호는 필수입니다.")
private String password;
public AdminLoginDto toAdminLoginDto() {
@@ -1,6 +1,8 @@
package com.alist.api.modules.admin.auth.mapper;
import com.alist.api.modules.admin.auth.dto.AdminApiKeyLoginDto;
import com.alist.api.modules.admin.auth.dto.AdminLoginDto;
import com.alist.api.modules.admin.auth.vo.AdminApiKeyLoginVo;
import com.alist.api.modules.admin.auth.vo.AdminLoginVo;
import org.apache.ibatis.annotations.Mapper;
@@ -15,4 +17,8 @@ public interface AdminAuthMapper {
AdminLoginVo selectAdminTokenByUserTokenIdx(Integer userTokenIdx);
void clearAdminRefreshToken(Integer userTokenIdx);
AdminApiKeyLoginVo selectAdminApiKeyLogin(AdminApiKeyLoginDto adminApiKeyLoginDto);
void updateAdminApiKeyLoginRefreshToken(AdminApiKeyLoginDto adminApiKeyLoginDto);
}
@@ -2,8 +2,10 @@ package com.alist.api.modules.admin.auth.service;
import com.alist.api.common.utils.SessionUtil;
import com.alist.api.config.jwt.JwtTokenProvider;
import com.alist.api.modules.admin.auth.dto.AdminApiKeyLoginDto;
import com.alist.api.modules.admin.auth.dto.AdminLoginDto;
import com.alist.api.modules.admin.auth.mapper.AdminAuthMapper;
import com.alist.api.modules.admin.auth.vo.AdminApiKeyLoginVo;
import com.alist.api.modules.admin.auth.vo.AdminLoginVo;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Value;
@@ -158,4 +160,33 @@ public class AdminAuthService {
return adminAuthMapper.selectAdminTokenByUserTokenIdx(userTokenIdx);
}
@Transactional
public AdminApiKeyLoginVo adminApiKeyLogin(AdminApiKeyLoginDto adminApiKeyLoginDto) {
AdminApiKeyLoginVo result = adminAuthMapper.selectAdminApiKeyLogin(adminApiKeyLoginDto);
if (result == null) {
AdminApiKeyLoginVo empty = new AdminApiKeyLoginVo();
empty.setResultCode(2003);
empty.setLogin(false);
return empty;
}
String accessToken = jwtTokenProvider.createAdminAccessToken(result.getUserTokenIdx());
String refreshToken = jwtTokenProvider.createAdminRefreshToken(result.getUserTokenIdx());
adminApiKeyLoginDto.setUserTokenIdx(result.getUserTokenIdx());
adminApiKeyLoginDto.setRefreshToken(refreshToken);
adminApiKeyLoginDto.setExpiresAt(Instant.now().plusSeconds(refreshTokenValiditySeconds));
adminAuthMapper.updateAdminApiKeyLoginRefreshToken(adminApiKeyLoginDto);
adminAuthMapper.updateAdminLastLoginAt(result.getUserIdx());
result.setAccessToken(accessToken);
result.setRefreshToken(refreshToken);
result.setResultCode(2001);
result.setLogin(true);
return result;
}
}
@@ -0,0 +1,40 @@
package com.alist.api.modules.admin.auth.vo;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
@Schema(description = "Admin API Key 로그인 응답")
@Getter
@Setter
public class AdminApiKeyLoginVo {
@Schema(description = "로그인 성공 여부", example = "true")
private boolean login;
@Schema(description = "회원 PK", example = "14")
private Integer userIdx;
@Schema(description = "관리자 아이디", example = "admin")
private String id;
@Schema(description = "관리자 권한 코드", example = "A")
private String userRole;
@Schema(description = "회원 유형 코드. A: 관리자", example = "A")
private String userType;
@Schema(description = "관리자 API 테스트용 Access Token")
private String accessToken;
@Schema(description = "관리자 API 테스트용 Refresh Token")
private String refreshToken;
@JsonIgnore
@Schema(hidden = true)
private Integer userTokenIdx;
@JsonIgnore
@Schema(hidden = true)
private int resultCode;
}
@@ -1,27 +1,42 @@
package com.alist.api.modules.admin.auth.vo;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
@Schema(description = "Admin 로그인 응답")
@Setter
@Getter
public class AdminLoginVo {
@Schema(description = "로그인 성공 여부", example = "true")
private boolean isLogin;
@Schema(description = "회원 PK", example = "14")
private Integer userIdx;
@Schema(description = "관리자 아이디", example = "admin")
private String id;
@Schema(description = "관리자 권한 코드", example = "A")
private String userRole;
@Schema(description = "회원 유형 코드. A: 관리자", example = "A")
private String userType;
@JsonIgnore
@Schema(hidden = true)
private String password;
@JsonIgnore
@Schema(hidden = true)
private Integer userTokenIdx;
@JsonIgnore
@Schema(hidden = true)
private String refreshToken;
@JsonIgnore
@Schema(hidden = true)
private int resultCode;
}
@@ -0,0 +1,79 @@
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.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 io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@Tag(name = "51. 회원 관리", description = "관리자 회원 관리 API")
@Slf4j
@RestController
@RequestMapping("/admin/member")
public class AdminMemberController {
private final AdminMemberService adminMemberService;
public AdminMemberController(AdminMemberService adminMemberService) {
this.adminMemberService = adminMemberService;
}
@Operation(
summary = "회원 목록 조회",
description = "관리자가 회원 목록을 검색 조건과 페이징으로 조회합니다."
)
@GetMapping("/list")
public ResponseEntity<ApiResponse<AdminMemberListVo>> adminMemberList(
@ModelAttribute AdminMemberSearchForm adminMemberSearchForm
) {
AdminMemberListVo result = adminMemberService.selectAdminMemberList(adminMemberSearchForm.toAdminMemberSearchDto());
if (result.getMemberList() == null || result.getMemberList().isEmpty()) {
return ApiResponse.entity(result, ApiResponseCode.CODE_2003);
}
return ApiResponse.entity(result, ApiResponseCode.CODE_2001, "회원");
}
@Operation(
summary = "회원 상세 조회",
description = "관리자가 회원 PK 기준으로 회원 상세 정보를 조회합니다."
)
@GetMapping("/detail")
public ResponseEntity<ApiResponse<AdminMemberDetailVo>> adminMemberDetail(
@RequestParam Integer userIdx
) {
AdminMemberDetailVo result = adminMemberService.selectAdminMemberDetail(userIdx);
if (result == null) {
return ApiResponse.entity((AdminMemberDetailVo) null, ApiResponseCode.CODE_2003);
}
return ApiResponse.entity(result, ApiResponseCode.CODE_2001, "회원");
}
@Operation(
summary = "회원 정보 수정",
description = "관리자가 회원 PK 기준으로 회원 기본 정보와 권한 정보를 수정합니다."
)
@PutMapping("/update")
public ResponseEntity<ApiResponse<AdminMemberUpdateVo>> adminMemberUpdate(
@Valid @RequestBody AdminMemberUpdateForm adminMemberUpdateForm
) {
AdminMemberUpdateVo result = adminMemberService.updateAdminMember(adminMemberUpdateForm.toAdminMemberUpdateDto());
if (!result.isMemberUpdated()) {
return ApiResponse.entity(result, ApiResponseCode.CODE_2003);
}
return ApiResponse.entity(result, ApiResponseCode.CODE_2005, "회원 수정");
}
}
@@ -0,0 +1,15 @@
package com.alist.api.modules.admin.member.dto;
import com.alist.api.common.paging.PageRequest;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class AdminMemberSearchDto extends PageRequest {
private String keyword;
private String userRole;
private String userType;
private String dormantYn;
private String withdrawStatus;
}
@@ -0,0 +1,14 @@
package com.alist.api.modules.admin.member.dto;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class AdminMemberUpdateDto {
private Integer userIdx;
private String email;
private String hp;
private String userRole;
private String userType;
}
@@ -0,0 +1,39 @@
package com.alist.api.modules.admin.member.form;
import com.alist.api.common.paging.PageRequest;
import com.alist.api.modules.admin.member.dto.AdminMemberSearchDto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
@Schema(description = "관리자 회원 목록 검색 조건")
@Getter
@Setter
public class AdminMemberSearchForm extends PageRequest {
@Schema(description = "검색어. 아이디, 이메일, 휴대폰 번호를 대상으로 검색합니다.", example = "admin")
private String keyword;
@Schema(description = "회원 권한 코드(CCD/SCM/I/R/E)", example = "SCM")
private String userRole;
@Schema(description = "회원 유형 코드. A: 관리자, T: 교사, S: 학생", example = "T")
private String userType;
@Schema(description = "휴면 여부. N: 정상, Y: 휴면", example = "N")
private String dormantYn;
@Schema(description = "탈퇴 상태. N: 정상, P: 탈퇴대기, Y: 탈퇴완료", example = "N")
private String withdrawStatus;
public AdminMemberSearchDto toAdminMemberSearchDto() {
AdminMemberSearchDto adminMemberSearchDto = new AdminMemberSearchDto();
adminMemberSearchDto.setPage(getPage());
adminMemberSearchDto.setSize(getSize());
adminMemberSearchDto.setKeyword(keyword);
adminMemberSearchDto.setUserRole(userRole);
adminMemberSearchDto.setUserType(userType);
adminMemberSearchDto.setDormantYn(dormantYn);
adminMemberSearchDto.setWithdrawStatus(withdrawStatus);
return adminMemberSearchDto;
}
}
@@ -0,0 +1,40 @@
package com.alist.api.modules.admin.member.form;
import com.alist.api.modules.admin.member.dto.AdminMemberUpdateDto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotNull;
import lombok.Getter;
import lombok.Setter;
@Schema(description = "관리자 회원 수정 요청 폼")
@Getter
@Setter
public class AdminMemberUpdateForm {
@Schema(description = "회원 PK", example = "15", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "회원 PK는 필수입니다.")
private Integer userIdx;
@Schema(description = "이메일", example = "user@example.com")
@Email(message = "이메일 형식을 확인해주세요.")
private String email;
@Schema(description = "휴대폰 번호", example = "01012345678")
private String hp;
@Schema(description = "회원 권한 코드(CCD/SCM/I/R/E)", example = "CCM")
private String userRole;
@Schema(description = "회원 유형 코드. A: 관리자, T: 교사, S: 학생", example = "T")
private String userType;
public AdminMemberUpdateDto toAdminMemberUpdateDto() {
AdminMemberUpdateDto adminMemberUpdateDto = new AdminMemberUpdateDto();
adminMemberUpdateDto.setUserIdx(userIdx);
adminMemberUpdateDto.setEmail(email);
adminMemberUpdateDto.setHp(hp);
adminMemberUpdateDto.setUserRole(userRole);
adminMemberUpdateDto.setUserType(userType);
return adminMemberUpdateDto;
}
}
@@ -0,0 +1,20 @@
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.vo.AdminMemberDetailVo;
import com.alist.api.modules.admin.member.vo.AdminMemberVo;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface AdminMemberMapper {
int selectAdminMemberCount(AdminMemberSearchDto adminMemberSearchDto);
List<AdminMemberVo> selectAdminMemberList(AdminMemberSearchDto adminMemberSearchDto);
AdminMemberDetailVo selectAdminMemberDetail(Integer userIdx);
int updateAdminMember(AdminMemberUpdateDto adminMemberUpdateDto);
}
@@ -0,0 +1,48 @@
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.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 org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
public class AdminMemberService {
private final AdminMemberMapper adminMemberMapper;
public AdminMemberService(AdminMemberMapper adminMemberMapper) {
this.adminMemberMapper = adminMemberMapper;
}
@Transactional(readOnly = true)
public AdminMemberListVo selectAdminMemberList(AdminMemberSearchDto adminMemberSearchDto) {
int totalCount = adminMemberMapper.selectAdminMemberCount(adminMemberSearchDto);
List<AdminMemberVo> memberList = adminMemberMapper.selectAdminMemberList(adminMemberSearchDto);
AdminMemberListVo result = new AdminMemberListVo();
result.setMemberList(memberList);
result.setPaging(adminMemberSearchDto, totalCount);
return result;
}
@Transactional(readOnly = true)
public AdminMemberDetailVo selectAdminMemberDetail(Integer userIdx) {
return adminMemberMapper.selectAdminMemberDetail(userIdx);
}
@Transactional
public AdminMemberUpdateVo updateAdminMember(AdminMemberUpdateDto adminMemberUpdateDto) {
int updateCount = adminMemberMapper.updateAdminMember(adminMemberUpdateDto);
AdminMemberUpdateVo result = new AdminMemberUpdateVo();
result.setMemberUpdated(updateCount > 0);
return result;
}
}
@@ -0,0 +1,51 @@
package com.alist.api.modules.admin.member.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
import java.time.LocalDateTime;
@Schema(description = "관리자 회원 상세 조회 응답")
@Getter
@Setter
public class AdminMemberDetailVo {
@Schema(description = "회원 PK", example = "15")
private Integer userIdx;
@Schema(description = "회원 토큰 PK", example = "15")
private Integer userTokenIdx;
@Schema(description = "회원 아이디", example = "admin")
private String id;
@Schema(description = "이메일", example = "user@example.com")
private String email;
@Schema(description = "휴대폰 번호", example = "01012345678")
private String hp;
@Schema(description = "회원 권한 코드(CCD/SCM/I/R/E)", example = "SCM")
private String userRole;
@Schema(description = "회원 유형 코드. A: 관리자, T: 교사, S: 학생", example = "T")
private String userType;
@Schema(description = "휴면 여부. N: 정상, Y: 휴면", example = "N")
private String dormantYn;
@Schema(description = "휴면 처리일")
private LocalDateTime dormantAt;
@Schema(description = "탈퇴 상태. N: 정상, P: 탈퇴대기, Y: 탈퇴완료", example = "N")
private String withdrawStatus;
@Schema(description = "탈퇴 신청일")
private LocalDateTime withdrawAt;
@Schema(description = "가입일")
private LocalDateTime createAt;
@Schema(description = "수정일")
private LocalDateTime updateAt;
}
@@ -0,0 +1,16 @@
package com.alist.api.modules.admin.member.vo;
import com.alist.api.common.paging.PageResponse;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Schema(description = "관리자 회원 목록 조회 응답")
@Getter
@Setter
public class AdminMemberListVo extends PageResponse {
@Schema(description = "회원 목록")
private List<AdminMemberVo> memberList;
}
@@ -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 AdminMemberUpdateVo {
@Schema(description = "회원 수정 여부", example = "true")
private boolean memberUpdated;
}
@@ -0,0 +1,51 @@
package com.alist.api.modules.admin.member.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
import java.time.LocalDateTime;
@Schema(description = "관리자 회원 목록 항목")
@Getter
@Setter
public class AdminMemberVo {
@Schema(description = "회원 PK", example = "15")
private Integer userIdx;
@Schema(description = "회원 토큰 PK", example = "15")
private Integer userTokenIdx;
@Schema(description = "회원 아이디", example = "admin")
private String id;
@Schema(description = "이메일", example = "user@example.com")
private String email;
@Schema(description = "휴대폰 번호", example = "01012345678")
private String hp;
@Schema(description = "회원 권한 코드(CCD/SCM/I/R/E)", example = "SCM")
private String userRole;
@Schema(description = "회원 유형 코드. A: 관리자, T: 교사, S: 학생", example = "T")
private String userType;
@Schema(description = "휴면 여부. N: 정상, Y: 휴면", example = "N")
private String dormantYn;
@Schema(description = "휴면 처리일")
private LocalDateTime dormantAt;
@Schema(description = "탈퇴 상태. N: 정상, P: 탈퇴대기, Y: 탈퇴완료", example = "N")
private String withdrawStatus;
@Schema(description = "탈퇴 신청일")
private LocalDateTime withdrawAt;
@Schema(description = "가입일")
private LocalDateTime createAt;
@Schema(description = "수정일")
private LocalDateTime updateAt;
}
@@ -1,6 +1,7 @@
package com.alist.api.modules.admin.user.dto;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
@@ -13,9 +14,14 @@ public class AdminUserDto {
private String userType;
@JsonIgnore
@Schema(hidden = true)
private String newPassword;
@JsonIgnore
@Schema(hidden = true)
private String password;
@JsonIgnore
@Schema(hidden = true)
private int resultCode;
}
@@ -1,21 +1,35 @@
package com.alist.api.modules.admin.user.vo;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
@Schema(description = "Admin 사용자 회원가입 응답")
@Setter
@Getter
public class AdminUserVo {
@Schema(description = "회원 PK", example = "14")
private Integer userIdx;
@Schema(description = "관리자 아이디", example = "admin")
private String id;
@Schema(description = "관리자 권한 코드", example = "A")
private String userRole;
@Schema(description = "회원 유형 코드. A: 관리자", example = "A")
private String userType;
@JsonIgnore
@Schema(hidden = true)
private String newPassword;
@JsonIgnore
@Schema(hidden = true)
private String password;
@JsonIgnore
@Schema(hidden = true)
private int resultCode;
}
@@ -7,6 +7,7 @@ import lombok.Setter;
import java.time.Instant;
@Schema(description = "API Key 로그인 응답")
@Getter
@Setter
public class LoginDto {
@@ -18,19 +19,34 @@ public class LoginDto {
private String refreshToken;
@JsonIgnore
@Schema(hidden = true)
private String password;
@JsonIgnore
@Schema(hidden = true)
private String userRole;
@JsonIgnore
@Schema(hidden = true)
private String userType;
@JsonIgnore
@Schema(hidden = true)
private String userApiKey;
@JsonIgnore
@Schema(hidden = true)
private Integer userTokenIdx;
@JsonIgnore
@Schema(hidden = true)
private Instant expiresAt;
@JsonIgnore
@Schema(hidden = true)
private Integer userIdx;
@JsonIgnore
@Schema(hidden = true)
private int resultCode;
}
@@ -1,8 +1,10 @@
package com.alist.api.modules.auth.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
@Schema(description = "SSO 인가 코드 교환 응답")
@Getter
@Setter
public class SsoExchangeDto {
@@ -10,10 +12,20 @@ public class SsoExchangeDto {
private String code;
private String clientId;
@Schema(description = "SSO 세션 ID")
private String ssoSessionId;
@Schema(description = "회원 PK", example = "1")
private Integer userIdx;
private Integer userTokenIdx;
@Schema(description = "회원 아이디", example = "test")
private String userId;
@Schema(description = "회원 권한 코드(CCD/SCM/I/R/E)", example = "SCM")
private String userRole;
@Schema(description = "회원 유형 코드. T: 교사, S: 학생", example = "T")
private String userType;
}
@@ -1,14 +1,25 @@
package com.alist.api.modules.auth.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
@Schema(description = "SSO 로그인 응답")
@Getter
@Setter
public class SsoLoginCheckDto {
@Schema(description = "로그인 성공 여부", example = "true")
private boolean loginIn;
@Schema(description = "회원 아이디", example = "test")
private String id;
@Schema(description = "회원 PK", example = "1")
private Integer userIdx;
@Schema(description = "회원 권한 코드", example = "CCD/SCM/I/R/E")
private String userRole;
@Schema(description = "회원 유형 코드. T: 교사, S: 학생", example = "S")
private String userType;
}
@@ -7,6 +7,7 @@ import lombok.Setter;
import java.time.Instant;
@Schema(description = "테스트용 토큰 발급 응답")
@Getter
@Setter
public class TokenDto {
@@ -18,5 +19,6 @@ public class TokenDto {
private String refreshToken;
@JsonIgnore
@Schema(hidden = true)
private int resultCode;
}
@@ -6,12 +6,14 @@ import jakarta.validation.constraints.NotBlank;
import lombok.Getter;
import lombok.Setter;
@Schema(description = "API Key 로그인 요청 폼")
@Getter
@Setter
public class ApikeyLoginForm {
@Schema(
description = "api key",
example = "aaaa111dsddd"
description = "사용자 API Key",
example = "YjeWJr9-N-XaJ7ix2M2spnfjOyG9nTmRlY9t6sKw4_M",
requiredMode = Schema.RequiredMode.REQUIRED
)
@NotBlank(message = "apiKey를 입력해주세요.")
private String userApiKey;
@@ -1,15 +1,29 @@
package com.alist.api.modules.auth.form;
import com.alist.api.modules.auth.dto.SsoExchangeDto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Getter;
import lombok.Setter;
@Schema(description = "SSO 인가 코드 교환 요청 폼")
@Getter
@Setter
public class SsoExchangeForm {
@NotBlank
@Schema(
description = "SSO 인가 코드",
example = "authorization-code",
requiredMode = Schema.RequiredMode.REQUIRED
)
@NotBlank(message = "인가 코드는 필수입니다.")
private String code;
@NotBlank
@Schema(
description = "SSO 클라이언트 ID",
example = "alist-main",
requiredMode = Schema.RequiredMode.REQUIRED
)
@NotBlank(message = "클라이언트 ID는 필수입니다.")
private String clientId;
public SsoExchangeDto toSsoExchangeDto() {
@@ -1,15 +1,29 @@
package com.alist.api.modules.auth.form;
import com.alist.api.modules.auth.dto.SsoLoginDto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Getter;
import lombok.Setter;
@Schema(description = "SSO 로그인 요청 폼")
@Getter
@Setter
public class SsoLoginForm {
@NotBlank
@Schema(
description = "회원 아이디",
example = "test",
requiredMode = Schema.RequiredMode.REQUIRED
)
@NotBlank(message = "아이디는 필수입니다.")
private String id;
@NotBlank
@Schema(
description = "비밀번호",
example = "password1234",
requiredMode = Schema.RequiredMode.REQUIRED
)
@NotBlank(message = "비밀번호는 필수입니다.")
private String password;
public SsoLoginDto toSsoLoginDto() {
@@ -1,17 +1,34 @@
package com.alist.api.modules.file.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
@Schema(description = "파일 업로드 응답")
@Getter
@Setter
public class FileUploadVo {
@Schema(description = "업로드 파일 접근 경로", example = "/upload/editor/2026/05/19")
private String uploadPath;
@Schema(description = "원본 파일명", example = "sample.png")
private String originalFileName;
@Schema(description = "저장 파일명", example = "a1b2c3d4.png")
private String storedFileName;
@Schema(description = "파일 확장자", example = "png")
private String fileExtension;
@Schema(description = "Content-Type", example = "image/png")
private String contentType;
@Schema(description = "파일 크기(byte)", example = "102400")
private Long fileSize;
@Schema(description = "이미지 너비(px)", example = "800")
private Integer width;
@Schema(description = "이미지 높이(px)", example = "600")
private Integer height;
}
@@ -1,24 +1,33 @@
package com.alist.api.modules.file.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Schema(description = "SunEditor 이미지 업로드 응답")
@Getter
@Setter
public class SunEditorUploadVo {
@Schema(description = "업로드 결과 목록")
private List<Item> result;
public SunEditorUploadVo(List<Item> result) {
this.result = result;
}
@Schema(description = "SunEditor 업로드 파일 항목")
@Getter
@Setter
public static class Item {
@Schema(description = "업로드 파일 URL", example = "https://api.alist.co.kr/upload/editor/sample.png")
private String url;
@Schema(description = "원본 파일명", example = "sample.png")
private String name;
@Schema(description = "파일 크기(byte)", example = "102400")
private Long size;
public Item(String url, String name, Long size) {
@@ -1,8 +1,10 @@
package com.alist.api.modules.main;
import io.swagger.v3.oas.annotations.Hidden;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Hidden
@Controller
public class MainController {
@@ -1,15 +1,28 @@
package com.alist.api.modules.migration.alist.user.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
@Schema(description = "ALIST 이전 회원 정보")
@Getter
@Setter
public class AlistUserVo {
@Schema(description = "이전 회원 아이디", example = "test")
private String id;
@Schema(description = "이전 회원 이름", example = "홍길동")
private String name;
@Schema(description = "이전 회원 휴대폰 번호", example = "01012345678")
private String hp;
@Schema(description = "이전 회원 이메일", example = "user@example.com")
private String email;
@Schema(description = "이전 회원 구분", example = "T/S")
private String type;
@Schema(description = "이전 회원 가입일", example = "2026-05-19")
private String joinDate;
}
@@ -1,15 +1,28 @@
package com.alist.api.modules.migration.eltown.user.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
@Schema(description = "ELTOWN 이전 회원 정보")
@Getter
@Setter
public class EltownUserVo {
@Schema(description = "이전 회원 아이디", example = "test")
private String id;
@Schema(description = "이전 회원 이름", example = "홍길동")
private String name;
@Schema(description = "이전 회원 휴대폰 번호", example = "01012345678")
private String hp;
@Schema(description = "이전 회원 이메일", example = "user@example.com")
private String email;
@Schema(description = "이전 회원 구분", example = "S")
private String type;
@Schema(description = "이전 회원 가입일", example = "2026-05-19")
private String joinDate;
}
@@ -1,20 +1,33 @@
package com.alist.api.modules.tusFile.dto;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
@Schema(description = "파일 다운로드 처리 DTO")
@Getter
@Setter
public class FileDownloadDto {
@Schema(description = "파일 상세 PK", example = "1")
private Long fileDetailIdx;
private Long fileMasterIdx;
private String fileUuid;
private String originName;
private String contentType;
private Long sizeBytes;
private String savePath;
@Schema(description = "파일 마스터 PK", example = "1")
private Long fileMasterIdx;
@Schema(description = "파일 UUID", example = "018f6f2d-7b5c-7d49-9c3a-8c0f4f9f9f10")
private String fileUuid;
@Schema(description = "원본 파일명", example = "sample.png")
private String originName;
@Schema(description = "Content-Type", example = "image/png")
private String contentType;
@Schema(description = "파일 크기(byte)", example = "102400")
private Long sizeBytes;
private String savePath;
private Integer userIdx;
private Integer userTokenIdx;
private String eventType;
@@ -23,5 +36,6 @@ public class FileDownloadDto {
private String referer;
@JsonIgnore
@Schema(hidden = true)
private Integer resultCode;
}
@@ -1,17 +1,34 @@
package com.alist.api.modules.tusFile.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
@Schema(description = "다운로드 파일 항목")
@Getter
@Setter
public class FileDownloadItemDto {
@Schema(description = "파일 상세 PK", example = "1")
private Long fileDetailIdx;
@Schema(description = "파일 마스터 PK", example = "1")
private Long fileMasterIdx;
@Schema(description = "파일 UUID", example = "018f6f2d-7b5c-7d49-9c3a-8c0f4f9f9f10")
private String fileUuid;
@Schema(description = "원본 파일명", example = "sample.png")
private String originName;
@Schema(description = "Content-Type", example = "image/png")
private String contentType;
@Schema(description = "파일 크기(byte)", example = "102400")
private Long sizeBytes;
@Schema(description = "미리보기 URL")
private String viewUrl;
@Schema(description = "다운로드 URL")
private String downloadUrl;
}
@@ -1,17 +1,23 @@
package com.alist.api.modules.tusFile.dto;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Schema(description = "파일 다운로드 목록 응답")
@Getter
@Setter
public class FileDownloadListDto {
@Schema(description = "파일 마스터 PK", example = "1")
private Long fileMasterIdx;
@Schema(description = "다운로드 파일 목록")
private List<FileDownloadItemDto> itemList;
@JsonIgnore
@Schema(hidden = true)
private Integer resultCode;
}
@@ -1,29 +1,46 @@
package com.alist.api.modules.tusFile.dto;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Schema(description = "TUS 파일 업로드 초기화 응답")
@Getter
@Setter
public class FileUploadDto {
@Schema(description = "파일 마스터 PK", example = "1")
private Long fileMasterIdx;
@Schema(description = "TUS 업로드 엔드포인트", example = "/tusFiles/upload")
private String tusEndpoint;
@JsonIgnore
@Schema(hidden = true)
private String fileCategory;
@JsonIgnore
@Schema(hidden = true)
private String folderPath;
@JsonIgnore
@Schema(hidden = true)
private Integer userTokenIdx;
@JsonIgnore
@Schema(hidden = true)
private Integer userIdx;
@JsonIgnore
@Schema(hidden = true)
private Integer status;
@JsonIgnore
@Schema(hidden = true)
private Integer totalCount;
@Schema(description = "업로드 파일 항목 목록")
private List<FileUploadItemDto> itemList;
}
@@ -1,23 +1,39 @@
package com.alist.api.modules.tusFile.dto;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
@Schema(description = "TUS 업로드 파일 항목 응답")
@Getter
@Setter
public class FileUploadItemDto {
@Schema(description = "파일 순번", example = "1")
private Integer fileSeq;
@Schema(description = "파일 UUID", example = "018f6f2d-7b5c-7d49-9c3a-8c0f4f9f9f10")
private String fileUuid;
@Schema(description = "TUS 업로드 토큰")
private String uploadToken;
@Schema(description = "원본 파일명", example = "sample.png")
private String originName;
@Schema(description = "파일 크기(byte)", example = "102400")
private Long sizeBytes;
@Schema(description = "Content-Type", example = "image/png")
private String contentType;
@JsonIgnore
@Schema(hidden = true)
private Long fileMasterIdx;
@JsonIgnore
@Schema(hidden = true)
private Integer userIdx;
@JsonIgnore
@Schema(hidden = true)
private Integer status;
}
@@ -1,15 +1,28 @@
package com.alist.api.modules.tusFile.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
@Schema(description = "업로드 상태 조회 응답")
@Getter
@Setter
public class UploadStatusDto {
@Schema(description = "파일 UUID", example = "018f6f2d-7b5c-7d49-9c3a-8c0f4f9f9f10")
private String fileUuid;
@Schema(description = "업로드 상태. PENDING, UPLOADING, DONE, FAILED", example = "UPLOADING")
private String status; // PENDING, UPLOADING, DONE, FAILED
@Schema(description = "업로드 완료 byte", example = "51200")
private Long uploadedBytes;
@Schema(description = "전체 파일 크기 byte", example = "102400")
private Long totalBytes;
@Schema(description = "업로드 진행률", example = "50")
private Integer percent;
@Schema(description = "상태 갱신 시각", example = "2026-05-19T10:30:00")
private String updatedAt;
}
@@ -1,23 +1,35 @@
package com.alist.api.modules.tusFile.dto;
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 UploadTokenDto {
private Integer userTokenIdx;
@Schema(description = "파일 UUID", example = "018f6f2d-7b5c-7d49-9c3a-8c0f4f9f9f10")
private String fileUuid;
@Schema(description = "업로드 토큰")
private String uploadToken;
private String uploadMetadataRaw;
private String uploadLengthRaw;
@Schema(description = "원본 파일명", example = "sample.png")
private String originName;
@Schema(description = "파일 크기(byte)", example = "102400")
private Long sizeBytes;
@Schema(description = "Content-Type", example = "image/png")
private String contentType;
@JsonIgnore
@Schema(hidden = true)
private Integer resultCode;
}
@@ -1,14 +1,21 @@
package com.alist.api.modules.tusFile.form;
import com.alist.api.modules.tusFile.dto.FileDeleteDto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Getter;
import lombok.Setter;
@Schema(description = "파일 삭제 요청 폼")
@Getter
@Setter
public class FileDeleteForm {
@NotBlank
@Schema(
description = "파일 UUID",
example = "018f6f2d-7b5c-7d49-9c3a-8c0f4f9f9f10",
requiredMode = Schema.RequiredMode.REQUIRED
)
@NotBlank(message = "파일 UUID는 필수입니다.")
private String fileUuid;
public FileDeleteDto toFileDeleteDto() {
@@ -2,6 +2,7 @@ package com.alist.api.modules.tusFile.form;
import com.alist.api.modules.tusFile.dto.FileUploadDto;
import com.alist.api.modules.tusFile.dto.FileUploadItemDto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
@@ -11,15 +12,26 @@ import lombok.Setter;
import java.util.ArrayList;
import java.util.List;
@Schema(description = "TUS 파일 업로드 초기화 요청 폼")
@Getter
@Setter
public class FileUploadForm {
@NotBlank
@Schema(
description = "파일 카테고리",
example = "BOARD",
requiredMode = Schema.RequiredMode.REQUIRED
)
@NotBlank(message = "파일 카테고리는 필수입니다.")
private String fileCategory;
@Schema(description = "업로드 폴더 경로. 미전달 시 / 로 처리됩니다.", example = "/board")
private String folderPath;
@NotEmpty
@Schema(
description = "업로드 파일 목록",
requiredMode = Schema.RequiredMode.REQUIRED
)
@NotEmpty(message = "업로드 파일 목록은 필수입니다.")
@Valid
private List<FileUploadItemForm> itemList;
@@ -1,21 +1,33 @@
package com.alist.api.modules.tusFile.form;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import lombok.Getter;
import lombok.Setter;
@Schema(description = "TUS 업로드 파일 항목")
@Getter
@Setter
public class FileUploadItemForm {
@NotBlank
@Schema(
description = "원본 파일명",
example = "sample.png",
requiredMode = Schema.RequiredMode.REQUIRED
)
@NotBlank(message = "원본 파일명은 필수입니다.")
private String originName;
@NotNull
@Positive
@Schema(
description = "파일 크기(byte)",
example = "102400",
requiredMode = Schema.RequiredMode.REQUIRED
)
@NotNull(message = "파일 크기는 필수입니다.")
@Positive(message = "파일 크기는 0보다 커야 합니다.")
private Long sizeBytes;
@Schema(description = "Content-Type", example = "image/png")
private String contentType;
}
@@ -2,13 +2,16 @@ package com.alist.api.modules.tusFile.form;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
@Schema(description = "TUS 훅 이벤트 상세 정보")
@Getter
@Setter
@JsonIgnoreProperties(ignoreUnknown = true)
public class TusHookEventForm {
@Schema(description = "TUS 업로드 정보")
@JsonProperty("Upload")
private TusHookUploadForm tusHookUploadForm;
}
@@ -3,16 +3,20 @@ package com.alist.api.modules.tusFile.form;
import com.alist.api.modules.tusFile.dto.TusHookDto;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
@Schema(description = "TUS 훅 요청 폼")
@Getter
@Setter
@JsonIgnoreProperties(ignoreUnknown = true)
public class TusHookForm {
@Schema(description = "TUS 훅 이벤트 타입", example = "post-finish")
@JsonProperty("Type")
private String type;
@Schema(description = "TUS 훅 이벤트 정보")
@JsonProperty("Event")
private TusHookEventForm tusHookEventForm;
@@ -2,24 +2,33 @@ package com.alist.api.modules.tusFile.form;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
import java.util.Map;
@Schema(description = "TUS 훅 업로드 정보")
@Getter
@Setter
@JsonIgnoreProperties(ignoreUnknown = true)
public class TusHookUploadForm {
@Schema(description = "TUS 업로드 ID", example = "018f6f2d7b5c7d499c3a8c0f4f9f9f10")
@JsonProperty("ID")
private String id;
@Schema(description = "업로드 파일 전체 크기(byte)", example = "102400")
@JsonProperty("Size")
private Long size;
@Schema(description = "현재 업로드 offset(byte)", example = "102400")
@JsonProperty("Offset")
private Long offset;
@Schema(
description = "TUS 메타데이터. fileUuid, filename, filetype 등을 포함합니다.",
example = "{\"fileUuid\":\"018f6f2d-7b5c-7d49-9c3a-8c0f4f9f9f10\",\"filename\":\"sample.png\",\"filetype\":\"image/png\"}"
)
@JsonProperty("MetaData")
private Map<String, String> metaData;
}
@@ -1,12 +1,19 @@
package com.alist.api.modules.tusFile.form;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Getter;
import lombok.Setter;
@Schema(description = "업로드 취소 요청 폼")
@Getter
@Setter
public class UploadCancelForm {
@NotBlank
@Schema(
description = "파일 UUID",
example = "018f6f2d-7b5c-7d49-9c3a-8c0f4f9f9f10",
requiredMode = Schema.RequiredMode.REQUIRED
)
@NotBlank(message = "파일 UUID는 필수입니다.")
private String fileUuid;
}
@@ -1,14 +1,21 @@
package com.alist.api.modules.tusFile.form;
import com.alist.api.modules.tusFile.dto.UploadStatusDto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Getter;
import lombok.Setter;
@Schema(description = "업로드 상태 조회 요청 폼")
@Getter
@Setter
public class UploadStatusForm {
@NotBlank
@Schema(
description = "파일 UUID",
example = "018f6f2d-7b5c-7d49-9c3a-8c0f4f9f9f10",
requiredMode = Schema.RequiredMode.REQUIRED
)
@NotBlank(message = "파일 UUID는 필수입니다.")
private String fileUuid;
public UploadStatusDto toUploadStatusDto() {
@@ -1,6 +1,7 @@
package com.alist.api.modules.user.dto;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
@@ -13,9 +14,14 @@ public class UserDto {
private String userType;
@JsonIgnore
@Schema(hidden = true)
private String newPassword;
@JsonIgnore
@Schema(hidden = true)
private String password;
@JsonIgnore
@Schema(hidden = true)
private int resultCode;
}
@@ -1,16 +1,28 @@
package com.alist.api.modules.user.form;
import com.alist.api.modules.user.dto.UserPasswordChangeDto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Getter;
import lombok.Setter;
@Schema(description = "사용자 비밀번호 수정 요청 폼")
@Getter
@Setter
public class UserPasswordChangeForm {
@Schema(
description = "현재 비밀번호",
example = "password1234",
requiredMode = Schema.RequiredMode.REQUIRED
)
@NotBlank(message = "현재 비밀번호는 필수입니다.")
private String currentPassword;
@Schema(
description = "새 비밀번호",
example = "newPassword1234",
requiredMode = Schema.RequiredMode.REQUIRED
)
@NotBlank(message = "새 비밀번호는 필수입니다.")
private String newPassword;
@@ -1,13 +1,20 @@
package com.alist.api.modules.user.form;
import com.alist.api.modules.user.dto.UserPasswordCheckDto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Getter;
import lombok.Setter;
@Schema(description = "사용자 비밀번호 확인 요청 폼")
@Getter
@Setter
public class UserPasswordCheckForm {
@Schema(
description = "현재 비밀번호",
example = "password1234",
requiredMode = Schema.RequiredMode.REQUIRED
)
@NotBlank(message = "비밀번호는 필수입니다.")
private String password;
@@ -1,20 +1,29 @@
package com.alist.api.modules.user.form;
import com.alist.api.modules.user.dto.UserProfileUpdateDto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import lombok.Getter;
import lombok.Setter;
@Schema(description = "사용자 개인정보 수정 요청 폼")
@Getter
@Setter
public class UserProfileUpdateForm {
@Schema(
description = "현재 비밀번호",
example = "password1234",
requiredMode = Schema.RequiredMode.REQUIRED
)
@NotBlank(message = "현재 비밀번호는 필수입니다.")
private String currentPassword;
@Schema(description = "이메일. 수정할 때만 전달합니다.", example = "user@example.com")
@Email(message = "이메일 형식을 확인해주세요.")
private String email;
@Schema(description = "휴대폰 번호. 수정할 때만 전달합니다.", example = "01012345678")
private String hp;
public UserProfileUpdateDto toUserProfileUpdateDto() {
@@ -2,14 +2,19 @@ package com.alist.api.modules.user.vo;
import com.alist.api.modules.migration.alist.user.vo.AlistUserVo;
import com.alist.api.modules.migration.eltown.user.vo.EltownUserVo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Schema(description = "이전 회원 조회 응답")
@Getter
@Setter
public class MigrationUserVo {
@Schema(description = "ALIST 이전 회원 목록")
List<AlistUserVo> alistUserList;
@Schema(description = "ELTOWN 이전 회원 목록")
List<EltownUserVo> eltownUserList;
}
@@ -1,14 +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 UserPasswordChangeVo {
@Schema(description = "비밀번호 수정 여부", example = "true")
private boolean passwordChanged;
@JsonIgnore
@Schema(hidden = true)
private Integer resultCode;
}
@@ -1,14 +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 UserPasswordCheckVo {
@Schema(description = "비밀번호 일치 여부", example = "true")
private boolean passwordMatched;
@JsonIgnore
@Schema(hidden = true)
private Integer resultCode;
}
@@ -1,14 +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 UserProfileUpdateVo {
@Schema(description = "개인정보 수정 여부", example = "true")
private boolean profileUpdated;
@JsonIgnore
@Schema(hidden = true)
private Integer resultCode;
}
@@ -1,21 +1,33 @@
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 = "사용자 회원가입 응답")
@Setter
@Getter
public class UserVo {
@Schema(description = "회원 PK", example = "1")
private Integer userIdx;
@Schema(description = "회원 아이디", example = "test")
private String id;
@Schema(description = "회원 권한 코드", example = "CCD/SCM/I/R/E")
private String userRole;
@Schema(description = "회원 유형 코드. T: 교사, S: 학생", example = "S")
private String userType;
@JsonIgnore
@Schema(hidden = true)
private String newPassword;
@JsonIgnore
@Schema(hidden = true)
private String password;
@JsonIgnore
@Schema(hidden = true)
private int resultCode;
}
@@ -26,10 +26,10 @@
UPDATE ALISTLMS.test_user ATU
INNER JOIN ALISTLMS.test_user_token ATUT ON ATUT.user_idx = ATU.user_idx
SET ATU.update_at = now()
<if test="email != null">
<if test="email != null and email != ''">
, ATU.email = #{email}
</if>
<if test="hp != null">
<if test="hp != null and hp != ''">
, ATU.hp = #{hp}
</if>
WHERE ATUT.user_token_idx = #{userTokenIdx}
@@ -27,6 +27,15 @@
WHERE user_token_idx = #{userTokenIdx}
AND user_type IN ('A')
</update>
<update id="updateAdminApiKeyLoginRefreshToken">
/*AdminLoginMapper.updateAdminApiKeyLoginRefreshToken*/
UPDATE ALISTLMS.test_user_token
SET refresh_token = #{refreshToken}
, expires_at = #{expiresAt}
, updated_at = now()
WHERE user_token_idx = #{userTokenIdx}
AND user_type IN ('A')
</update>
<select id="selectAdminLoginById" resultType="com.alist.api.modules.admin.auth.vo.AdminLoginVo">
/*AdminLoginMapper.selectAdminLoginById*/
SELECT ATU.user_idx
@@ -57,4 +66,18 @@
AND ATU.del_yn = 1
LIMIT 1
</select>
<select id="selectAdminApiKeyLogin" resultType="com.alist.api.modules.admin.auth.vo.AdminApiKeyLoginVo">
/*AdminLoginMapper.selectAdminApiKeyLogin*/
SELECT ATU.user_idx
, ATU.id
, ATUT.user_token_idx
, ATUT.user_role
, ATUT.user_type
FROM ALISTLMS.test_user_token ATUT
INNER JOIN ALISTLMS.test_user ATU ON ATU.user_idx = ATUT.user_idx
WHERE ATUT.user_api_key = #{userApiKey}
AND ATUT.user_type IN ('A')
AND ATU.del_yn = 1
LIMIT 1
</select>
</mapper>
@@ -0,0 +1,97 @@
<?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">
<mapper namespace="com.alist.api.modules.admin.member.mapper.AdminMemberMapper">
<sql id="adminMemberWhere">
FROM ALISTLMS.test_user ATU
INNER JOIN ALISTLMS.test_user_token ATUT ON ATUT.user_idx = ATU.user_idx
WHERE ATU.del_yn = 1
<if test="keyword != null and keyword != ''">
AND (
ATU.id LIKE CONCAT('%', #{keyword}, '%')
OR ATU.email LIKE CONCAT('%', #{keyword}, '%')
OR ATU.hp LIKE CONCAT('%', #{keyword}, '%')
)
</if>
<if test="userRole != null and userRole != ''">
AND ATUT.user_role = #{userRole}
</if>
<if test="userType != null and userType != ''">
AND ATUT.user_type = #{userType}
</if>
<if test="dormantYn != null and dormantYn != ''">
AND ATU.dormant_yn = #{dormantYn}
</if>
<if test="withdrawStatus != null and withdrawStatus != ''">
AND ATU.withdraw_status = #{withdrawStatus}
</if>
</sql>
<update id="updateAdminMember">
/*AdminMemberMapper.updateAdminMember*/
UPDATE ALISTLMS.test_user ATU
INNER JOIN ALISTLMS.test_user_token ATUT ON ATUT.user_idx = ATU.user_idx
SET ATU.update_at = now()
, ATUT.updated_at = now()
<if test="email != null and email != ''">
, ATU.email = #{email}
</if>
<if test="hp != null and hp != ''">
, ATU.hp = #{hp}
</if>
<if test="userRole != null and userRole != ''">
, ATUT.user_role = #{userRole}
</if>
<if test="userType != null and userType != ''">
, ATUT.user_type = #{userType}
</if>
WHERE ATU.user_idx = #{userIdx}
AND ATU.del_yn = 1
</update>
<select id="selectAdminMemberCount" resultType="int">
/*AdminMemberMapper.selectAdminMemberCount*/
SELECT COUNT(*)
<include refid="adminMemberWhere"/>
</select>
<select id="selectAdminMemberList" resultType="com.alist.api.modules.admin.member.vo.AdminMemberVo">
/*AdminMemberMapper.selectAdminMemberList*/
SELECT ATU.user_idx
, ATUT.user_token_idx
, ATU.id
, ATU.email
, ATU.hp
, ATUT.user_role
, ATUT.user_type
, ATU.dormant_yn
, ATU.dormant_at
, ATU.withdraw_status
, ATU.withdraw_at
, ATU.create_at
, ATU.update_at
<include refid="adminMemberWhere"/>
ORDER BY ATU.user_idx DESC
LIMIT #{limit}
OFFSET #{offset}
</select>
<select id="selectAdminMemberDetail" resultType="com.alist.api.modules.admin.member.vo.AdminMemberDetailVo">
/*AdminMemberMapper.selectAdminMemberDetail*/
SELECT ATU.user_idx
, ATUT.user_token_idx
, ATU.id
, ATU.email
, ATU.hp
, ATUT.user_role
, ATUT.user_type
, ATU.dormant_yn
, ATU.dormant_at
, ATU.withdraw_status
, ATU.withdraw_at
, ATU.create_at
, ATU.update_at
FROM ALISTLMS.test_user ATU
INNER JOIN ALISTLMS.test_user_token ATUT ON ATUT.user_idx = ATU.user_idx
WHERE ATU.del_yn = 1
AND ATU.user_idx = #{userIdx}
</select>
</mapper>