Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 운영진 관리 기능 구현 완료 #648

Open
wants to merge 15 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
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 이상의 권한이 필요함<br>" +
"position은 다음과 같이 등록 가능<br>" +
"- PRESIDENT : 회장<br>" +
"- VICE_PRESIDENT : 부회장<br>" +
"- GENERAL : 일반 운영진<br>")
limehee marked this conversation as resolved.
Show resolved Hide resolved
@PreAuthorize("hasRole('ADMIN')")
@PostMapping("")
public ApiResponse<String> registerExecutive(
@Valid @RequestBody ExecutiveRequestDto requestDto
) {
String id = registerExecutiveUseCase.registerExecutive(requestDto);
return ApiResponse.success(id);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
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("/{id}")
limehee marked this conversation as resolved.
Show resolved Hide resolved
public ApiResponse<String> removeMember(
@PathVariable(name = "id") String id
) {
String executiveId = removeExecutiveUseCase.removeExecutive(id);
return ApiResponse.success(executiveId);
}

}
Original file line number Diff line number Diff line change
@@ -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<List<ExecutiveResponseDto>> retrieveExecutives() {
List<ExecutiveResponseDto> executives = retrieveExecutiveUseCase.retrieveExecutives();
return ApiResponse.success(executives);
}
}
Original file line number Diff line number Diff line change
@@ -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("/{id}")
public ApiResponse<String> updateExecutive(
@PathVariable(name = "id") String id,
@RequestBody ExecutiveUpdateRequestDto requestDto
) {
String executiveId = updateExecutiveUseCase.updateExecutive(id, requestDto);
return ApiResponse.success(executiveId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package page.clab.api.domain.memberManagement.executive.adapter.out.persistence;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
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.domain.memberManagement.executive.domain.ExecutivePosition;
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 field;

@Column(nullable = false)
@Enumerated(EnumType.STRING)
private ExecutivePosition position;

private String imageUrl;

@Column(name = "is_deleted", nullable = false)
private Boolean isDeleted;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isDeleted 필드를 사용하는 경우 언제 소프트 딜리트가 되었는지 확인할 수 있는 수단이 되기 때문에 deletedAt 필드도 함께 사용하는 것이 좋아보여요!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

deletedAt을 추가함으로써 삭제된지 오래된 데이터들을 영구 삭제하거나, 혹시라도 복구할 때 삭제 시점을 보고 복구하기 위함인가요??
boardEmoji를 제외한 다른 도메인들에는 deletedAt이 없어서 질문드립니다!
deletedAt을 추가하는 도메인에 대한 특이사항이 있을까요?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저는 현재로서는 updatedAt 필드만으로도 삭제 시점을 충분히 관리할 수 있다고 생각해요. 다만, deletedAt 필드를 추가하게 된다면, 다른 소프트 삭제를 사용하는 테이블에도 동일하게 적용하여 일관성을 유지하는 것이 적절하다고 판단돼요.

Copy link
Collaborator

@mingmingmon mingmingmon Jan 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

내용에 대한 변경은 updateAt, 삭제에 대한 정보는 deletedAt을 활용하는 것이라고 생각했습니다! 다른 테이블에서 삭제 시점을 deletedAt이 아닌 updateAt으로 사용하고 계신다면 유지해도 괜찮을 것 같아요.

boardEmojideletedAt 칼럼도 명시적으로 활용하는 로직이 있는지 확인하고, 삭제하도록 이슈 등록해두겠습니다!

}
Original file line number Diff line number Diff line change
@@ -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);
}
Original file line number Diff line number Diff line change
@@ -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<Executive> findAll() {
List<ExecutiveJpaEntity> jpaEntities = executiveRepository.findAll();
return jpaEntities.stream()
.map(executiveMapper::toDomain)
.collect(Collectors.toList());
}

@Override
public Executive getById(String id) {
ExecutiveJpaEntity jpaEntity = executiveRepository.findById(id)
.orElseThrow(() -> new NotFoundException("학번이 " + id + "인 운영진이 존재하지 않습니다."));
limehee marked this conversation as resolved.
Show resolved Hide resolved
return executiveMapper.toDomain(jpaEntity);
}

@Override
public Boolean existsById(String id) {
return executiveRepository.existsById(id);
}
}
Original file line number Diff line number Diff line change
@@ -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<ExecutiveJpaEntity, String> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
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.getId())
.name(requestDto.getName())
.email(requestDto.getEmail())
.field(requestDto.getField())
.position(requestDto.getPosition())
.imageUrl(requestDto.getImageUrl())
.isDeleted(false)
.build();
}

public ExecutiveResponseDto toDto(Executive executive) {
return ExecutiveResponseDto.builder()
.id(executive.getId())
.name(executive.getName())
.email(executive.getEmail())
.field(executive.getField())
.position(executive.getPosition())
.imageUrl(executive.getImageUrl())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
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;
import page.clab.api.domain.memberManagement.executive.domain.ExecutivePosition;

@Getter
@Setter
public class ExecutiveRequestDto {

@NotNull(message = "{notNull.executive.id}")
@Schema(description = "학번", example = "202310000")
private String id;

@NotNull(message = "{notNull.executive.name}")
@Schema(description = "이름", example = "홍길동")
private String name;

@NotNull(message = "{notNull.executive.email}")
@Schema(description = "이메일", example = "[email protected]")
Copy link
Preview

Copilot AI Dec 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a typo in the email example: "[email protected]" should be "[email protected]".

Suggested change
@Schema(description = "이메일", example = "clab.coreteam@gamil.com")
@Schema(description = "이메일", example = "clab.coreteam@gmail.com")

Copilot is powered by AI, so mistakes are possible. Review output carefully before use.

Positive Feedback
Negative Feedback

Provide additional feedback

Please help us improve GitHub Copilot by sharing more details about this comment.

Please select one or more of the options
private String email;

@NotNull(message = "{notNull.executive.field}")
@Schema(description = "분야", example = "Back-End")
private String field;

@NotNull(message = "{notNull.executive.position}")
@Schema(description = "직급", example = "PRESIDENT")
private ExecutivePosition position;

@Schema(description = "프로필 이미지", example = "https://www.clab.page/assets/dongmin-860f3a1e.jpeg")
private String imageUrl;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package page.clab.api.domain.memberManagement.executive.application.dto.request;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
import page.clab.api.domain.memberManagement.executive.domain.ExecutivePosition;

@Getter
@Setter
public class ExecutiveUpdateRequestDto {

@Schema(description = "이름", example = "홍길동")
private String name;

@Schema(description = "이메일", example = "[email protected]")
private String email;

@Schema(description = "분야", example = "Back-End")
private String field;

@Schema(description = "직급", example = "PRESIDENT")
private ExecutivePosition position;

@Schema(description = "프로필 이미지", example = "https://www.clab.page/assets/dongmin-860f3a1e.jpeg")
private String imageUrl;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package page.clab.api.domain.memberManagement.executive.application.dto.response;

import lombok.Builder;
import lombok.Getter;
import page.clab.api.domain.memberManagement.executive.domain.ExecutivePosition;

@Getter
@Builder
public class ExecutiveResponseDto {

private String id;
private String name;
private String email;
private String field;
private ExecutivePosition position;
private String imageUrl;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package page.clab.api.domain.memberManagement.executive.application.exception;

public class ExecutiveRegistrationException extends RuntimeException {

public ExecutiveRegistrationException(String message) {
super(message);
}
}
Loading
Loading