Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public static AdminFeeSearchResponse of(StudentTransferStatus status, Member mem
status.getMemberId(),
member != null ? member.getName() : ApiConstants.WITHDRAWN_MEMBER_LABEL,
member != null ? member.getStudentId() : null,
member != null ? member.getDepartment().name() : null,
member != null ? member.getDepartment().displayName() : null,
status.getStatus().name(),
status.getCreatedAt(),
status.getReviewedAt()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,35 @@
package kr.ac.kookmin.stream.member.domain.member.domain;

import java.util.Arrays;
import kr.ac.kookmin.stream.common.BusinessException;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.experimental.Accessors;

/**
* 학부. 학생회 부서(CouncilDepartment)와는 다른 개념이다.
*/
@Accessors(fluent = true)
@AllArgsConstructor
public enum Department {
AI,
SW
AI("인공지능전공", "인공지능학부"),
SW("소프트웨어전공", "소프트웨어학부");

// 로그인 provider가 주는 소속 문자열에서 찾는 전공명
private final String majorName;

// 응답에 내려주는 학부명
@Getter
private final String displayName;

/** 소속 문자열을 학부로 바꾼다. 소프트웨어융합대학 전공이 아니면 가입할 수 없다. */
public static Department fromMajor(String major) {
if (major == null) {
throw new BusinessException(MemberErrorCode.DEPARTMENT_NOT_ALLOWED);
}
return Arrays.stream(values())
.filter(department -> major.contains(department.majorName))
.findFirst()
.orElseThrow(() -> new BusinessException(MemberErrorCode.DEPARTMENT_NOT_ALLOWED));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,36 @@ public class Member {
private String studentId;
private String name;
private Department department;
// 로그인 provider가 주는 학적 상태 원문(재학·휴학·졸업 등). 로그인 전 이관 회원은 null이다
private String academicStatus;
private String email;
private String fcmToken;
private Role role;
private CouncilDepartment councilDepartment;

// 로그인으로 처음 가입하는 회원. 운영진 권한·학생회 부서는 가입 뒤 따로 부여한다
public static Member create(String studentId, String name, Department department, String academicStatus) {
return new Member(null, studentId, name, department, academicStatus, null, null, Role.STUDENT, null);
}

public static Member of(
Long id,
String studentId,
String name,
Department department,
String academicStatus,
String email,
String fcmToken,
Role role,
CouncilDepartment councilDepartment
) {
return new Member(id, studentId, name, department, email, fcmToken, role, councilDepartment);
return new Member(id, studentId, name, department, academicStatus, email, fcmToken, role, councilDepartment);
}

// 로그인할 때마다 provider의 최신 이름·학부·학적 상태로 갱신한다
public void updateProfile(String name, Department department, String academicStatus) {
this.name = name;
this.department = department;
this.academicStatus = academicStatus;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
@AllArgsConstructor
public enum MemberErrorCode implements ErrorCode {

MEMBER_NOT_FOUND(ErrorStatus.NOT_FOUND, "회원을 찾을 수 없습니다.");
MEMBER_NOT_FOUND(ErrorStatus.NOT_FOUND, "회원을 찾을 수 없습니다."),
DEPARTMENT_NOT_ALLOWED(ErrorStatus.FORBIDDEN, "소프트웨어융합대학 학생만 이용할 수 있습니다.");

private final int status;
private final String message;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package kr.ac.kookmin.stream.member.domain.member.domain;

/**
* 로그인 provider가 준 회원 프로필. major·academicStatus는 원문이며, 학부 변환은 {@link Department#fromMajor}가 한다.
*/
public record MemberProfileCommand(
String studentId,
String name,
String major,
String academicStatus
) {}
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
package kr.ac.kookmin.stream.member.domain.member.domain;

import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.experimental.Accessors;

@Getter
@Accessors(fluent = true)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public enum TermType {
PRIVACY_POLICY, // 개인정보 이용 동의
TERMS_OF_SERVICE // 서비스 이용 약관 동의

PRIVACY_POLICY(true), // 개인정보 이용 동의
TERMS_OF_SERVICE(true); // 서비스 이용 약관 동의

/** 가입 시 반드시 동의해야 하는 약관인지 여부 */
private final boolean required;
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

public interface MemberRepository {
Optional<Member> findById(Long id);
Optional<Member> findByStudentId(String studentId);
List<Member> findAllByIds(List<Long> ids);
List<Long> searchIdsByKeyword(String keyword);
Member save(Member member);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package kr.ac.kookmin.stream.member.domain.member.repository;

import java.util.List;
import kr.ac.kookmin.stream.member.domain.member.domain.MemberTermAgreement;

public interface MemberTermAgreementRepository {
List<MemberTermAgreement> findAllByMemberId(Long memberId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@
import java.util.List;
import java.util.Map;
import kr.ac.kookmin.stream.member.domain.member.domain.Member;
import kr.ac.kookmin.stream.member.domain.member.domain.MemberProfileCommand;

public interface MemberService {
Member getById(Long id);
List<Member> findAllByIds(List<Long> ids);
Map<Long, Member> getMapByIds(List<Long> ids);
List<Long> searchIdsByKeyword(String keyword);
Member updateProfile(Long id, MemberProfileCommand command);
Member registerOrUpdateByStudentId(MemberProfileCommand command);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package kr.ac.kookmin.stream.member.domain.member.service;

public interface MemberTermService {
boolean hasAgreedRequiredTerms(Long memberId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@
import java.util.function.Function;
import java.util.stream.Collectors;
import kr.ac.kookmin.stream.common.BusinessException;
import kr.ac.kookmin.stream.member.domain.member.domain.Department;
import kr.ac.kookmin.stream.member.domain.member.domain.Member;
import kr.ac.kookmin.stream.member.domain.member.domain.MemberErrorCode;
import kr.ac.kookmin.stream.member.domain.member.domain.MemberProfileCommand;
import kr.ac.kookmin.stream.member.domain.member.repository.MemberRepository;
import kr.ac.kookmin.stream.member.domain.member.service.MemberService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
Expand Down Expand Up @@ -40,4 +43,25 @@ public Map<Long, Member> getMapByIds(List<Long> ids) {
public List<Long> searchIdsByKeyword(String keyword) {
return memberRepository.searchIdsByKeyword(keyword);
}

@Override
@Transactional
public Member updateProfile(Long id, MemberProfileCommand command) {
Member member = getById(id);
member.updateProfile(command.name(), Department.fromMajor(command.major()), command.academicStatus());
return memberRepository.save(member);
}

@Override
@Transactional
public Member registerOrUpdateByStudentId(MemberProfileCommand command) {
Department department = Department.fromMajor(command.major());
Member member = memberRepository.findByStudentId(command.studentId())
.map(existing -> {
existing.updateProfile(command.name(), department, command.academicStatus());
return existing;
})
.orElseGet(() -> Member.create(command.studentId(), command.name(), department, command.academicStatus()));
return memberRepository.save(member);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package kr.ac.kookmin.stream.member.domain.member.service.impl;

import java.util.Arrays;
import java.util.Set;
import java.util.stream.Collectors;
import kr.ac.kookmin.stream.member.domain.member.domain.MemberTermAgreement;
import kr.ac.kookmin.stream.member.domain.member.domain.TermType;
import kr.ac.kookmin.stream.member.domain.member.repository.MemberTermAgreementRepository;
import kr.ac.kookmin.stream.member.domain.member.service.MemberTermService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
class MemberTermServiceImpl implements MemberTermService {

private final MemberTermAgreementRepository memberTermAgreementRepository;

@Override
public boolean hasAgreedRequiredTerms(Long memberId) {
Set<TermType> agreedTermTypes = memberTermAgreementRepository.findAllByMemberId(memberId).stream()
.filter(MemberTermAgreement::isAgreed)
.map(MemberTermAgreement::getTermType)
.collect(Collectors.toSet());
return Arrays.stream(TermType.values())
.filter(TermType::required)
.allMatch(agreedTermTypes::contains);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ public class MemberJpaEntity extends BaseSoftDeleteEntity {
@Column(nullable = false, length = 30)
private Department department;

@Column(name = "academic_status", length = 50)
private String academicStatus;

private String email;

@Column(name = "fcm_token")
Expand All @@ -56,6 +59,7 @@ private MemberJpaEntity(Member member) {
this.studentId = member.getStudentId();
this.name = member.getName();
this.department = member.getDepartment();
this.academicStatus = member.getAcademicStatus();
this.email = member.getEmail();
this.fcmToken = member.getFcmToken();
this.role = member.getRole();
Expand All @@ -67,6 +71,6 @@ public static MemberJpaEntity from(Member member) {
}

public Member toDomain() {
return Member.of(id, studentId, name, department, email, fcmToken, role, councilDepartment);
return Member.of(id, studentId, name, department, academicStatus, email, fcmToken, role, councilDepartment);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ public interface MemberJpaRepository extends JpaRepository<MemberJpaEntity, Long

Optional<MemberJpaEntity> findByIdAndIsDeletedFalse(Long id);

Optional<MemberJpaEntity> findByStudentIdAndIsDeletedFalse(String studentId);

List<MemberJpaEntity> findAllByIdInAndIsDeletedFalse(List<Long> ids);

@Query("""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ public Optional<Member> findById(Long id) {
return memberJpaRepository.findByIdAndIsDeletedFalse(id).map(MemberJpaEntity::toDomain);
}

@Override
public Optional<Member> findByStudentId(String studentId) {
return memberJpaRepository.findByStudentIdAndIsDeletedFalse(studentId).map(MemberJpaEntity::toDomain);
}

@Override
public List<Member> findAllByIds(List<Long> ids) {
if (ids.isEmpty()) {
Expand All @@ -32,4 +37,9 @@ public List<Member> findAllByIds(List<Long> ids) {
public List<Long> searchIdsByKeyword(String keyword) {
return memberJpaRepository.searchIdsByKeyword(keyword);
}

@Override
public Member save(Member member) {
return memberJpaRepository.save(MemberJpaEntity.from(member)).toDomain();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package kr.ac.kookmin.stream.db.member;

import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;

public interface MemberTermAgreementJpaRepository extends JpaRepository<MemberTermAgreementJpaEntity, Long> {
List<MemberTermAgreementJpaEntity> findAllByMemberId(Long memberId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package kr.ac.kookmin.stream.db.member;

import java.util.List;
import kr.ac.kookmin.stream.member.domain.member.domain.MemberTermAgreement;
import kr.ac.kookmin.stream.member.domain.member.repository.MemberTermAgreementRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;

@Repository
@RequiredArgsConstructor
public class MemberTermAgreementRepositoryImpl implements MemberTermAgreementRepository {

private final MemberTermAgreementJpaRepository memberTermAgreementJpaRepository;

@Override
public List<MemberTermAgreement> findAllByMemberId(Long memberId) {
return memberTermAgreementJpaRepository.findAllByMemberId(memberId).stream()
.map(MemberTermAgreementJpaEntity::toDomain)
.toList();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-- 로그인 provider가 주는 학적 상태 원문(재학·휴학·졸업 등). 로그인할 때마다 갱신한다.
-- 로그인 전인 기존(이관) 회원은 값이 없으므로 NULL을 허용한다.
ALTER TABLE members
ADD COLUMN academic_status VARCHAR(50) NULL AFTER department;