diff --git a/src/main/java/page/clab/api/domain/hiring/application/application/dto/request/ApplicationRequestDto.java b/src/main/java/page/clab/api/domain/hiring/application/application/dto/request/ApplicationRequestDto.java index a595cb8fd..67c1781aa 100644 --- a/src/main/java/page/clab/api/domain/hiring/application/application/dto/request/ApplicationRequestDto.java +++ b/src/main/java/page/clab/api/domain/hiring/application/application/dto/request/ApplicationRequestDto.java @@ -28,7 +28,7 @@ public class ApplicationRequestDto { private String contact; @NotNull(message = "{notNull.application.email}") - @Schema(description = "이메일", example = "clab.coreteam@gamil.com", required = true) + @Schema(description = "이메일", example = "clab.coreteam@gmail.com", required = true) private String email; @NotNull(message = "{notNull.application.department}") diff --git a/src/main/java/page/clab/api/domain/memberManagement/executive/adapter/in/web/ExecutiveRegisterController.java b/src/main/java/page/clab/api/domain/memberManagement/executive/adapter/in/web/ExecutiveRegisterController.java new file mode 100644 index 000000000..c206fdcf3 --- /dev/null +++ b/src/main/java/page/clab/api/domain/memberManagement/executive/adapter/in/web/ExecutiveRegisterController.java @@ -0,0 +1,33 @@ +package page.clab.api.domain.memberManagement.executive.adapter.in.web; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import page.clab.api.domain.memberManagement.executive.application.dto.request.ExecutiveRequestDto; +import page.clab.api.domain.memberManagement.executive.application.port.in.RegisterExecutiveUseCase; +import page.clab.api.global.common.dto.ApiResponse; + +@RestController +@RequestMapping("/api/v1/executive") +@RequiredArgsConstructor +@Tag(name = "Member Management - Executive", description = "운영진") +public class ExecutiveRegisterController { + + private final RegisterExecutiveUseCase registerExecutiveUseCase; + + @Operation(summary = "[A] 운영진 등록", description = "ROLE_ADMIN 이상의 권한이 필요함") + @PreAuthorize("hasRole('ADMIN')") + @PostMapping("") + public ApiResponse registerExecutive( + @Valid @RequestBody ExecutiveRequestDto requestDto + ) { + String id = registerExecutiveUseCase.registerExecutive(requestDto); + return ApiResponse.success(id); + } +} diff --git a/src/main/java/page/clab/api/domain/memberManagement/executive/adapter/in/web/ExecutiveRemoveController.java b/src/main/java/page/clab/api/domain/memberManagement/executive/adapter/in/web/ExecutiveRemoveController.java new file mode 100644 index 000000000..5dd794c87 --- /dev/null +++ b/src/main/java/page/clab/api/domain/memberManagement/executive/adapter/in/web/ExecutiveRemoveController.java @@ -0,0 +1,31 @@ +package page.clab.api.domain.memberManagement.executive.adapter.in.web; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import page.clab.api.domain.memberManagement.executive.application.port.in.RemoveExecutiveUseCase; +import page.clab.api.global.common.dto.ApiResponse; + +@RestController +@RequestMapping("/api/v1/executive") +@RequiredArgsConstructor +@Tag(name = "Member Management - Executive", description = "운영진") +public class ExecutiveRemoveController { + + private final RemoveExecutiveUseCase removeExecutiveUseCase; + + @Operation(summary = "[A] 운영진 정보 삭제", description = "ROLE_ADMIN 이상의 권한이 필요함") + @PreAuthorize("hasRole('ADMIN')") + @DeleteMapping("/{executiveId}") + public ApiResponse removeMember( + @PathVariable(name = "executiveId") String executiveId + ) { + String id = removeExecutiveUseCase.removeExecutive(executiveId); + return ApiResponse.success(id); + } +} diff --git a/src/main/java/page/clab/api/domain/memberManagement/executive/adapter/in/web/ExecutiveRetrievalController.java b/src/main/java/page/clab/api/domain/memberManagement/executive/adapter/in/web/ExecutiveRetrievalController.java new file mode 100644 index 000000000..683c36bf5 --- /dev/null +++ b/src/main/java/page/clab/api/domain/memberManagement/executive/adapter/in/web/ExecutiveRetrievalController.java @@ -0,0 +1,30 @@ +package page.clab.api.domain.memberManagement.executive.adapter.in.web; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import page.clab.api.domain.memberManagement.executive.application.dto.response.ExecutiveResponseDto; +import page.clab.api.domain.memberManagement.executive.application.port.in.RetrieveExecutiveUseCase; +import page.clab.api.global.common.dto.ApiResponse; + +@RestController +@RequestMapping("/api/v1/executive") +@RequiredArgsConstructor +@Tag(name = "Member Management - Executive", description = "운영진") +public class ExecutiveRetrievalController { + + private final RetrieveExecutiveUseCase retrieveExecutiveUseCase; + + @Operation(summary = "[G] 운영진 정보 조회", description = "ROLE_GUEST 이상의 권한이 필요함") + @PreAuthorize("hasRole('GUEST')") + @GetMapping("") + public ApiResponse> retrieveExecutives() { + List executives = retrieveExecutiveUseCase.retrieveExecutives(); + return ApiResponse.success(executives); + } +} diff --git a/src/main/java/page/clab/api/domain/memberManagement/executive/adapter/in/web/ExecutiveUpdateController.java b/src/main/java/page/clab/api/domain/memberManagement/executive/adapter/in/web/ExecutiveUpdateController.java new file mode 100644 index 000000000..e02be431a --- /dev/null +++ b/src/main/java/page/clab/api/domain/memberManagement/executive/adapter/in/web/ExecutiveUpdateController.java @@ -0,0 +1,34 @@ +package page.clab.api.domain.memberManagement.executive.adapter.in.web; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import page.clab.api.domain.memberManagement.executive.application.dto.request.ExecutiveUpdateRequestDto; +import page.clab.api.domain.memberManagement.executive.application.port.in.UpdateExecutiveUseCase; +import page.clab.api.global.common.dto.ApiResponse; + +@RestController +@RequestMapping("/api/v1/executive") +@RequiredArgsConstructor +@Tag(name = "Member Management - Executive", description = "운영진") +public class ExecutiveUpdateController { + + private final UpdateExecutiveUseCase updateExecutiveUseCase; + + @Operation(summary = "[A] 운영진 정보 수정", description = "ROLE_ADMIN 이상의 권한이 필요함") + @PreAuthorize("hasRole('ADMIN')") + @PatchMapping("/{executiveId}") + public ApiResponse updateExecutive( + @PathVariable(name = "executiveId") String executiveId, + @RequestBody ExecutiveUpdateRequestDto requestDto + ) { + String id = updateExecutiveUseCase.updateExecutive(executiveId, requestDto); + return ApiResponse.success(id); + } +} diff --git a/src/main/java/page/clab/api/domain/memberManagement/executive/adapter/out/persistence/ExecutiveJpaEntity.java b/src/main/java/page/clab/api/domain/memberManagement/executive/adapter/out/persistence/ExecutiveJpaEntity.java new file mode 100644 index 000000000..70efb3175 --- /dev/null +++ b/src/main/java/page/clab/api/domain/memberManagement/executive/adapter/out/persistence/ExecutiveJpaEntity.java @@ -0,0 +1,51 @@ +package page.clab.api.domain.memberManagement.executive.adapter.out.persistence; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.Size; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.hibernate.annotations.SQLDelete; +import org.hibernate.annotations.SQLRestriction; +import page.clab.api.global.common.domain.BaseEntity; + +@Entity +@Table(name = "executive") +@Getter +@Setter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@SQLDelete(sql = "UPDATE executive SET is_deleted = true WHERE id = ?") +@SQLRestriction("is_deleted = false") +public class ExecutiveJpaEntity extends BaseEntity { + + @Id + @Column(nullable = false, updatable = false, unique = true) + @Size(min = 9, max = 9, message = "{size.executive.id}") + private String id; + + @Column(nullable = false) + @Size(min = 1, max = 10, message = "{size.executive.name}") + private String name; + + @Column(nullable = false) + @Email(message = "{email.executive.email}") + @Size(min = 1, message = "{size.executive.email}") + private String email; + + @Column(nullable = false) + private String interests; + + private String imageUrl; + + @Column(name = "is_deleted", nullable = false) + private Boolean isDeleted; +} diff --git a/src/main/java/page/clab/api/domain/memberManagement/executive/adapter/out/persistence/ExecutiveMapper.java b/src/main/java/page/clab/api/domain/memberManagement/executive/adapter/out/persistence/ExecutiveMapper.java new file mode 100644 index 000000000..732fe5d96 --- /dev/null +++ b/src/main/java/page/clab/api/domain/memberManagement/executive/adapter/out/persistence/ExecutiveMapper.java @@ -0,0 +1,12 @@ +package page.clab.api.domain.memberManagement.executive.adapter.out.persistence; + +import org.mapstruct.Mapper; +import page.clab.api.domain.memberManagement.executive.domain.Executive; + +@Mapper(componentModel = "spring") +public interface ExecutiveMapper { + + ExecutiveJpaEntity toEntity(Executive executive); + + Executive toDomain(ExecutiveJpaEntity jpaEntity); +} diff --git a/src/main/java/page/clab/api/domain/memberManagement/executive/adapter/out/persistence/ExecutivePersistenceAdapter.java b/src/main/java/page/clab/api/domain/memberManagement/executive/adapter/out/persistence/ExecutivePersistenceAdapter.java new file mode 100644 index 000000000..726a8c8ac --- /dev/null +++ b/src/main/java/page/clab/api/domain/memberManagement/executive/adapter/out/persistence/ExecutivePersistenceAdapter.java @@ -0,0 +1,56 @@ +package page.clab.api.domain.memberManagement.executive.adapter.out.persistence; + +import java.util.List; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import page.clab.api.domain.memberManagement.executive.application.port.out.RegisterExecutivePort; +import page.clab.api.domain.memberManagement.executive.application.port.out.RetrieveExecutivePort; +import page.clab.api.domain.memberManagement.executive.application.port.out.UpdateExecutivePort; +import page.clab.api.domain.memberManagement.executive.domain.Executive; +import page.clab.api.global.exception.NotFoundException; + +@Component +@RequiredArgsConstructor +public class ExecutivePersistenceAdapter implements + RegisterExecutivePort, + RetrieveExecutivePort, + UpdateExecutivePort { + + private final ExecutiveMapper executiveMapper; + private final ExecutiveRepository executiveRepository; + + @Override + public Executive save(Executive executive) { + ExecutiveJpaEntity jpaEntity = executiveMapper.toEntity(executive); + ExecutiveJpaEntity savedEntity = executiveRepository.save(jpaEntity); + return executiveMapper.toDomain(savedEntity); + } + + @Override + public Executive update(Executive executive) { + ExecutiveJpaEntity jpaEntity = executiveMapper.toEntity(executive); + ExecutiveJpaEntity savedEntity = executiveRepository.save(jpaEntity); + return executiveMapper.toDomain(savedEntity); + } + + @Override + public List findAll() { + List jpaEntities = executiveRepository.findAll(); + return jpaEntities.stream() + .map(executiveMapper::toDomain) + .collect(Collectors.toList()); + } + + @Override + public Executive getById(String executiveId) { + ExecutiveJpaEntity jpaEntity = executiveRepository.findById(executiveId) + .orElseThrow(() -> new NotFoundException("[Executive] id: " + executiveId + "에 해당하는 운영진이 존재하지 않습니다.")); + return executiveMapper.toDomain(jpaEntity); + } + + @Override + public Boolean existsById(String executiveId) { + return executiveRepository.existsById(executiveId); + } +} diff --git a/src/main/java/page/clab/api/domain/memberManagement/executive/adapter/out/persistence/ExecutiveRepository.java b/src/main/java/page/clab/api/domain/memberManagement/executive/adapter/out/persistence/ExecutiveRepository.java new file mode 100644 index 000000000..67233a3bd --- /dev/null +++ b/src/main/java/page/clab/api/domain/memberManagement/executive/adapter/out/persistence/ExecutiveRepository.java @@ -0,0 +1,8 @@ +package page.clab.api.domain.memberManagement.executive.adapter.out.persistence; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface ExecutiveRepository extends JpaRepository { +} diff --git a/src/main/java/page/clab/api/domain/memberManagement/executive/application/dto/mapper/ExecutiveDtoMapper.java b/src/main/java/page/clab/api/domain/memberManagement/executive/application/dto/mapper/ExecutiveDtoMapper.java new file mode 100644 index 000000000..52613fbe2 --- /dev/null +++ b/src/main/java/page/clab/api/domain/memberManagement/executive/application/dto/mapper/ExecutiveDtoMapper.java @@ -0,0 +1,32 @@ +package page.clab.api.domain.memberManagement.executive.application.dto.mapper; + +import org.springframework.stereotype.Component; +import page.clab.api.domain.memberManagement.executive.application.dto.request.ExecutiveRequestDto; +import page.clab.api.domain.memberManagement.executive.application.dto.response.ExecutiveResponseDto; +import page.clab.api.domain.memberManagement.executive.domain.Executive; + +@Component +public class ExecutiveDtoMapper { + + public Executive fromDto(ExecutiveRequestDto requestDto) { + return Executive.builder() + .id(requestDto.getExecutiveId()) + .name(requestDto.getName()) + .email(requestDto.getEmail()) + .interests(requestDto.getInterests()) + .imageUrl(requestDto.getImageUrl()) + .isDeleted(false) + .build(); + } + + public ExecutiveResponseDto toDto(Executive executive, String position) { + return ExecutiveResponseDto.builder() + .executiveId(executive.getId()) + .name(executive.getName()) + .email(executive.getEmail()) + .interests(executive.getInterests()) + .position(position) + .imageUrl(executive.getImageUrl()) + .build(); + } +} diff --git a/src/main/java/page/clab/api/domain/memberManagement/executive/application/dto/request/ExecutiveRequestDto.java b/src/main/java/page/clab/api/domain/memberManagement/executive/application/dto/request/ExecutiveRequestDto.java new file mode 100644 index 000000000..702f0c402 --- /dev/null +++ b/src/main/java/page/clab/api/domain/memberManagement/executive/application/dto/request/ExecutiveRequestDto.java @@ -0,0 +1,30 @@ +package page.clab.api.domain.memberManagement.executive.application.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class ExecutiveRequestDto { + + @NotNull(message = "{notNull.executive.executiveId}") + @Schema(description = "학번", example = "202310000") + private String executiveId; + + @NotNull(message = "{notNull.executive.name}") + @Schema(description = "이름", example = "홍길동") + private String name; + + @NotNull(message = "{notNull.executive.email}") + @Schema(description = "이메일", example = "clab.coreteam@gmail.com") + private String email; + + @NotNull(message = "{notNull.executive.interests}") + @Schema(description = "분야", example = "Back-End") + private String interests; + + @Schema(description = "프로필 이미지", example = "https://www.clab.page/assets/dongmin-860f3a1e.jpeg") + private String imageUrl; +} diff --git a/src/main/java/page/clab/api/domain/memberManagement/executive/application/dto/request/ExecutiveUpdateRequestDto.java b/src/main/java/page/clab/api/domain/memberManagement/executive/application/dto/request/ExecutiveUpdateRequestDto.java new file mode 100644 index 000000000..3a26e3580 --- /dev/null +++ b/src/main/java/page/clab/api/domain/memberManagement/executive/application/dto/request/ExecutiveUpdateRequestDto.java @@ -0,0 +1,22 @@ +package page.clab.api.domain.memberManagement.executive.application.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class ExecutiveUpdateRequestDto { + + @Schema(description = "이름", example = "홍길동") + private String name; + + @Schema(description = "이메일", example = "clab.coreteam@gmail.com") + private String email; + + @Schema(description = "분야", example = "Back-End") + private String interests; + + @Schema(description = "프로필 이미지", example = "https://www.clab.page/assets/dongmin-860f3a1e.jpeg") + private String imageUrl; +} diff --git a/src/main/java/page/clab/api/domain/memberManagement/executive/application/dto/response/ExecutiveResponseDto.java b/src/main/java/page/clab/api/domain/memberManagement/executive/application/dto/response/ExecutiveResponseDto.java new file mode 100644 index 000000000..758e82574 --- /dev/null +++ b/src/main/java/page/clab/api/domain/memberManagement/executive/application/dto/response/ExecutiveResponseDto.java @@ -0,0 +1,16 @@ +package page.clab.api.domain.memberManagement.executive.application.dto.response; + +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class ExecutiveResponseDto { + + private String executiveId; + private String name; + private String email; + private String interests; + private String position; + private String imageUrl; +} diff --git a/src/main/java/page/clab/api/domain/memberManagement/executive/application/exception/ExecutiveRegistrationException.java b/src/main/java/page/clab/api/domain/memberManagement/executive/application/exception/ExecutiveRegistrationException.java new file mode 100644 index 000000000..03224e226 --- /dev/null +++ b/src/main/java/page/clab/api/domain/memberManagement/executive/application/exception/ExecutiveRegistrationException.java @@ -0,0 +1,8 @@ +package page.clab.api.domain.memberManagement.executive.application.exception; + +public class ExecutiveRegistrationException extends RuntimeException { + + public ExecutiveRegistrationException(String message) { + super(message); + } +} diff --git a/src/main/java/page/clab/api/domain/memberManagement/executive/application/port/in/RegisterExecutiveUseCase.java b/src/main/java/page/clab/api/domain/memberManagement/executive/application/port/in/RegisterExecutiveUseCase.java new file mode 100644 index 000000000..57833a9f4 --- /dev/null +++ b/src/main/java/page/clab/api/domain/memberManagement/executive/application/port/in/RegisterExecutiveUseCase.java @@ -0,0 +1,7 @@ +package page.clab.api.domain.memberManagement.executive.application.port.in; + +import page.clab.api.domain.memberManagement.executive.application.dto.request.ExecutiveRequestDto; + +public interface RegisterExecutiveUseCase { + String registerExecutive(ExecutiveRequestDto requestDto); +} diff --git a/src/main/java/page/clab/api/domain/memberManagement/executive/application/port/in/RemoveExecutiveUseCase.java b/src/main/java/page/clab/api/domain/memberManagement/executive/application/port/in/RemoveExecutiveUseCase.java new file mode 100644 index 000000000..feaf47215 --- /dev/null +++ b/src/main/java/page/clab/api/domain/memberManagement/executive/application/port/in/RemoveExecutiveUseCase.java @@ -0,0 +1,5 @@ +package page.clab.api.domain.memberManagement.executive.application.port.in; + +public interface RemoveExecutiveUseCase { + String removeExecutive(String executiveId); +} diff --git a/src/main/java/page/clab/api/domain/memberManagement/executive/application/port/in/RetrieveExecutiveUseCase.java b/src/main/java/page/clab/api/domain/memberManagement/executive/application/port/in/RetrieveExecutiveUseCase.java new file mode 100644 index 000000000..8e1225fd3 --- /dev/null +++ b/src/main/java/page/clab/api/domain/memberManagement/executive/application/port/in/RetrieveExecutiveUseCase.java @@ -0,0 +1,8 @@ +package page.clab.api.domain.memberManagement.executive.application.port.in; + +import java.util.List; +import page.clab.api.domain.memberManagement.executive.application.dto.response.ExecutiveResponseDto; + +public interface RetrieveExecutiveUseCase { + List retrieveExecutives(); +} diff --git a/src/main/java/page/clab/api/domain/memberManagement/executive/application/port/in/UpdateExecutiveUseCase.java b/src/main/java/page/clab/api/domain/memberManagement/executive/application/port/in/UpdateExecutiveUseCase.java new file mode 100644 index 000000000..33ee55f0c --- /dev/null +++ b/src/main/java/page/clab/api/domain/memberManagement/executive/application/port/in/UpdateExecutiveUseCase.java @@ -0,0 +1,7 @@ +package page.clab.api.domain.memberManagement.executive.application.port.in; + +import page.clab.api.domain.memberManagement.executive.application.dto.request.ExecutiveUpdateRequestDto; + +public interface UpdateExecutiveUseCase { + String updateExecutive(String executiveId, ExecutiveUpdateRequestDto requestDto); +} diff --git a/src/main/java/page/clab/api/domain/memberManagement/executive/application/port/out/RegisterExecutivePort.java b/src/main/java/page/clab/api/domain/memberManagement/executive/application/port/out/RegisterExecutivePort.java new file mode 100644 index 000000000..a1cab5d14 --- /dev/null +++ b/src/main/java/page/clab/api/domain/memberManagement/executive/application/port/out/RegisterExecutivePort.java @@ -0,0 +1,7 @@ +package page.clab.api.domain.memberManagement.executive.application.port.out; + +import page.clab.api.domain.memberManagement.executive.domain.Executive; + +public interface RegisterExecutivePort { + Executive save(Executive executive); +} diff --git a/src/main/java/page/clab/api/domain/memberManagement/executive/application/port/out/RetrieveExecutivePort.java b/src/main/java/page/clab/api/domain/memberManagement/executive/application/port/out/RetrieveExecutivePort.java new file mode 100644 index 000000000..c6a1f9186 --- /dev/null +++ b/src/main/java/page/clab/api/domain/memberManagement/executive/application/port/out/RetrieveExecutivePort.java @@ -0,0 +1,13 @@ +package page.clab.api.domain.memberManagement.executive.application.port.out; + +import java.util.List; +import page.clab.api.domain.memberManagement.executive.domain.Executive; + +public interface RetrieveExecutivePort { + + List findAll(); + + Executive getById(String id); + + Boolean existsById(String id); +} diff --git a/src/main/java/page/clab/api/domain/memberManagement/executive/application/port/out/UpdateExecutivePort.java b/src/main/java/page/clab/api/domain/memberManagement/executive/application/port/out/UpdateExecutivePort.java new file mode 100644 index 000000000..0f383e4ae --- /dev/null +++ b/src/main/java/page/clab/api/domain/memberManagement/executive/application/port/out/UpdateExecutivePort.java @@ -0,0 +1,7 @@ +package page.clab.api.domain.memberManagement.executive.application.port.out; + +import page.clab.api.domain.memberManagement.executive.domain.Executive; + +public interface UpdateExecutivePort { + Executive update(Executive executive); +} diff --git a/src/main/java/page/clab/api/domain/memberManagement/executive/application/service/ExecutiveRegisterService.java b/src/main/java/page/clab/api/domain/memberManagement/executive/application/service/ExecutiveRegisterService.java new file mode 100644 index 000000000..34602bfef --- /dev/null +++ b/src/main/java/page/clab/api/domain/memberManagement/executive/application/service/ExecutiveRegisterService.java @@ -0,0 +1,47 @@ +package page.clab.api.domain.memberManagement.executive.application.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import page.clab.api.domain.memberManagement.executive.application.dto.mapper.ExecutiveDtoMapper; +import page.clab.api.domain.memberManagement.executive.application.dto.request.ExecutiveRequestDto; +import page.clab.api.domain.memberManagement.executive.application.exception.ExecutiveRegistrationException; +import page.clab.api.domain.memberManagement.executive.application.port.in.RegisterExecutiveUseCase; +import page.clab.api.domain.memberManagement.executive.application.port.out.RegisterExecutivePort; +import page.clab.api.domain.memberManagement.executive.application.port.out.RetrieveExecutivePort; +import page.clab.api.domain.memberManagement.executive.domain.Executive; +import page.clab.api.external.memberManagement.member.application.port.ExternalRetrieveMemberUseCase; + +@Service +@RequiredArgsConstructor +public class ExecutiveRegisterService implements RegisterExecutiveUseCase { + + private final RegisterExecutivePort registerExecutivePort; + private final RetrieveExecutivePort retrieveExecutivePort; + private final ExecutiveDtoMapper mapper; + private final ExternalRetrieveMemberUseCase externalRetrieveMemberUseCase; + + @Transactional + @Override + public String registerExecutive(ExecutiveRequestDto requestDto) { + checkAlreadyExecutive(requestDto); + checkIsMember(requestDto); + Executive executive = mapper.fromDto(requestDto); + Executive savedExecutive = registerExecutivePort.save(executive); + return savedExecutive.getId(); + } + + private void checkAlreadyExecutive(ExecutiveRequestDto requestDto) { + if (retrieveExecutivePort.existsById(requestDto.getExecutiveId())) { + throw new ExecutiveRegistrationException("[Executive] id: " + requestDto.getExecutiveId() + "에 해당하는 운영진이 " + + "이미 존재합니다."); + } + } + + private void checkIsMember(ExecutiveRequestDto requestDto) { + if (!externalRetrieveMemberUseCase.existsById(requestDto.getExecutiveId())) { + throw new ExecutiveRegistrationException("[Executive] id: " + requestDto.getExecutiveId() + "에 해당하는 운영진이 " + + "멤버가 아닙니다."); + } + } +} diff --git a/src/main/java/page/clab/api/domain/memberManagement/executive/application/service/ExecutiveRemoveService.java b/src/main/java/page/clab/api/domain/memberManagement/executive/application/service/ExecutiveRemoveService.java new file mode 100644 index 000000000..beb54422c --- /dev/null +++ b/src/main/java/page/clab/api/domain/memberManagement/executive/application/service/ExecutiveRemoveService.java @@ -0,0 +1,26 @@ +package page.clab.api.domain.memberManagement.executive.application.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import page.clab.api.domain.memberManagement.executive.application.port.in.RemoveExecutiveUseCase; +import page.clab.api.domain.memberManagement.executive.application.port.out.RegisterExecutivePort; +import page.clab.api.domain.memberManagement.executive.application.port.out.RetrieveExecutivePort; +import page.clab.api.domain.memberManagement.executive.domain.Executive; + +@Service +@RequiredArgsConstructor +public class ExecutiveRemoveService implements RemoveExecutiveUseCase { + + private final RetrieveExecutivePort retrieveExecutivePort; + private final RegisterExecutivePort registerExecutivePort; + + @Transactional + @Override + public String removeExecutive(String executiveId) { + Executive executive = retrieveExecutivePort.getById(executiveId); + executive.delete(); + registerExecutivePort.save(executive); + return executive.getId(); + } +} diff --git a/src/main/java/page/clab/api/domain/memberManagement/executive/application/service/ExecutiveRetrievalService.java b/src/main/java/page/clab/api/domain/memberManagement/executive/application/service/ExecutiveRetrievalService.java new file mode 100644 index 000000000..2a762d925 --- /dev/null +++ b/src/main/java/page/clab/api/domain/memberManagement/executive/application/service/ExecutiveRetrievalService.java @@ -0,0 +1,65 @@ +package page.clab.api.domain.memberManagement.executive.application.service; + +import java.time.LocalDate; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import page.clab.api.domain.memberManagement.executive.application.dto.mapper.ExecutiveDtoMapper; +import page.clab.api.domain.memberManagement.executive.application.dto.response.ExecutiveResponseDto; +import page.clab.api.domain.memberManagement.executive.application.port.in.RetrieveExecutiveUseCase; +import page.clab.api.domain.memberManagement.executive.application.port.out.RetrieveExecutivePort; +import page.clab.api.domain.memberManagement.executive.domain.Executive; +import page.clab.api.domain.memberManagement.position.domain.PositionType; +import page.clab.api.external.memberManagement.position.application.port.ExternalRetrievePositionUseCase; + +@Service +@RequiredArgsConstructor +public class ExecutiveRetrievalService implements RetrieveExecutiveUseCase { + + private final RetrieveExecutivePort retrieveExecutivePort; + private final ExternalRetrievePositionUseCase externalRetrievePositionUseCase; + private final ExecutiveDtoMapper mapper; + + @Transactional(readOnly = true) + @Override + public List retrieveExecutives() { + List executives = retrieveExecutivePort.findAll(); + Map positionMap = getPositionMap(executives); + List sortedExecutives = sortExecutives(executives, positionMap); + + return sortedExecutives.stream() + .map(executive -> mapper.toDto(executive, positionMap.get(executive.getId()))) + .toList(); + } + + private List sortExecutives(List executives, Map positionMap) { + return executives.stream() + .sorted(Comparator + .comparing((Executive executive) -> + getPriority(positionMap.get(executive.getId()))) + .thenComparing(Executive::getId)) + .toList(); + } + + private int getPriority(String positionKey) { + return Optional.ofNullable(PositionType.getPriorityByKey(positionKey)) + .orElse(Integer.MAX_VALUE); + } + + private Map getPositionMap(List executives) { + return executives.stream() + .collect(Collectors.toMap( + Executive::getId, + executive -> externalRetrievePositionUseCase + .findTopByMemberIdAndYearOrderByCreatedAtDesc(executive.getId(), + String.valueOf(LocalDate.now().getYear())) + .map(position -> position.getPositionType().getKey()) + .orElse("") + )); + } +} diff --git a/src/main/java/page/clab/api/domain/memberManagement/executive/application/service/ExecutiveUpdateService.java b/src/main/java/page/clab/api/domain/memberManagement/executive/application/service/ExecutiveUpdateService.java new file mode 100644 index 000000000..4e3ca1320 --- /dev/null +++ b/src/main/java/page/clab/api/domain/memberManagement/executive/application/service/ExecutiveUpdateService.java @@ -0,0 +1,27 @@ +package page.clab.api.domain.memberManagement.executive.application.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import page.clab.api.domain.memberManagement.executive.application.dto.request.ExecutiveUpdateRequestDto; +import page.clab.api.domain.memberManagement.executive.application.port.in.UpdateExecutiveUseCase; +import page.clab.api.domain.memberManagement.executive.application.port.out.RetrieveExecutivePort; +import page.clab.api.domain.memberManagement.executive.application.port.out.UpdateExecutivePort; +import page.clab.api.domain.memberManagement.executive.domain.Executive; + +@Service +@RequiredArgsConstructor +public class ExecutiveUpdateService implements UpdateExecutiveUseCase { + + private final UpdateExecutivePort updateExecutivePort; + private final RetrieveExecutivePort retrieveExecutivePort; + + @Transactional + @Override + public String updateExecutive(String executiveId, ExecutiveUpdateRequestDto requestDto) { + Executive executive = retrieveExecutivePort.getById(executiveId); + executive.update(requestDto); + Executive updatedExecutive = updateExecutivePort.update(executive); + return updatedExecutive.getId(); + } +} diff --git a/src/main/java/page/clab/api/domain/memberManagement/executive/domain/Executive.java b/src/main/java/page/clab/api/domain/memberManagement/executive/domain/Executive.java new file mode 100644 index 000000000..8bb02ff8c --- /dev/null +++ b/src/main/java/page/clab/api/domain/memberManagement/executive/domain/Executive.java @@ -0,0 +1,36 @@ +package page.clab.api.domain.memberManagement.executive.domain; + +import java.util.Optional; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import page.clab.api.domain.memberManagement.executive.application.dto.request.ExecutiveUpdateRequestDto; + +@Getter +@Setter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public class Executive { + + private String id; + private String name; + private String email; + private String interests; + private String imageUrl; + private Boolean isDeleted; + + public void update(ExecutiveUpdateRequestDto requestDto) { + Optional.ofNullable(requestDto.getName()).ifPresent(this::setName); + Optional.ofNullable(requestDto.getEmail()).ifPresent(this::setEmail); + Optional.ofNullable(requestDto.getInterests()).ifPresent(this::setInterests); + Optional.ofNullable(requestDto.getImageUrl()).ifPresent(this::setImageUrl); + } + + public void delete() { + this.isDeleted = true; + } +} diff --git a/src/main/java/page/clab/api/domain/memberManagement/member/application/dto/request/MemberRequestDto.java b/src/main/java/page/clab/api/domain/memberManagement/member/application/dto/request/MemberRequestDto.java index 7a77352d2..a3bb87f61 100644 --- a/src/main/java/page/clab/api/domain/memberManagement/member/application/dto/request/MemberRequestDto.java +++ b/src/main/java/page/clab/api/domain/memberManagement/member/application/dto/request/MemberRequestDto.java @@ -27,7 +27,7 @@ public class MemberRequestDto { private String contact; @NotNull(message = "{notNull.member.email}") - @Schema(description = "이메일", example = "clab.coreteam@gamil.com", required = true) + @Schema(description = "이메일", example = "clab.coreteam@gmail.com", required = true) private String email; @NotNull(message = "{notNull.member.department}") diff --git a/src/main/java/page/clab/api/domain/memberManagement/member/application/dto/request/MemberResetPasswordRequestDto.java b/src/main/java/page/clab/api/domain/memberManagement/member/application/dto/request/MemberResetPasswordRequestDto.java index 0861d4dee..978a67fac 100644 --- a/src/main/java/page/clab/api/domain/memberManagement/member/application/dto/request/MemberResetPasswordRequestDto.java +++ b/src/main/java/page/clab/api/domain/memberManagement/member/application/dto/request/MemberResetPasswordRequestDto.java @@ -18,6 +18,6 @@ public class MemberResetPasswordRequestDto { private String name; @NotNull(message = "{notNull.member.email}") - @Schema(description = "이메일", example = "clab.coreteam@gamil.com", required = true) + @Schema(description = "이메일", example = "clab.coreteam@gmail.com", required = true) private String email; } diff --git a/src/main/java/page/clab/api/domain/memberManagement/member/application/dto/request/MemberUpdateRequestDto.java b/src/main/java/page/clab/api/domain/memberManagement/member/application/dto/request/MemberUpdateRequestDto.java index 04083d654..7e9f2b263 100644 --- a/src/main/java/page/clab/api/domain/memberManagement/member/application/dto/request/MemberUpdateRequestDto.java +++ b/src/main/java/page/clab/api/domain/memberManagement/member/application/dto/request/MemberUpdateRequestDto.java @@ -16,7 +16,7 @@ public class MemberUpdateRequestDto { @Schema(description = "연락처", example = "01012345678") private String contact; - @Schema(description = "이메일", example = "clab.coreteam@gamil.com") + @Schema(description = "이메일", example = "clab.coreteam@gmail.com") private String email; @Schema(description = "학년", example = "1") diff --git a/src/main/java/page/clab/api/domain/memberManagement/position/adapter/out/persistence/PositionPersistenceAdapter.java b/src/main/java/page/clab/api/domain/memberManagement/position/adapter/out/persistence/PositionPersistenceAdapter.java index 8c16c68f2..8cfe54ec8 100644 --- a/src/main/java/page/clab/api/domain/memberManagement/position/adapter/out/persistence/PositionPersistenceAdapter.java +++ b/src/main/java/page/clab/api/domain/memberManagement/position/adapter/out/persistence/PositionPersistenceAdapter.java @@ -78,4 +78,10 @@ public List findByMemberId(String memberId) { .map(mapper::toDomain) .toList(); } + + @Override + public Optional findTopByMemberIdAndYearOrderByCreatedAtDesc(String memberId, String year) { + return repository.findTopByMemberIdAndYearOrderByCreatedAtDesc(memberId, year) + .map(mapper::toDomain); + } } diff --git a/src/main/java/page/clab/api/domain/memberManagement/position/adapter/out/persistence/PositionRepository.java b/src/main/java/page/clab/api/domain/memberManagement/position/adapter/out/persistence/PositionRepository.java index bba32cff3..4f4a7e1f9 100644 --- a/src/main/java/page/clab/api/domain/memberManagement/position/adapter/out/persistence/PositionRepository.java +++ b/src/main/java/page/clab/api/domain/memberManagement/position/adapter/out/persistence/PositionRepository.java @@ -15,4 +15,6 @@ Optional findByMemberIdAndYearAndPositionType(String memberId PositionType positionType); List findAllByMemberIdAndYearOrderByPositionTypeAsc(String memberId, String year); + + Optional findTopByMemberIdAndYearOrderByCreatedAtDesc(String memberId, String year); } diff --git a/src/main/java/page/clab/api/domain/memberManagement/position/application/port/out/RetrievePositionPort.java b/src/main/java/page/clab/api/domain/memberManagement/position/application/port/out/RetrievePositionPort.java index c6a2aabbe..62a6a79e9 100644 --- a/src/main/java/page/clab/api/domain/memberManagement/position/application/port/out/RetrievePositionPort.java +++ b/src/main/java/page/clab/api/domain/memberManagement/position/application/port/out/RetrievePositionPort.java @@ -18,4 +18,6 @@ public interface RetrievePositionPort { Page findByConditions(String year, PositionType positionType, Pageable pageable); List findByMemberId(String memberId); + + Optional findTopByMemberIdAndYearOrderByCreatedAtDesc(String memberId, String year); } diff --git a/src/main/java/page/clab/api/domain/memberManagement/position/domain/PositionType.java b/src/main/java/page/clab/api/domain/memberManagement/position/domain/PositionType.java index 674479ddf..46062f396 100644 --- a/src/main/java/page/clab/api/domain/memberManagement/position/domain/PositionType.java +++ b/src/main/java/page/clab/api/domain/memberManagement/position/domain/PositionType.java @@ -9,13 +9,23 @@ @AllArgsConstructor public enum PositionType { - PRESIDENT("PRESIDENT", "회장"), - VICE_PRESIDENT("VICE_PRESIDENT", "부회장"), - OPERATION("OPERATION", "운영진"), - CORE_TEAM("CORE_TEAM", "코어팀"), - MEMBER("MEMBER", "일반회원"); + PRESIDENT("PRESIDENT", "회장", 1), + VICE_PRESIDENT("VICE_PRESIDENT", "부회장", 2), + OPERATION("OPERATION", "운영진", 3), + CORE_TEAM("CORE_TEAM", "코어팀", null), + MEMBER("MEMBER", "일반회원", null); @Enumerated(EnumType.STRING) private final String key; private final String description; + private final Integer priority; + + public static Integer getPriorityByKey(String key) { + for (PositionType positionType : values()) { + if (positionType.getKey().equals(key)) { + return positionType.getPriority(); + } + } + throw new IllegalArgumentException("Invalid PositionType key: " + key); + } } diff --git a/src/main/java/page/clab/api/external/memberManagement/position/application/port/ExternalRetrievePositionUseCase.java b/src/main/java/page/clab/api/external/memberManagement/position/application/port/ExternalRetrievePositionUseCase.java index 50891dc4e..ae2636250 100644 --- a/src/main/java/page/clab/api/external/memberManagement/position/application/port/ExternalRetrievePositionUseCase.java +++ b/src/main/java/page/clab/api/external/memberManagement/position/application/port/ExternalRetrievePositionUseCase.java @@ -7,4 +7,6 @@ public interface ExternalRetrievePositionUseCase { Optional findByMemberIdAndYearAndPositionType(String memberId, String year, PositionType positionType); + + Optional findTopByMemberIdAndYearOrderByCreatedAtDesc(String memberId, String year); } diff --git a/src/main/java/page/clab/api/external/memberManagement/position/application/service/ExternalPositionRetrievalService.java b/src/main/java/page/clab/api/external/memberManagement/position/application/service/ExternalPositionRetrievalService.java index 6861eb65c..75ee9b7f5 100644 --- a/src/main/java/page/clab/api/external/memberManagement/position/application/service/ExternalPositionRetrievalService.java +++ b/src/main/java/page/clab/api/external/memberManagement/position/application/service/ExternalPositionRetrievalService.java @@ -21,4 +21,11 @@ public Optional findByMemberIdAndYearAndPositionType(String memberId, PositionType positionType) { return retrievePositionPort.findByMemberIdAndYearAndPositionType(memberId, year, positionType); } + + @Transactional(readOnly = true) + @Override + public Optional findTopByMemberIdAndYearOrderByCreatedAtDesc(String memberId, String year) { + return retrievePositionPort.findTopByMemberIdAndYearOrderByCreatedAtDesc(memberId, year); + } + } diff --git a/src/main/java/page/clab/api/global/common/file/api/FileController.java b/src/main/java/page/clab/api/global/common/file/api/FileController.java index b2c2e607d..a0280cb93 100644 --- a/src/main/java/page/clab/api/global/common/file/api/FileController.java +++ b/src/main/java/page/clab/api/global/common/file/api/FileController.java @@ -54,6 +54,18 @@ public ApiResponse profileUpload( return ApiResponse.success(responseDto); } + @Operation(summary = "[A] 운영진 사진 업로드", description = "ROLE_ADMIN 이상의 권한이 필요함") + @PreAuthorize("hasRole('ADMIN')") + @PostMapping(value = "/executives", consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) + public ApiResponse executiveUpload( + @RequestParam(name = "multipartFile") MultipartFile multipartFile, + @RequestParam(name = "storagePeriod") long storagePeriod + ) throws IOException, PermissionDeniedException { + String path = fileService.buildPath("executives"); + UploadedFileResponseDto responseDto = fileService.saveFile(multipartFile, path, storagePeriod); + return ApiResponse.success(responseDto); + } + @Operation(summary = "[A] 함께하는 활동 사진 업로드", description = "ROLE_ADMIN 이상의 권한이 필요함") @PreAuthorize("hasRole('ADMIN')") @PostMapping(value = "/activity-photos", consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) diff --git a/src/main/java/page/clab/api/global/common/file/application/FileService.java b/src/main/java/page/clab/api/global/common/file/application/FileService.java index e1bb00c16..1e920a268 100644 --- a/src/main/java/page/clab/api/global/common/file/application/FileService.java +++ b/src/main/java/page/clab/api/global/common/file/application/FileService.java @@ -82,21 +82,22 @@ public class FileService { private String maxFileSize; private static final Map> roleCategoryMap = Map.of( - Role.GUEST, Set.of("boards", "profiles", "activity-photos", "membership-fees"), + Role.GUEST, Set.of("boards", "profiles", "activity-photos", "membership-fees", "executives"), Role.USER, Set.of("boards", "profiles", "activity-photos", "membership-fees", "notices", "weekly-activities", "members", - "assignments", "submits"), + "assignments", "submits", "executives"), Role.ADMIN, Set.of("boards", "profiles", "activity-photos", "membership-fees", "notices", "weekly-activities", "members", - "assignments", "submits"), + "assignments", "submits", "executives"), Role.SUPER, Set.of("boards", "profiles", "activity-photos", "membership-fees", "notices", "weekly-activities", "members", - "assignments", "submits") + "assignments", "submits", "executives") ); private final Map> categoryAccessMap = Map.of( "boards", (url, auth) -> true, "profiles", (url, auth) -> true, + "executives", (url, auth) -> true, "activity-photos", (url, auth) -> true, "membership-fees", (url, auth) -> true, "notices", this::isNonSubmitCategoryAccessible, @@ -281,7 +282,7 @@ private void validateMemberCloudUsage(MultipartFile multipartFile, String path) } private void checkAndRemoveExistingFile(String path) { - List validPrefixes = Arrays.asList("profiles", "members/", "assignments"); + List validPrefixes = Arrays.asList("profiles", "members/", "assignments", "executives"); boolean shouldDelete = validPrefixes.stream().anyMatch(path::startsWith); if (shouldDelete) { UploadedFile fileToDelete = uploadedFileService.getUniqueUploadedFileByCategory(path); @@ -300,7 +301,7 @@ public boolean isUserAccessibleAtFile(Authentication authentication, String url) } public boolean isUserAccessibleByCategory(String category, String url, Authentication authentication) { - if (category.equals("activity-photos")) { + if (category.equals("activity-photos") || category.equals("executives")) { return true; } if (AuthUtil.isUserUnAuthenticated(authentication)) { diff --git a/src/main/java/page/clab/api/global/handler/GlobalExceptionHandler.java b/src/main/java/page/clab/api/global/handler/GlobalExceptionHandler.java index 7c2e20030..00df20d73 100644 --- a/src/main/java/page/clab/api/global/handler/GlobalExceptionHandler.java +++ b/src/main/java/page/clab/api/global/handler/GlobalExceptionHandler.java @@ -63,6 +63,7 @@ import page.clab.api.domain.library.bookLoanRecord.application.exception.LoanSuspensionException; import page.clab.api.domain.library.bookLoanRecord.application.exception.MaxBorrowLimitExceededException; import page.clab.api.domain.library.bookLoanRecord.application.exception.OverdueException; +import page.clab.api.domain.memberManagement.executive.application.exception.ExecutiveRegistrationException; import page.clab.api.domain.memberManagement.member.application.exception.DuplicateMemberContactException; import page.clab.api.domain.memberManagement.member.application.exception.DuplicateMemberEmailException; import page.clab.api.domain.memberManagement.member.application.exception.DuplicateMemberIdException; @@ -127,7 +128,8 @@ public class GlobalExceptionHandler { UnknownPathException.class, AssignmentBoardHasNoDueDateTimeException.class, FeedbackBoardHasNoContentException.class, - InvalidBoardCategoryHashtagException.class + InvalidBoardCategoryHashtagException.class, + ExecutiveRegistrationException.class }) public ErrorResponse badRequestException(HttpServletResponse response, Exception e) { response.setStatus(HttpServletResponse.SC_OK); diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index 6e7db6fb5..cfb1ad445 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -54,10 +54,14 @@ size.verification.memberId=학번은 {min}자 이상 {max}자 이하로 입력 size.verification.verification=인증 코드는 {min}자 이상 {max}자 이하로 입력하세요. size.workExperience.companyName=회사명은 최소 {min}글자 이상이어야 합니다. size.workExperience.position=직책은 최소 {min}글자 이상이어야 합니다. +size.executive.id=학번은 최소 {min}자 이상 {max}자 이하로 입력하세요. +size.executive.name=이름은 최소 {min}자 이상 {max}자 이하로 입력하세요. +size.executive.email=이메일을 {min}자 이상 입력하세요. range.activityGroup.progress=진행도는 0에서 100 사이의 값이어야 합니다. pattern.application.studentId=학번은 숫자로만 입력하세요. email.application.email=올바른 이메일 형식으로 입력하세요. email.member.email=올바른 이메일 형식으로 입력하세요. +email.executive.email=올바른 이메일 형식으로 입력하세요. min.application.grade=학년은 {value}학년 이상이어야 합니다. min.donation.amount=금액은 {value}원 이상이어야 합니다. min.member.grade=최소값은 {value} 이상이어야 합니다. @@ -189,4 +193,8 @@ notNull.workExperience.companyName=회사명은 필수 입력 항목입니다. notNull.workExperience.position=직책은 필수 입력 항목입니다. notNull.workExperience.startDate=시작일은 필수 입력 항목입니다. notNull.workExperience.endDate=종료일은 필수 입력 항목입니다. +notNull.executive.executiveId=학번은 필수 입력 항목입니다. +notNull.executive.name=이름은 필수 입력 항목입니다. +notNull.executive.email=이메일은 필수 입력 항목입니다. +notNull.executive.interests=분야는 필수 입력 항목입니다. invalid.activityGroupBoard.dueDateTime=마감일자는 현재 시간 이후로 설정되어야 합니다. \ No newline at end of file