diff --git a/.github/workflows/main-cd.yml b/.github/workflows/main-cd.yml index 426bc15..462e69a 100644 --- a/.github/workflows/main-cd.yml +++ b/.github/workflows/main-cd.yml @@ -36,6 +36,15 @@ jobs: git clone git@github.com:Ring-Us/ring-us-server.git /home/ubuntu/ring-us-server cd /home/ubuntu/ring-us-server fi + + cd /home/ubuntu/ring-us-server + + echo "${{ secrets.ENV }}" > .env.dev + + # ๐Ÿ›  Gradle ๋นŒ๋“œ ๊ถŒํ•œ ์„ค์ • + echo "[GitHub Actions] ๐Ÿ”ง Fixing Gradle permissions" + sudo chown -R $USER:$USER ~/.gradle + sudo chmod -R 777 ~/.gradle # ๐Ÿ›  Gradle ๋นŒ๋“œ ๊ถŒํ•œ ์„ค์ • echo "[GitHub Actions] ๐Ÿ”ง Fixing Gradle permissions" diff --git a/docker-compose.yml b/docker-compose.yml index 60f2710..a87fed0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,7 +11,7 @@ services: - redis ports: - ${SPRING_PORT}:${SPRING_PORT} - restart: always + restart: "always" database: image: mysql:8.4.4 @@ -27,7 +27,7 @@ services: - ${DB_PORT} ports: - ${DB_PORT}:${DB_PORT} - restart: no + restart: "no" volumes: - ring-us-database:/var/lib/mysql @@ -39,9 +39,9 @@ services: - ${REDIS_PORT} ports: - ${REDIS_PORT}:${REDIS_PORT} - restart: always + restart: "always" volumes: - ring-us-redis:/data volumes: ring-us-database: - ring-us-redis: + ring-us-redis: \ No newline at end of file diff --git a/src/main/java/es/princip/ringus/application/auth/service/AuthService.java b/src/main/java/es/princip/ringus/application/auth/service/AuthService.java index 945e87b..4d50ef6 100644 --- a/src/main/java/es/princip/ringus/application/auth/service/AuthService.java +++ b/src/main/java/es/princip/ringus/application/auth/service/AuthService.java @@ -37,6 +37,7 @@ public SignUpResponse signUp(SignUpRequest request, HttpSession session){ emailSessionRepository.deleteById(sessionId); + verificationService.deleteSession(sessionId); return new SignUpResponse(member.getId()); } diff --git a/src/main/java/es/princip/ringus/application/auth/service/EmailVerificationService.java b/src/main/java/es/princip/ringus/application/auth/service/EmailVerificationService.java index 6496e54..8c4e066 100644 --- a/src/main/java/es/princip/ringus/application/auth/service/EmailVerificationService.java +++ b/src/main/java/es/princip/ringus/application/auth/service/EmailVerificationService.java @@ -8,6 +8,7 @@ import es.princip.ringus.domain.exception.SignUpErrorCode; import es.princip.ringus.domain.member.MemberRepository; import es.princip.ringus.global.exception.CustomRuntimeException; +import es.princip.ringus.presentation.auth.dto.request.GenerateCodeRequest; import jakarta.annotation.PostConstruct; import jakarta.servlet.http.HttpSession; import jakarta.transaction.Transactional; @@ -20,7 +21,6 @@ @Service @RequiredArgsConstructor public class EmailVerificationService { - private final EmailVerificationRepository verificationRepository; private final EmailSessionRepository sessionRepository; private final MemberRepository memberRepository; @@ -35,14 +35,21 @@ public void init(){ } @Transactional - public void generateVerificationCode(String email) { - if(memberRepository.existsByEmail(email)){ - throw new CustomRuntimeException(SignUpErrorCode.DUPLICATE_EMAIL); + public void generateVerificationCode(GenerateCodeRequest request) { + if(request.isPasswordReset()) { + if (!memberRepository.existsByEmail(request.email())) { + throw new CustomRuntimeException(SignUpErrorCode.NOT_FOUND_MEMBER); + } + } + else { + if(memberRepository.existsByEmail(request.email())) { + throw new CustomRuntimeException(SignUpErrorCode.DUPLICATE_EMAIL); + } } - EmailVerification verification = EmailVerification.of(email); + EmailVerification verification = EmailVerification.of(request.email()); - emailSendService.sendMimeMessage(email, verification.getVerificationCode()); + emailSendService.sendMimeMessage(request.email(), verification.getVerificationCode()); verificationRepository.save(verification); } @@ -91,6 +98,13 @@ public void verifySession(String email, HttpSession session){ throw new CustomRuntimeException(EmailErrorCode.SESSION_EMAIL_MISMATCH); } - sessionRepository.delete(emailSession); + } + + @Transactional + public void deleteSession(String email) { + if(!sessionRepository.existsById(email)) { + throw new CustomRuntimeException(EmailErrorCode.SESSION_NOT_FOUND); + } + sessionRepository.deleteById(email); } } \ No newline at end of file diff --git a/src/main/java/es/princip/ringus/application/member/service/MemberService.java b/src/main/java/es/princip/ringus/application/member/service/MemberService.java index 9af103d..8abd147 100644 --- a/src/main/java/es/princip/ringus/application/member/service/MemberService.java +++ b/src/main/java/es/princip/ringus/application/member/service/MemberService.java @@ -1,5 +1,6 @@ package es.princip.ringus.application.member.service; +import es.princip.ringus.application.auth.service.EmailVerificationService; import es.princip.ringus.domain.exception.MemberErrorCode; import es.princip.ringus.domain.exception.SignUpErrorCode; import es.princip.ringus.domain.member.Member; @@ -8,6 +9,7 @@ import es.princip.ringus.domain.mentee.MenteeRepository; import es.princip.ringus.domain.mentor.Mentor; import es.princip.ringus.domain.mentor.MentorRepository; +import es.princip.ringus.domain.mentoring.MentoringRepository; import es.princip.ringus.domain.serviceTerm.ServiceTermAgreement; import es.princip.ringus.global.exception.CustomRuntimeException; import es.princip.ringus.global.util.UniversityDomainUtil; @@ -15,6 +17,8 @@ import es.princip.ringus.presentation.member.dto.MemberResponse; import es.princip.ringus.presentation.member.dto.MenteeProfileResponse; import es.princip.ringus.presentation.member.dto.MentorProfileResponse; +import es.princip.ringus.presentation.member.dto.PasswordUpdateRequest; +import jakarta.servlet.http.HttpSession; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.security.crypto.password.PasswordEncoder; @@ -30,8 +34,10 @@ public class MemberService { private final MemberRepository memberRepository; private final MentorRepository mentorRepository; - private final MenteeRepository menteeRepository; + private final MenteeRepository menteeRepository; private final PasswordEncoder passwordEncoder; + private final MentoringRepository mentoringRepository; + private final EmailVerificationService emailVerificationService; /** * ํšŒ์› ์ €์žฅ (์ด๋ฉ”์ผ ์ธ์ฆ ํ›„ ํšŒ์›๊ฐ€์ž… ์ง„ํ–‰) @@ -68,13 +74,30 @@ public MemberResponse getMember(Long memberId) { Mentor mentor = mentorRepository.findByMemberId(memberId) .orElseThrow(() -> new CustomRuntimeException(MemberErrorCode.MEMBER_NOT_FOUND)); - MentorProfileResponse profile = MentorProfileResponse.from(mentor); + MentorProfileResponse profile = MentorProfileResponse.from(mentor, mentoringRepository.findMentoringCountBy(mentor.getId())); return MemberResponse.of(member, profile); } } return MemberResponse.of(member); } + @Transactional + public void updatePassword(PasswordUpdateRequest request, HttpSession session) { + emailVerificationService.verifySession(request.email(), session); + + Member member = memberRepository.findByEmail(request.email()) + .orElseThrow(() -> new CustomRuntimeException(SignUpErrorCode.NOT_FOUND_MEMBER)); + + if (passwordEncoder.matches(request.newPassword(), member.getPassword())) { + throw new CustomRuntimeException(MemberErrorCode.DUPLICATE_EXISTING_PASSWORD); + } + + member.updatePassword(request.newPassword(), passwordEncoder); + + emailVerificationService.deleteSession(request.email()); + + } + public boolean isUniqueNickname(String nickname) { return !mentorRepository.existsByNickname(nickname) && !menteeRepository.existsByNickname(nickname); } diff --git a/src/main/java/es/princip/ringus/application/mentee/service/MyMenteeService.java b/src/main/java/es/princip/ringus/application/mentee/service/MyMenteeService.java index 53019b8..41ce4d4 100644 --- a/src/main/java/es/princip/ringus/application/mentee/service/MyMenteeService.java +++ b/src/main/java/es/princip/ringus/application/mentee/service/MyMenteeService.java @@ -1,6 +1,9 @@ package es.princip.ringus.application.mentee.service; +import es.princip.ringus.domain.exception.MemberErrorCode; import es.princip.ringus.domain.exception.MenteeErrorCode; +import es.princip.ringus.domain.member.Member; +import es.princip.ringus.domain.member.MemberRepository; import es.princip.ringus.domain.mentee.Mentee; import es.princip.ringus.domain.mentee.MenteeRepository; import es.princip.ringus.global.exception.CustomRuntimeException; @@ -13,11 +16,16 @@ @RequiredArgsConstructor @Transactional(readOnly = true) public class MyMenteeService { + private final MemberRepository memberRepository; private final MenteeRepository menteeRepository; public MyMenteeResponse getDetailBy(Long mentorId) { Mentee mentee = menteeRepository.findByMemberId(mentorId) .orElseThrow(() -> new CustomRuntimeException(MenteeErrorCode.MENTEE_NOT_FOUND)); - return MyMenteeResponse.from(mentee); + + Member member = memberRepository.findById(mentee.getMemberId()) + .orElseThrow(() -> new CustomRuntimeException(MemberErrorCode.MEMBER_NOT_FOUND)); + + return MyMenteeResponse.from(member,mentee); } } diff --git a/src/main/java/es/princip/ringus/application/mentor/service/MentorService.java b/src/main/java/es/princip/ringus/application/mentor/service/MentorService.java index 1bfc061..1029bb5 100644 --- a/src/main/java/es/princip/ringus/application/mentor/service/MentorService.java +++ b/src/main/java/es/princip/ringus/application/mentor/service/MentorService.java @@ -7,6 +7,7 @@ import es.princip.ringus.domain.member.MemberRepository; import es.princip.ringus.domain.mentor.Mentor; import es.princip.ringus.domain.mentor.MentorRepository; +import es.princip.ringus.domain.mentoring.MentoringRepository; import es.princip.ringus.domain.support.CursorResponse; import es.princip.ringus.global.exception.CustomRuntimeException; import es.princip.ringus.presentation.mentor.dto.*; @@ -25,6 +26,7 @@ public class MentorService { private final MemberRepository memberRepository; private final MentorRepository mentorRepository; + private final MentoringRepository mentoringRepository; @Transactional public Long register(Long memberId, MentorRequest request) { @@ -47,7 +49,7 @@ public Long register(Long memberId, MentorRequest request) { @Transactional public Long edit(Long memberId, EditMentorRequest request) { - Mentor mentor = mentorRepository.findById(memberId) + Mentor mentor = mentorRepository.findByMemberId(memberId) .orElseThrow(() -> new CustomRuntimeException(MentorErrorCode.MENTOR_PROFILE_NOT_FOUND)); mentor.edit(request); return mentor.getId(); @@ -57,6 +59,7 @@ public CursorResponse getMentorBy(CursorRequest request, Pag if(request.isBookmarked()) { Member member = memberRepository.findById(memberId) .orElseThrow(() -> new CustomRuntimeException(MemberErrorCode.MEMBER_NOT_FOUND)); + Long menteeId = member.getId(); if (member.isNotMentee()) { throw new CustomRuntimeException(MemberErrorCode.MEMBER_TYPE_DIFFERENT); @@ -71,6 +74,10 @@ public CursorResponse getMentorBy(CursorRequest request, Pag public MentorDetailResponse getDetailBy(Long mentorId) { Mentor mentor = mentorRepository.findById(mentorId) .orElseThrow(() -> new CustomRuntimeException(MentorErrorCode.MENTOR_PROFILE_NOT_FOUND)); - return MentorDetailResponse.from(mentor); + + return MentorDetailResponse.from( + mentor, + mentoringRepository.findMentoringCountBy(mentorId) + ); } } \ No newline at end of file diff --git a/src/main/java/es/princip/ringus/application/mentor/service/MyMentorService.java b/src/main/java/es/princip/ringus/application/mentor/service/MyMentorService.java index 8c9dcf9..d8e94ab 100644 --- a/src/main/java/es/princip/ringus/application/mentor/service/MyMentorService.java +++ b/src/main/java/es/princip/ringus/application/mentor/service/MyMentorService.java @@ -1,8 +1,12 @@ package es.princip.ringus.application.mentor.service; +import es.princip.ringus.domain.exception.MemberErrorCode; import es.princip.ringus.domain.exception.MentorErrorCode; +import es.princip.ringus.domain.member.Member; +import es.princip.ringus.domain.member.MemberRepository; import es.princip.ringus.domain.mentor.Mentor; import es.princip.ringus.domain.mentor.MentorRepository; +import es.princip.ringus.domain.mentoring.MentoringRepository; import es.princip.ringus.global.exception.CustomRuntimeException; import es.princip.ringus.presentation.mentor.dto.MyMentorResponse; import lombok.RequiredArgsConstructor; @@ -14,10 +18,16 @@ @Transactional(readOnly = true) public class MyMentorService { private final MentorRepository mentorRepository; + private final MemberRepository memberRepository; + private final MentoringRepository mentoringRepository; public MyMentorResponse getDetailBy(Long memberId) { Mentor mentor = mentorRepository.findByMemberId(memberId) .orElseThrow(() -> new CustomRuntimeException(MentorErrorCode.MENTOR_PROFILE_NOT_FOUND)); - return MyMentorResponse.from(mentor); + + Member member = memberRepository.findById(mentor.getMemberId()) + .orElseThrow(() -> new CustomRuntimeException(MemberErrorCode.MEMBER_NOT_FOUND)); + + return MyMentorResponse.from(member, mentor, mentoringRepository.findMentoringCountBy(mentor.getId())); } } diff --git a/src/main/java/es/princip/ringus/application/mentoring/MentoringService.java b/src/main/java/es/princip/ringus/application/mentoring/MentoringService.java index d91fa2a..14c8cff 100644 --- a/src/main/java/es/princip/ringus/application/mentoring/MentoringService.java +++ b/src/main/java/es/princip/ringus/application/mentoring/MentoringService.java @@ -1,7 +1,9 @@ package es.princip.ringus.application.mentoring; +import es.princip.ringus.application.notification.service.NotificationService; import es.princip.ringus.domain.exception.MenteeErrorCode; import es.princip.ringus.domain.exception.MentorErrorCode; +import es.princip.ringus.domain.exception.MentoringErrorCode; import es.princip.ringus.domain.mentee.Mentee; import es.princip.ringus.domain.mentee.MenteeRepository; import es.princip.ringus.domain.mentor.Mentor; @@ -10,8 +12,8 @@ import es.princip.ringus.domain.mentoring.MentoringRepository; import es.princip.ringus.domain.mentoring.MentoringStatus; import es.princip.ringus.global.exception.CustomRuntimeException; -import es.princip.ringus.presentation.mentoring.dto.CreateMentoringRequest; -import es.princip.ringus.presentation.mentoring.dto.MentoringResponse; +import es.princip.ringus.global.sender.dto.MentoringRequestMessage; +import es.princip.ringus.presentation.mentoring.dto.*; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -24,6 +26,7 @@ public class MentoringService { private final MentorRepository mentorRepository; private final MenteeRepository menteeRepository; + private final NotificationService notificationService; /** * ๋ฉ˜ํ† ๋ง ์‹ ์ฒญ ์ƒ์„ฑ */ @@ -39,11 +42,46 @@ public MentoringResponse createMentoring(CreateMentoringRequest request, Long me request.applyTimes(), request.mentoringMessage(), mentor, - mentee); + mentee + ); mentee.addMentoring(mentoring); mentor.addMentoring(mentoring); + notificationService.notify(MentoringRequestMessage.from(mentee, mentor, mentoring)); return MentoringResponse.from(mentoringRepository.save(mentoring)); } + + /** + * ๋ฉ˜ํ† ๋ง ์‹ ์ฒญ ์ทจ์†Œ + */ + @Transactional + public MentoringCancelResponse cancelMentoring(CancelMentoringRequest request, Long memberId) { + Mentoring mentoring = mentoringRepository.findById(request.mentoringId()) + .orElseThrow(() -> new CustomRuntimeException(MentoringErrorCode.MENTORING_NOT_FOUND)); + + if (!mentoring.getMentee().getMemberId().equals(memberId)) { + throw new CustomRuntimeException(MentoringErrorCode.MENTORING_CANCEL_NOT_POSSIBLE); + } + + mentoring.cancel(); + return MentoringCancelResponse.from(mentoring); + } + + /** + * ๋ฉ˜ํ† ๋ง ์‹ ์ฒญ ๊ฑฐ์ ˆ + */ + @Transactional + public MentoringRejectResponse rejectMentoring(RejectMentoringRequest request, Long memberId) { + Mentoring mentoring = mentoringRepository.findById(request.mentoringId()) + .orElseThrow(() -> new CustomRuntimeException(MentoringErrorCode.MENTORING_NOT_FOUND)); + + if (!mentoring.getMentor().getMemberId().equals(memberId)) { + throw new CustomRuntimeException(MentorErrorCode.MENTOR_NOT_FOUND); + } + + mentoring.reject(); + + return MentoringRejectResponse.from(mentoring); + } } \ No newline at end of file diff --git a/src/main/java/es/princip/ringus/application/notification/service/NotificationService.java b/src/main/java/es/princip/ringus/application/notification/service/NotificationService.java new file mode 100644 index 0000000..95aaf00 --- /dev/null +++ b/src/main/java/es/princip/ringus/application/notification/service/NotificationService.java @@ -0,0 +1,26 @@ +package es.princip.ringus.application.notification.service; + +import es.princip.ringus.domain.notification.Notification; +import es.princip.ringus.domain.notification.NotificationRepository; +import es.princip.ringus.global.factory.NotificationMessageFactory; +import es.princip.ringus.global.sender.NotificationSender; +import es.princip.ringus.global.sender.dto.MentoringRequestMessage; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class NotificationService { + + private final NotificationSender notificationSender; + private final NotificationMessageFactory notificationMessageFactory; + private final NotificationRepository notificationRepository; + + public void notify(MentoringRequestMessage request) { + Notification notification = notificationMessageFactory.mentoringRequestMessage(request); + notificationRepository.save(notification); + notificationSender.send(notification); + } +} \ No newline at end of file diff --git a/src/main/java/es/princip/ringus/domain/bookmark/BookmarkQueryDslRepository.java b/src/main/java/es/princip/ringus/domain/bookmark/BookmarkQueryDslRepository.java new file mode 100644 index 0000000..c956ef1 --- /dev/null +++ b/src/main/java/es/princip/ringus/domain/bookmark/BookmarkQueryDslRepository.java @@ -0,0 +1,5 @@ +package es.princip.ringus.domain.bookmark; + +public interface BookmarkQueryDslRepository { + Boolean isBookmarked(Long memberId, Long mentorId); +} diff --git a/src/main/java/es/princip/ringus/domain/bookmark/BookmarkQueryDslRepositoryImpl.java b/src/main/java/es/princip/ringus/domain/bookmark/BookmarkQueryDslRepositoryImpl.java new file mode 100644 index 0000000..ca70fd6 --- /dev/null +++ b/src/main/java/es/princip/ringus/domain/bookmark/BookmarkQueryDslRepositoryImpl.java @@ -0,0 +1,19 @@ +package es.princip.ringus.domain.bookmark; + +import es.princip.ringus.domain.support.QueryDslSupport; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +import static es.princip.ringus.domain.bookmark.QBookmark.bookmark; + +@Repository +@RequiredArgsConstructor +public class BookmarkQueryDslRepositoryImpl extends QueryDslSupport implements BookmarkQueryDslRepository { + @Override + public Boolean isBookmarked(Long memberId, Long mentorId) { + return queryFactory.select() + .from(bookmark) + .where(bookmark.mentor.id.eq(mentorId).and(bookmark.mentee.memberId.eq(memberId))) + .fetchCount() > 0; + } +} diff --git a/src/main/java/es/princip/ringus/domain/bookmark/BookmarkRepository.java b/src/main/java/es/princip/ringus/domain/bookmark/BookmarkRepository.java index ec217a3..86a91b0 100644 --- a/src/main/java/es/princip/ringus/domain/bookmark/BookmarkRepository.java +++ b/src/main/java/es/princip/ringus/domain/bookmark/BookmarkRepository.java @@ -8,7 +8,7 @@ import java.util.Optional; @Repository -public interface BookmarkRepository extends JpaRepository { +public interface BookmarkRepository extends JpaRepository, BookmarkQueryDslRepository { Optional findByMentorAndMentee(Mentor mentor, Mentee mentee); Optional findByMentee(Mentee mentee); diff --git a/src/main/java/es/princip/ringus/domain/exception/EmailErrorCode.java b/src/main/java/es/princip/ringus/domain/exception/EmailErrorCode.java index 87ab72a..d9fa9fa 100644 --- a/src/main/java/es/princip/ringus/domain/exception/EmailErrorCode.java +++ b/src/main/java/es/princip/ringus/domain/exception/EmailErrorCode.java @@ -5,7 +5,7 @@ public enum EmailErrorCode implements ErrorCode { - SUCESSS_VALILDATION(HttpStatus.OK, "์„ฑ๊ณต์ ์œผ๋กœ ์ธ์ฆ๋˜์—ˆ์Šต๋‹ˆ๋‹ค"), + SUCESSS_VAILDATION(HttpStatus.OK, "์„ฑ๊ณต์ ์œผ๋กœ ์ธ์ฆ๋˜์—ˆ์Šต๋‹ˆ๋‹ค"), TTL_EXPIRED(HttpStatus.FORBIDDEN, "TTL ๋งŒ๋ฃŒ"), ERROR_EXCEEDED_ATTEMPTS(HttpStatus.FORBIDDEN, "์ธ์ฆ๋ฒˆํ˜ธ ํ‹€๋ฆผ ํšŸ์ˆ˜ 5ํšŒ ์ด์ƒ, ์ƒˆ๋กœ์šด ์ธ์ฆ๋ฒˆํ˜ธ๋ฅผ ๋ฐœ๊ธ‰ ๋ฐ›์•„์ฃผ์„ธ์š”"), ERROR_INVALID_CODE(HttpStatus.BAD_REQUEST, "์ธ์ฆ๋ฒˆํ˜ธ๊ฐ€ ํ‹€๋ฆฝ๋‹ˆ๋‹ค"), diff --git a/src/main/java/es/princip/ringus/domain/exception/FileErrorCode.java b/src/main/java/es/princip/ringus/domain/exception/FileErrorCode.java new file mode 100644 index 0000000..408a498 --- /dev/null +++ b/src/main/java/es/princip/ringus/domain/exception/FileErrorCode.java @@ -0,0 +1,36 @@ +package es.princip.ringus.domain.exception; + +import es.princip.ringus.global.exception.ErrorCode; +import org.springframework.http.HttpStatus; + +public enum FileErrorCode implements ErrorCode { + FILE_NOT_FOUND(HttpStatus.NOT_FOUND,"ํŒŒ์ผ์„ ์ฐพ์„ ์ˆ˜ ์—†์Œ"), + FILE_UPLOAD_FAILED(HttpStatus.BAD_REQUEST,"ํŒŒ์ผ ์—…๋กœ๋“œ ์‹คํŒจ"), + FILE_DELETE_FAILED(HttpStatus.BAD_REQUEST,"ํŒŒ์ผ ์‚ญ์ œ ์‹คํŒจ"), + FILE_DOWNLOAD_FAILED(HttpStatus.BAD_REQUEST,"ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ ์‹คํŒจ"), + FILE_NOT_ALLOWED(HttpStatus.BAD_REQUEST,"ํ—ˆ์šฉ๋˜์ง€ ์•Š๋Š” ํŒŒ์ผ ํ˜•์‹"), + FILE_SIZE_EXCEEDED(HttpStatus.BAD_REQUEST,"ํŒŒ์ผ ํฌ๊ธฐ ์ดˆ๊ณผ"); + + FileErrorCode( HttpStatus status,String message) { + this.status = status; + this.message = message; + } + + private final String message; + private final HttpStatus status; + + @Override + public HttpStatus status() { + return this.status; + } + + @Override + public String message() { + return this.message; + } + + @Override + public String code() { + return this.name(); + } +} diff --git a/src/main/java/es/princip/ringus/domain/exception/MemberErrorCode.java b/src/main/java/es/princip/ringus/domain/exception/MemberErrorCode.java index 8e65533..4bd2a77 100644 --- a/src/main/java/es/princip/ringus/domain/exception/MemberErrorCode.java +++ b/src/main/java/es/princip/ringus/domain/exception/MemberErrorCode.java @@ -7,7 +7,9 @@ public enum MemberErrorCode implements ErrorCode { SESSION_EXPIRED(HttpStatus.UNAUTHORIZED, "์„ธ์…˜์ด ๊ฑฐ๋ถ€๋จ"), MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND, "๋ฉค๋ฒ„๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Œ"), - MEMBER_TYPE_DIFFERENT(HttpStatus.BAD_REQUEST, "๋‹ค๋ฅธ ๋ฉค๋ฒ„ ํƒ€์ž…"); + MEMBER_TYPE_DIFFERENT(HttpStatus.BAD_REQUEST, "๋‹ค๋ฅธ ๋ฉค๋ฒ„ ํƒ€์ž…"), + INVAILD_PASSWORD(HttpStatus.BAD_REQUEST, "๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์œ ํšจํ•˜์ง€ ์•Š์Œ"), + DUPLICATE_EXISTING_PASSWORD(HttpStatus.BAD_REQUEST, "์ด๋ฏธ ์‚ฌ์šฉ ์ค‘์ธ ๋น„๋ฐ€๋ฒˆํ˜ธ์ž…๋‹ˆ๋‹ค."); MemberErrorCode(HttpStatus status, String message) { this.status = status; diff --git a/src/main/java/es/princip/ringus/domain/exception/MentoringErrorCode.java b/src/main/java/es/princip/ringus/domain/exception/MentoringErrorCode.java index 2038873..f71135d 100644 --- a/src/main/java/es/princip/ringus/domain/exception/MentoringErrorCode.java +++ b/src/main/java/es/princip/ringus/domain/exception/MentoringErrorCode.java @@ -9,7 +9,8 @@ public enum MentoringErrorCode implements ErrorCode { MENTORING_TIME_CONVERT_ERROR(HttpStatus.NOT_MODIFIED,"์‹ ์ฒญ ์‹œ๊ฐ„ ๋ณ€ํ™˜ ์˜ค๋ฅ˜"), MENTORING_TIME_ERROR(HttpStatus.BAD_REQUEST,"์‹ ์ฒญ ๊ฐ€๋Šฅ ์‹œ๊ฐ„ ์˜ค๋ฅ˜"), MENTORING_STATUS_NOT_WAITING(HttpStatus.BAD_REQUEST,"๋ฉ˜ํ† ๋ง ๋Œ€๊ธฐ ์ƒํƒœ๊ฐ€ ์•„๋‹˜"), - MENTORING_TIME_NOT_MATCH(HttpStatus.BAD_REQUEST,"๋ฉ˜ํ‹ฐ์˜ ์ œ์•ˆ ์‹œ๊ฐ„์— ๋ฉ˜ํ† ๋ง ์‹œ๊ฐ„์ด ํฌํ•จ๋˜์ง€ ์•Š์Œ"); + MENTORING_TIME_NOT_MATCH(HttpStatus.BAD_REQUEST,"๋ฉ˜ํ‹ฐ์˜ ์ œ์•ˆ ์‹œ๊ฐ„์— ๋ฉ˜ํ† ๋ง ์‹œ๊ฐ„์ด ํฌํ•จ๋˜์ง€ ์•Š์Œ"), + MENTORING_CANCEL_NOT_POSSIBLE(HttpStatus.BAD_REQUEST,"๋ฉ˜ํ† ๋ง ์ทจ์†Œ ๋ถˆ๊ฐ€๋Šฅ"); MentoringErrorCode(HttpStatus status , String message) { this.status = status; diff --git a/src/main/java/es/princip/ringus/domain/member/Member.java b/src/main/java/es/princip/ringus/domain/member/Member.java index 077c531..0096d6b 100644 --- a/src/main/java/es/princip/ringus/domain/member/Member.java +++ b/src/main/java/es/princip/ringus/domain/member/Member.java @@ -101,4 +101,8 @@ public boolean isMentor() { public boolean isMentee() { return this.memberType == MemberType.ROLE_MENTEE; } + + public void updatePassword(String newPassword, PasswordEncoder passwordEncoder) { + this.password = passwordEncoder.encode(newPassword); + } } diff --git a/src/main/java/es/princip/ringus/domain/mentee/EducationLevelType.java b/src/main/java/es/princip/ringus/domain/mentee/EducationLevelType.java deleted file mode 100644 index b3ecfd4..0000000 --- a/src/main/java/es/princip/ringus/domain/mentee/EducationLevelType.java +++ /dev/null @@ -1,6 +0,0 @@ -package es.princip.ringus.domain.mentee; - - -public enum EducationLevelType { - BACHELOR, MASTER, DOCTOR, OTHER; -} diff --git a/src/main/java/es/princip/ringus/domain/mentee/Mentee.java b/src/main/java/es/princip/ringus/domain/mentee/Mentee.java index 39d68bb..46c9a6a 100644 --- a/src/main/java/es/princip/ringus/domain/mentee/Mentee.java +++ b/src/main/java/es/princip/ringus/domain/mentee/Mentee.java @@ -38,6 +38,11 @@ public class Mentee { private String introduction; @Embedded + @AttributeOverrides({ + @AttributeOverride(name = "fileName", column = @Column(name = "profile_image_file_name")), + @AttributeOverride(name = "filePath", column = @Column(name = "profile_image_file_path")), + @AttributeOverride(name = "fileSize", column = @Column(name = "profile_image_file_size")) + }) private ProfileImage profileImage; @Column(name = "member_id") diff --git a/src/main/java/es/princip/ringus/domain/mentor/Mentor.java b/src/main/java/es/princip/ringus/domain/mentor/Mentor.java index df9a861..18a2701 100644 --- a/src/main/java/es/princip/ringus/domain/mentor/Mentor.java +++ b/src/main/java/es/princip/ringus/domain/mentor/Mentor.java @@ -13,8 +13,6 @@ import java.util.ArrayList; import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; @Getter @Entity @@ -52,7 +50,7 @@ public class Mentor { @CollectionTable(name = "mentor_mentoring_fields", joinColumns = @JoinColumn(name = "mentor_id")) @Enumerated(EnumType.STRING) @Column(name = "mentoring_field") - private Set mentoringField; + private List mentoringField; // ํ•ด์‹œํƒœ๊ทธ @ElementCollection(fetch = FetchType.EAGER) @@ -68,9 +66,19 @@ public class Mentor { // ํฌํŠธํด๋ฆฌ์˜ค @Embedded + @AttributeOverrides({ + @AttributeOverride(name = "fileName", column = @Column(name = "portfolio_file_name")), + @AttributeOverride(name = "filePath", column = @Column(name = "portfolio_file_path")), + @AttributeOverride(name = "fileSize", column = @Column(name = "portfolio_file_size")) + }) private Portfolio portfolio; @Embedded + @AttributeOverrides({ + @AttributeOverride(name = "fileName", column = @Column(name = "profile_image_file_name")), + @AttributeOverride(name = "filePath", column = @Column(name = "profile_image_file_path")), + @AttributeOverride(name = "fileSize", column = @Column(name = "profile_image_file_size")) + }) private ProfileImage profileImage; @Column(name = "member_id") @@ -83,7 +91,7 @@ public Mentor( final Organization organization, final Introduction introduction, final Timezone timezone, - final Set mentoringField, + final List mentoringField, final List hashtags, final String message, final Portfolio portfolio, @@ -109,7 +117,7 @@ public void edit(final EditMentorRequest request) { this.organization = request.organization().toEntity(); this.introduction = request.introduction().toEntity(); this.timezone = request.timezone().toEntity(); - this.mentoringField = request.mentoringField().stream().map(MentoringField::valueOf).collect(Collectors.toSet()); + this.mentoringField = request.mentoringField().stream().map(MentoringField::from).toList(); this.hashtags = request.hashtags().stream().map(Hashtag::new).toList(); this.message = request.message(); this.portfolio = request.portfolio().toEntity(); diff --git a/src/main/java/es/princip/ringus/domain/mentor/MentorQueryDslRepositoryImpl.java b/src/main/java/es/princip/ringus/domain/mentor/MentorQueryDslRepositoryImpl.java index 8a5ba40..a59f5f4 100644 --- a/src/main/java/es/princip/ringus/domain/mentor/MentorQueryDslRepositoryImpl.java +++ b/src/main/java/es/princip/ringus/domain/mentor/MentorQueryDslRepositoryImpl.java @@ -3,6 +3,8 @@ import com.querydsl.core.Tuple; import com.querydsl.core.types.OrderSpecifier; import com.querydsl.jpa.impl.JPAQuery; +import es.princip.ringus.domain.bookmark.BookmarkRepository; +import es.princip.ringus.domain.mentoring.MentoringRepository; import es.princip.ringus.domain.support.QueryDslSupport; import es.princip.ringus.presentation.mentor.dto.CursorRequest; import es.princip.ringus.presentation.mentor.dto.MentorCardResponse; @@ -23,6 +25,9 @@ @RequiredArgsConstructor public class MentorQueryDslRepositoryImpl extends QueryDslSupport implements MentorQueryDslRepository{ + private final MentoringRepository mentoringRepository; + private final BookmarkRepository bookmarkRepository; + private List fetchMentor( final Pageable pageable, final CursorRequest request, @@ -31,33 +36,33 @@ private List fetchMentor( JPAQuery query; if (request.isSuggested()) { query = queryFactory.select( - mentor.id, - mentor.nickname, - mentor.profileImage, - mentor.introduction, - mentor.organization, - mentor.message, - mentoring.mentoringStatus - ) - .from(mentor); + mentor.id, + mentor.nickname, + mentor.profileImage, + mentor.introduction, + mentor.organization, + mentor.message, + mentoring.mentoringStatus + ) + .from(mentor); } else { query = queryFactory.select( - mentor.id, - mentor.nickname, - mentor.profileImage, - mentor.introduction, - mentor.organization, - mentor.message - ) - .from(mentor); + mentor.id, + mentor.nickname, + mentor.profileImage, + mentor.introduction, + mentor.organization, + mentor.message + ) + .from(mentor); } queryFilter(query, request, memberId); query - .where(request.cursor() != null ? mentor.id.goe(request.cursor()) : mentor.id.isNotNull()) - .orderBy(new OrderSpecifier<>(ASC, mentor.id)) - .limit(pageable.getPageSize() + 1); + .where(request.cursor() != null ? mentor.id.goe(request.cursor()) : mentor.id.isNotNull()) + .orderBy(new OrderSpecifier<>(ASC, mentor.id)) + .limit(pageable.getPageSize() + 1); return query.fetch(); } @@ -67,33 +72,47 @@ private List fetchContent( final CursorRequest request, final Long memberId ) { - return fetchMentor(pageable, request, memberId).stream() - .map(tuple -> { - if (request.isSuggested()) { - // ๐Ÿ”ฅ ๋ถ๋งˆํฌ๋œ ๋ฉ˜ํ†  ์กฐํšŒ (๋ฉ˜ํ†  ๋ชฉ๋ก ์กฐํšŒ) - return MentorCardResponse.of( - tuple.get(mentor.id), - tuple.get(mentor.nickname), - tuple.get(mentor.profileImage), - tuple.get(mentor.introduction), - tuple.get(mentor.organization), - tuple.get(mentoring.mentoringStatus.stringValue()) - - ); - } else { - return MentorCardResponse.of( - tuple.get(mentor.id), - tuple.get(mentor.nickname), - tuple.get(mentor.profileImage), - tuple.get(mentor.introduction), - tuple.get(mentor.organization), - tuple.get(mentor.message), - 0 - ); + .map(tuple -> { + if (request.isSuggested()) { + return MentorCardResponse.of( + tuple.get(mentor.id), + tuple.get(mentor.nickname), + tuple.get(mentor.profileImage), + tuple.get(mentor.introduction), + tuple.get(mentor.organization), + tuple.get(mentor.message), + tuple.get(mentoring.mentoringStatus.stringValue()), + mentoringRepository.findMentoringCountBy(tuple.get(mentor.id)) + ); + } else if (request.isBookmarked()) { + if (memberId == null) { + throw new IllegalArgumentException("๋ถ๋งˆํฌ๋ฅผ ์กฐํšŒํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” memberId๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค."); } - }) - .toList(); + + return MentorCardResponse.of( + tuple.get(mentor.id), + tuple.get(mentor.nickname), + tuple.get(mentor.profileImage), + tuple.get(mentor.introduction), + tuple.get(mentor.organization), + tuple.get(mentor.message), + mentoringRepository.findMentoringCountBy(tuple.get(mentor.id)), + bookmarkRepository.isBookmarked(memberId, tuple.get(mentor.id)) + ); + } else { + return MentorCardResponse.of( + tuple.get(mentor.id), + tuple.get(mentor.nickname), + tuple.get(mentor.profileImage), + tuple.get(mentor.introduction), + tuple.get(mentor.organization), + tuple.get(mentor.message), + mentoringRepository.findMentoringCountBy(tuple.get(mentor.id)) + ); + } + }) + .toList(); } private void queryFilter( @@ -103,18 +122,18 @@ private void queryFilter( ) { if(request.isBookmarked() && memberId != null){ query - .join(mentee).on(mentee.memberId.eq(memberId)) - .join(bookmark).on(mentor.id.eq(bookmark.mentor.id)) - .where( - bookmark.mentee.id.eq(mentee.id) - ); + .join(mentee).on(mentee.memberId.eq(memberId)) + .join(bookmark).on(mentor.id.eq(bookmark.mentor.id)) + .where( + bookmark.mentee.id.eq(mentee.id) + ); } - // ๋ฉ˜ํ† ๋ง ์ƒํƒœ ํ•„ํ„ฐ ์ ์šฉ (๋ฉ˜ํ† ๋ง ์‹ ์ฒญ ํ˜„ํ™ฉ ์กฐํšŒ ์‹œ) + if (request.isSuggested()) { query.join(mentoring).on(mentoring.mentor.id.eq(mentor.id)) - .where( - mentoring.mentoringStatus.isNotNull() - ); + .where( + mentoring.mentoringStatus.isNotNull() + ); } } diff --git a/src/main/java/es/princip/ringus/domain/mentor/vo/Days.java b/src/main/java/es/princip/ringus/domain/mentor/vo/Days.java index 0449aea..35e479d 100644 --- a/src/main/java/es/princip/ringus/domain/mentor/vo/Days.java +++ b/src/main/java/es/princip/ringus/domain/mentor/vo/Days.java @@ -1,5 +1,42 @@ package es.princip.ringus.domain.mentor.vo; +import lombok.Getter; + +import java.util.Arrays; + +@Getter public enum Days { - MON, TUE, WED, THU, FRI, SAT, SUN + MON("MON", "์›”"), + TUE("TUE", "ํ™”"), + WED("WED", "์ˆ˜"), + THU("THU", "๋ชฉ"), + FRI("FRI", "๊ธˆ"), + SAT("SAT", "ํ† "), + SUN("SUN", "์ผ"); + + private final String code; + private final String kor; + + Days(String code, String kor) { + this.code = code; + this.kor = kor; + } + + public static String toKor(String code) { + return Arrays.stream(values()) + .filter(e -> e.code.equalsIgnoreCase(code)) + .findFirst() + .orElseThrow(() -> + new IllegalArgumentException("๋งคํ•‘๋˜๋Š” Days๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค: " + code) + ).kor; + } + + public static String fromCode(String kor) { + return Arrays.stream(values()) + .filter(e -> e.kor.equals(kor)) + .findFirst() + .orElseThrow( + () -> new IllegalArgumentException("๋งคํ•‘๋˜๋Š” Days๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค: " + kor) + ).code; + } } \ No newline at end of file diff --git a/src/main/java/es/princip/ringus/domain/mentor/vo/DetailedJob.java b/src/main/java/es/princip/ringus/domain/mentor/vo/DetailedJob.java index b3a95c6..70afb14 100644 --- a/src/main/java/es/princip/ringus/domain/mentor/vo/DetailedJob.java +++ b/src/main/java/es/princip/ringus/domain/mentor/vo/DetailedJob.java @@ -1,120 +1,123 @@ package es.princip.ringus.domain.mentor.vo; +import lombok.Getter; + +import java.util.Arrays; + +@Getter public enum DetailedJob { - // Marketing - BRAND_MARKETING(JobCategory.MARKETING), // ๋ธŒ๋žœ๋“œ ๋งˆ์ผ€ํŒ… - PERFORMANCE_MARKETING(JobCategory.MARKETING), // ํผํฌ๋จผ์Šค ๋งˆ์ผ€ํŒ… - DIGITAL_SOCIAL_MARKETING(JobCategory.MARKETING), // ๋””์ง€ํ„ธ/์†Œ์…œ ๋งˆ์ผ€ํŒ… - GROWTH_MARKETING(JobCategory.MARKETING), // ๊ทธ๋กœ์Šค ๋งˆ์ผ€ํŒ… - PR(JobCategory.MARKETING), // PR - AE(JobCategory.MARKETING), // AE - CONTENT_MARKETING(JobCategory.MARKETING), // ์ฝ˜ํ…์ธ  ๋งˆ์ผ€ํŒ… - CREATIVE_DIRECTING(JobCategory.MARKETING), // ํฌ๋ฆฌ์—์ดํ‹ฐ๋ธŒ ๋””๋ ‰ํŒ… - COPYWRITER(JobCategory.MARKETING), // ์นดํ”ผ๋ผ์ดํ„ฐ - MEDIA_PLANNER(JobCategory.MARKETING), // ๋ฏธ๋””์–ด ํ”Œ๋ž˜๋„ˆ - BROADCAST_PD(JobCategory.MARKETING), // ๋ฐฉ์†กPD/์˜์ƒPD - OTHER_MARKETING(JobCategory.MARKETING), // ๊ธฐํƒ€ - - // Service Planning - SERVICE_PLANNING(JobCategory.SERVICE_PLANNING), // ์„œ๋น„์Šค๊ธฐํš - PM_PO(JobCategory.SERVICE_PLANNING), // PM/PO - STRATEGY_PLANNING(JobCategory.SERVICE_PLANNING), // ์ „๋žต ๊ธฐํš - OPERATION_PLANNING(JobCategory.SERVICE_PLANNING), // ์šด์˜/๊ธฐํš - BUSINESS_DEVELOPMENT(JobCategory.SERVICE_PLANNING), // ์‚ฌ์—… ๊ฐœ๋ฐœ - CX_MANAGER(JobCategory.SERVICE_PLANNING), // CX ๋งค๋‹ˆ์ € - STARTUP(JobCategory.SERVICE_PLANNING), // ์ฐฝ์—… - OTHER_SERVICE_PLANNING(JobCategory.SERVICE_PLANNING), // ๊ธฐํƒ€ - - // Design - UX_UI_DESIGN(JobCategory.DESIGN), // UX/UI๋””์ž์ธ - GRAPHIC_DESIGN(JobCategory.DESIGN), // ๊ทธ๋ž˜ํ”ฝ ๋””์ž์ธ - PRODUCT_DESIGN(JobCategory.DESIGN), // ์ƒํ’ˆ ๋””์ž์ธ - BRAND_DESIGN(JobCategory.DESIGN), // ๋ธŒ๋žœ๋“œ ๋””์ž์ธ - WEB_DESIGN(JobCategory.DESIGN), // ์›น ๋””์ž์ธ - ART_DIRECTOR(JobCategory.DESIGN), // ์•„ํŠธ ๋””๋ ‰ํ„ฐ - OTHER_DESIGN(JobCategory.DESIGN), // ๊ธฐํƒ€ - - // Development - FRONTEND(JobCategory.DEVELOPMENT), // ํ”„๋ก ํŠธ์—”๋“œ - BACKEND(JobCategory.DEVELOPMENT), // ๋ฐฑ์—”๋“œ - FULLSTACK(JobCategory.DEVELOPMENT), // ํ’€์Šคํƒ ๊ฐœ๋ฐœ์ž - IOS_ANDROID(JobCategory.DEVELOPMENT), // iOS/Android ๊ฐœ๋ฐœ์ž - DEVOPS(JobCategory.DEVELOPMENT), // DevOps ์—”์ง€๋‹ˆ์–ด - CLOUD(JobCategory.DEVELOPMENT), // ํด๋ผ์šฐ๋“œ ์—”์ง€๋‹ˆ์–ด - SYSTEM_NETWORK(JobCategory.DEVELOPMENT), // ์‹œ์Šคํ…œ/๋„คํŠธ์›Œํฌ ์—”์ง€๋‹ˆ์–ด - SECURITY(JobCategory.DEVELOPMENT), // ๋ณด์•ˆ ์—”์ง€๋‹ˆ์–ด - OTHER_DEVELOPMENT(JobCategory.DEVELOPMENT), // ๊ธฐํƒ€ - - // Graduate School - DOMESTIC_GRADUATE_SCHOOL(JobCategory.GRADUATE_SCHOOL), // ๊ตญ๋‚ด ๋Œ€ํ•™์› - OVERSEAS_GRADUATE_SCHOOL(JobCategory.GRADUATE_SCHOOL), // ํ•ด์™ธ ๋Œ€ํ•™์› - OTHER_GRADUATE_SCHOOL(JobCategory.GRADUATE_SCHOOL), // ๊ธฐํƒ€ - - // HR Support - HR_PLANNING(JobCategory.HR_SUPPORT), // ์ธ์‚ฌ๊ธฐํš - RECRUITMENT(JobCategory.HR_SUPPORT), // ์ฑ„์šฉ๋‹ด๋‹น - TALENT_DEVELOPMENT(JobCategory.HR_SUPPORT), // ์ธ์žฌ์œก์„ฑ/๊ต์œก๋‹ด๋‹น - ORGANIZATION_CULTURE(JobCategory.HR_SUPPORT), // ์กฐ์ง๋ฌธํ™”๋‹ด๋‹น - LABOR(JobCategory.HR_SUPPORT), // ๋…ธ๋ฌด๋‹ด๋‹น - GENERAL_AFFAIRS(JobCategory.HR_SUPPORT), // ์ด๋ฌด/๊ฒฝ์˜์ง€์› - HR_OPERATION(JobCategory.HR_SUPPORT), // ์ธ์‚ฌ์šด์˜ - RECRUITER(JobCategory.HR_SUPPORT), // ๋ฆฌํฌ๋ฃจํ„ฐ - OTHER_HR_SUPPOReT(JobCategory.HR_SUPPORT), // ๊ธฐํƒ€ - - // Sales Customer - B2B_SALES(JobCategory.SALES_CUSTOMER), // ๊ธฐ์—…์˜์—…(B2B) - B2C_SALES(JobCategory.SALES_CUSTOMER), // ๊ฐœ์ธ์˜์—…(B2C) - OVERSEAS_SALES(JobCategory.SALES_CUSTOMER), // ํ•ด์™ธ์˜์—… - TECHNICAL_SALES(JobCategory.SALES_CUSTOMER), // ๊ธฐ์ˆ ์˜์—… - SOLUTION_CONSULTANT(JobCategory.SALES_CUSTOMER), // ์†”๋ฃจ์…˜ ์ปจ์„คํ„ดํŠธ - KAM(JobCategory.SALES_CUSTOMER), // ์ฃผ์š”๊ณ ๊ฐ๊ด€๋ฆฌ(KAM) - SALES_SUPPORT(JobCategory.SALES_CUSTOMER), // ์˜์—…๊ด€๋ฆฌ/์ง€์› - CSM_CX(JobCategory.SALES_CUSTOMER), // CSM/CX - OTHER_SALES_CUSTOMER(JobCategory.SALES_CUSTOMER), // ๊ธฐํƒ€ - - // Finance Consulting VC - CONSULTANT(JobCategory.FINANCE_CONSULTING_VC), // ์ปจ์„คํ„ดํŠธ - VC_INVESTMENT(JobCategory.FINANCE_CONSULTING_VC), // VC/ํˆฌ์ž - IB_PE_ALTERNATIVE_INVESTMENT(JobCategory.FINANCE_CONSULTING_VC), // IB/PE/๋Œ€์ฒดํˆฌ์ž - ANALYST(JobCategory.FINANCE_CONSULTING_VC), // ์—๋„๋ฆฌ์ŠคํŠธ - ACCOUNTING_FINANCE(JobCategory.FINANCE_CONSULTING_VC), // ํšŒ๊ณ„/์žฌ๋ฌด - OTHER_FINANCE_CONSULTING_VC(JobCategory.FINANCE_CONSULTING_VC), // ๊ธฐํƒ€ - - // Data - DATA_SCIENTIST(JobCategory.DATA), // ๋ฐ์ดํ„ฐ ์‚ฌ์ด์–ธํ‹ฐ์ŠคํŠธ - DATA_ENGINEER(JobCategory.DATA), // ๋ฐ์ดํ„ฐ ์—”์ง€๋‹ˆ์–ด - DATA_ANALYST(JobCategory.DATA), // ๋ฐ์ดํ„ฐ ์• ๋„๋ฆฌ์ŠคํŠธ - BI_ENGINEER(JobCategory.DATA), // BI ์—”์ง€๋‹ˆ์–ด - MACHINE_LEARNING_ENGINEER(JobCategory.DATA), // ๋จธ์‹ ๋Ÿฌ๋‹ ์—”์ง€๋‹ˆ์–ด - DATA_ARCHITECT(JobCategory.DATA), // ๋ฐ์ดํ„ฐ ์•„ํ‚คํ…ํŠธ - RESEARCH_ANALYST(JobCategory.DATA), // ๋ฆฌ์„œ์น˜ ์• ๋„๋ฆฌ์ŠคํŠธ - OTHER_DATA(JobCategory.DATA), // ๊ธฐํƒ€ - - // Medical - CLINICAL_DOCTOR(JobCategory.MEDICAL), // ์ž„์ƒ์˜์‚ฌ - CLINICAL_RESEARCHER(JobCategory.MEDICAL), // ์ž„์ƒ์—ฐ๊ตฌ์› - MEDICAL_DEVICE_RND(JobCategory.MEDICAL), // ์˜๋ฃŒ๊ธฐ๊ธฐ ์—ฐ๊ตฌ๊ฐœ๋ฐœ - PHARMACEUTICAL_RESEARCHER(JobCategory.MEDICAL), // ์ œ์•ฝํšŒ์‚ฌ ์—ฐ๊ตฌ์› - BIO_RESEARCHER(JobCategory.MEDICAL), // ๋ฐ”์ด์˜ค ์—ฐ๊ตฌ์› - OTHER_MEDICAL(JobCategory.MEDICAL), // ๊ธฐํƒ€ - - // Legal - LAWYER(JobCategory.LEGAL), // ๋ณ€ํ˜ธ์‚ฌ - LEGAL_COUNSEL(JobCategory.LEGAL), // ๋ฒ•๋ฌด๋‹ด๋‹น - PATENT(JobCategory.LEGAL), // ํŠนํ—ˆ๋‹ด๋‹น - COMPLIANCE(JobCategory.LEGAL), // ์ค€๋ฒ•๊ฐ์‹œ์ธ(์ปดํ”Œ๋ผ์ด์–ธ์Šค) - LAW_FIRM_STAFF(JobCategory.LEGAL), // ๋ฒ•๋ฌด๋ฒ•์ธ ์‚ฌ๋ฌด์ง - LEGAL_ADVISOR(JobCategory.LEGAL), // ๋ฒ•๋ฅ ์ž๋ฌธ - PATENT_ENGINEER(JobCategory.LEGAL), // ํŠนํ—ˆ์—”์ง€๋‹ˆ์–ด - OTHER_LEGAL(JobCategory.LEGAL); // ๊ธฐํƒ€ + BRAND_MARKETING("BRAND_MARKETING", "๋ธŒ๋žœ๋“œ ๋งˆ์ผ€ํŒ…", JobCategory.MARKETING), + PERFORMANCE_MARKETING("PERFORMANCE_MARKETING", "ํผํฌ๋จผ์Šค ๋งˆ์ผ€ํŒ…", JobCategory.MARKETING), + DIGITAL_SOCIAL_MARKETING("DIGITAL_SOCIAL_MARKETING", "๋””์ง€ํ„ธ/์†Œ์…œ ๋งˆ์ผ€ํŒ…", JobCategory.MARKETING), + GROWTH_MARKETING("GROWTH_MARKETING", "๊ทธ๋กœ์Šค ๋งˆ์ผ€ํŒ…", JobCategory.MARKETING), + PR("PR", "PR", JobCategory.MARKETING), + AE("AE", "AE", JobCategory.MARKETING), + CONTENT_MARKETING("CONTENT_MARKETING", "์ฝ˜ํ…์ธ  ๋งˆ์ผ€ํŒ…", JobCategory.MARKETING), + CREATIVE_DIRECTING("CREATIVE_DIRECTING", "ํฌ๋ฆฌ์—์ดํ‹ฐ๋ธŒ ๋””๋ ‰ํŒ…", JobCategory.MARKETING), + COPYWRITER("COPYWRITER", "์นดํ”ผ๋ผ์ดํ„ฐ", JobCategory.MARKETING), + MEDIA_PLANNER("MEDIA_PLANNER", "๋ฏธ๋””์–ด ํ”Œ๋ž˜๋„ˆ", JobCategory.MARKETING), + BROADCAST_PD("BROADCAST_PD", "๋ฐฉ์†กPD/์˜์ƒPD", JobCategory.MARKETING), + OTHER_MARKETING("OTHER_MARKETING", "๊ธฐํƒ€", JobCategory.MARKETING), + + SERVICE_PLANNING("SERVICE_PLANNING", "์„œ๋น„์Šค๊ธฐํš", JobCategory.SERVICE_PLANNING), + PM_PO("PM_PO", "PM/PO", JobCategory.SERVICE_PLANNING), + STRATEGY_PLANNING("STRATEGY_PLANNING", "์ „๋žต ๊ธฐํš", JobCategory.SERVICE_PLANNING), + OPERATION_PLANNING("OPERATION_PLANNING", "์šด์˜/๊ธฐํš", JobCategory.SERVICE_PLANNING), + BUSINESS_DEVELOPMENT("BUSINESS_DEVELOPMENT", "์‚ฌ์—… ๊ฐœ๋ฐœ", JobCategory.SERVICE_PLANNING), + CX_MANAGER("CX_MANAGER", "CX ๋งค๋‹ˆ์ €", JobCategory.SERVICE_PLANNING), + STARTUP("STARTUP", "์ฐฝ์—…", JobCategory.SERVICE_PLANNING), + OTHER_SERVICE_PLANNING("OTHER_SERVICE_PLANNING", "๊ธฐํƒ€", JobCategory.SERVICE_PLANNING), + + UX_UI_DESIGN("UX_UI_DESIGN", "UX/UI ๋””์ž์ธ", JobCategory.DESIGN), + GRAPHIC_DESIGN("GRAPHIC_DESIGN", "๊ทธ๋ž˜ํ”ฝ ๋””์ž์ธ", JobCategory.DESIGN), + PRODUCT_DESIGN("PRODUCT_DESIGN", "์ƒํ’ˆ ๋””์ž์ธ", JobCategory.DESIGN), + BRAND_DESIGN("BRAND_DESIGN", "๋ธŒ๋žœ๋“œ ๋””์ž์ธ", JobCategory.DESIGN), + WEB_DESIGN("WEB_DESIGN", "์›น ๋””์ž์ธ", JobCategory.DESIGN), + ART_DIRECTOR("ART_DIRECTOR", "์•„ํŠธ ๋””๋ ‰ํ„ฐ", JobCategory.DESIGN), + OTHER_DESIGN("OTHER_DESIGN", "๊ธฐํƒ€", JobCategory.DESIGN), + + FRONTEND("FRONTEND", "ํ”„๋ก ํŠธ์—”๋“œ", JobCategory.DEVELOPMENT), + BACKEND("BACKEND", "๋ฐฑ์—”๋“œ", JobCategory.DEVELOPMENT), + FULLSTACK("FULLSTACK", "ํ’€์Šคํƒ ๊ฐœ๋ฐœ์ž", JobCategory.DEVELOPMENT), + IOS_ANDROID("IOS_ANDROID", "iOS/Android ๊ฐœ๋ฐœ์ž", JobCategory.DEVELOPMENT), + DEVOPS("DEVOPS", "DevOps ์—”์ง€๋‹ˆ์–ด", JobCategory.DEVELOPMENT), + CLOUD("CLOUD", "ํด๋ผ์šฐ๋“œ ์—”์ง€๋‹ˆ์–ด", JobCategory.DEVELOPMENT), + SYSTEM_NETWORK("SYSTEM_NETWORK", "์‹œ์Šคํ…œ/๋„คํŠธ์›Œํฌ ์—”์ง€๋‹ˆ์–ด", JobCategory.DEVELOPMENT), + SECURITY("SECURITY", "๋ณด์•ˆ ์—”์ง€๋‹ˆ์–ด", JobCategory.DEVELOPMENT), + OTHER_DEVELOPMENT("OTHER_DEVELOPMENT", "๊ธฐํƒ€", JobCategory.DEVELOPMENT), + + DOMESTIC_GRADUATE_SCHOOL("DOMESTIC_GRADUATE_SCHOOL", "๊ตญ๋‚ด ๋Œ€ํ•™์›", JobCategory.GRADUATE_SCHOOL), + OVERSEAS_GRADUATE_SCHOOL("OVERSEAS_GRADUATE_SCHOOL", "ํ•ด์™ธ ๋Œ€ํ•™์›", JobCategory.GRADUATE_SCHOOL), + OTHER_GRADUATE_SCHOOL("OTHER_GRADUATE_SCHOOL", "๊ธฐํƒ€", JobCategory.GRADUATE_SCHOOL), + + HR_PLANNING("HR_PLANNING", "์ธ์‚ฌ๊ธฐํš", JobCategory.HR_SUPPORT), + RECRUITMENT("RECRUITMENT", "์ฑ„์šฉ๋‹ด๋‹น", JobCategory.HR_SUPPORT), + TALENT_DEVELOPMENT("TALENT_DEVELOPMENT", "์ธ์žฌ์œก์„ฑ/๊ต์œก๋‹ด๋‹น", JobCategory.HR_SUPPORT), + ORGANIZATION_CULTURE("ORGANIZATION_CULTURE", "์กฐ์ง๋ฌธํ™”๋‹ด๋‹น", JobCategory.HR_SUPPORT), + LABOR("LABOR", "๋…ธ๋ฌด๋‹ด๋‹น", JobCategory.HR_SUPPORT), + GENERAL_AFFAIRS("GENERAL_AFFAIRS", "์ด๋ฌด/๊ฒฝ์˜์ง€์›", JobCategory.HR_SUPPORT), + HR_OPERATION("HR_OPERATION", "์ธ์‚ฌ์šด์˜", JobCategory.HR_SUPPORT), + RECRUITER("RECRUITER", "๋ฆฌํฌ๋ฃจํ„ฐ", JobCategory.HR_SUPPORT), + OTHER_HR_SUPPORT("OTHER_HR_SUPPORT", "๊ธฐํƒ€", JobCategory.HR_SUPPORT), + + B2B_SALES("B2B_SALES", "๊ธฐ์—…์˜์—…(B2B)", JobCategory.SALES_CUSTOMER), + B2C_SALES("B2C_SALES", "๊ฐœ์ธ์˜์—…(B2C)", JobCategory.SALES_CUSTOMER), + OVERSEAS_SALES("OVERSEAS_SALES", "ํ•ด์™ธ์˜์—…", JobCategory.SALES_CUSTOMER), + TECHNICAL_SALES("TECHNICAL_SALES", "๊ธฐ์ˆ ์˜์—…", JobCategory.SALES_CUSTOMER), + SOLUTION_CONSULTANT("SOLUTION_CONSULTANT", "์†”๋ฃจ์…˜ ์ปจ์„คํ„ดํŠธ", JobCategory.SALES_CUSTOMER), + KAM("KAM", "์ฃผ์š”๊ณ ๊ฐ๊ด€๋ฆฌ(KAM)", JobCategory.SALES_CUSTOMER), + SALES_SUPPORT("SALES_SUPPORT", "์˜์—…๊ด€๋ฆฌ/์ง€์›", JobCategory.SALES_CUSTOMER), + CSM_CX("CSM_CX", "CSM/CX", JobCategory.SALES_CUSTOMER), + OTHER_SALES_CUSTOMER("OTHER_SALES_CUSTOMER", "๊ธฐํƒ€", JobCategory.SALES_CUSTOMER), + + CONSULTANT("CONSULTANT", "์ปจ์„คํ„ดํŠธ", JobCategory.FINANCE), + VC_INVESTMENT("VC_INVESTMENT", "VC/ํˆฌ์ž", JobCategory.FINANCE), + IB_PE_ALTERNATIVE_INVESTMENT("IB_PE_ALTERNATIVE_INVESTMENT", "IB/PE/๋Œ€์ฒดํˆฌ์ž", JobCategory.FINANCE), + ANALYST("ANALYST", "์• ๋„๋ฆฌ์ŠคํŠธ", JobCategory.FINANCE), + ACCOUNTING_FINANCE("ACCOUNTING_FINANCE", "ํšŒ๊ณ„/์žฌ๋ฌด", JobCategory.FINANCE), + OTHER_FINANCE("OTHER_FINANCE", "๊ธฐํƒ€", JobCategory.FINANCE), + + DATA_SCIENTIST("DATA_SCIENTIST", "๋ฐ์ดํ„ฐ ์‚ฌ์ด์–ธํ‹ฐ์ŠคํŠธ", JobCategory.DATA), + DATA_ENGINEER("DATA_ENGINEER", "๋ฐ์ดํ„ฐ ์—”์ง€๋‹ˆ์–ด", JobCategory.DATA), + DATA_ANALYST("DATA_ANALYST", "๋ฐ์ดํ„ฐ ์• ๋„๋ฆฌ์ŠคํŠธ", JobCategory.DATA), + BI_ENGINEER("BI_ENGINEER", "BI ์—”์ง€๋‹ˆ์–ด", JobCategory.DATA), + MACHINE_LEARNING_ENGINEER("MACHINE_LEARNING_ENGINEER", "๋จธ์‹ ๋Ÿฌ๋‹ ์—”์ง€๋‹ˆ์–ด", JobCategory.DATA), + DATA_ARCHITECT("DATA_ARCHITECT", "๋ฐ์ดํ„ฐ ์•„ํ‚คํ…ํŠธ", JobCategory.DATA), + RESEARCH_ANALYST("RESEARCH_ANALYST", "๋ฆฌ์„œ์น˜ ์• ๋„๋ฆฌ์ŠคํŠธ", JobCategory.DATA), + OTHER_DATA("OTHER_DATA", "๊ธฐํƒ€", JobCategory.DATA), + + CLINICAL_DOCTOR("CLINICAL_DOCTOR", "์ž„์ƒ์˜์‚ฌ", JobCategory.MEDICAL), + CLINICAL_RESEARCHER("CLINICAL_RESEARCHER", "์ž„์ƒ์—ฐ๊ตฌ์›", JobCategory.MEDICAL), + MEDICAL_DEVICE_RND("MEDICAL_DEVICE_RND", "์˜๋ฃŒ๊ธฐ๊ธฐ ์—ฐ๊ตฌ๊ฐœ๋ฐœ", JobCategory.MEDICAL), + PHARMACEUTICAL_RESEARCHER("PHARMACEUTICAL_RESEARCHER", "์ œ์•ฝํšŒ์‚ฌ ์—ฐ๊ตฌ์›", JobCategory.MEDICAL), + BIO_RESEARCHER("BIO_RESEARCHER", "๋ฐ”์ด์˜ค ์—ฐ๊ตฌ์›", JobCategory.MEDICAL), + OTHER_MEDICAL("OTHER_MEDICAL", "๊ธฐํƒ€", JobCategory.MEDICAL), + + LAWYER("LAWYER", "๋ณ€ํ˜ธ์‚ฌ", JobCategory.LEGAL), + LEGAL_COUNSEL("LEGAL_COUNSEL", "๋ฒ•๋ฌด๋‹ด๋‹น", JobCategory.LEGAL), + PATENT("PATENT", "ํŠนํ—ˆ๋‹ด๋‹น", JobCategory.LEGAL), + COMPLIANCE("COMPLIANCE", "์ค€๋ฒ•๊ฐ์‹œ์ธ(์ปดํ”Œ๋ผ์ด์–ธ์Šค)", JobCategory.LEGAL), + LAW_FIRM_STAFF("LAW_FIRM_STAFF", "๋ฒ•๋ฌด๋ฒ•์ธ ์‚ฌ๋ฌด์ง", JobCategory.LEGAL), + LEGAL_ADVISOR("LEGAL_ADVISOR", "๋ฒ•๋ฅ ์ž๋ฌธ", JobCategory.LEGAL), + PATENT_ENGINEER("PATENT_ENGINEER", "ํŠนํ—ˆ์—”์ง€๋‹ˆ์–ด", JobCategory.LEGAL), + OTHER_LEGAL("OTHER_LEGAL", "๊ธฐํƒ€", JobCategory.LEGAL); + private final String code; + private final String kor; private final JobCategory category; - DetailedJob(JobCategory category) { + DetailedJob(String code, String kor, JobCategory category) { + this.code = code; + this.kor = kor; this.category = category; } - public JobCategory getCategory() { - return category; + public static DetailedJob from(String kor) { + return Arrays.stream(values()) + .filter(e -> e.kor.equals(kor)) + .findFirst() + .orElseThrow( + () -> new IllegalArgumentException("๋งคํ•‘๋˜๋Š” DetailedJob๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค: " + kor) + ); } } \ No newline at end of file diff --git a/src/main/java/es/princip/ringus/domain/mentor/vo/JobCategory.java b/src/main/java/es/princip/ringus/domain/mentor/vo/JobCategory.java index 3ead55a..2ff9f51 100644 --- a/src/main/java/es/princip/ringus/domain/mentor/vo/JobCategory.java +++ b/src/main/java/es/princip/ringus/domain/mentor/vo/JobCategory.java @@ -1,14 +1,37 @@ package es.princip.ringus.domain.mentor.vo; + +import lombok.Getter; + +import java.util.Arrays; + +@Getter public enum JobCategory { - MARKETING, // ๋งˆ์ผ€ํŒ…/ ๊ด‘๊ณ ํ™๋ณด/ ๋ฏธ๋””์–ด - SERVICE_PLANNING, // ์„œ๋น„์Šค ๊ธฐํš/ ์‚ฌ์—…/ ์šด์˜ - DESIGN, // ๋””์ž์ธ - DEVELOPMENT, // ๊ฐœ๋ฐœ - GRADUATE_SCHOOL, // ๋Œ€ํ•™์› - HR_SUPPORT, // ์ธ์‚ฌ/ ์ฑ„์šฉ/ ๊ฒฝ์˜์ง€์› - SALES_CUSTOMER, // ์˜์—…/ ๊ณ ๊ฐ - FINANCE_CONSULTING_VC, // ๊ธˆ์œต/ ์ปจ์„คํŒ…/ VC/ ์žฌ๋ฌด - DATA, // ๋ฐ์ดํ„ฐ - MEDICAL, // ์˜๋ฃŒ - LEGAL // ๋ฒ•๋ฅ  + MARKETING("MARKETING", "๋งˆ์ผ€ํŒ…"), + SERVICE_PLANNING("SERVICE_PLANNING", "์„œ๋น„์Šค ๊ธฐํš"), + DESIGN("DESIGN", "๋””์ž์ธ"), + DEVELOPMENT("DEVELOPMENT", "๊ฐœ๋ฐœ"), + GRADUATE_SCHOOL("GRADUATE_SCHOOL", "๋Œ€ํ•™์›"), + HR_SUPPORT("HR_SUPPORT", "์ธ์‚ฌ"), + SALES_CUSTOMER("SALES_CUSTOMER", "์˜์—…"), + FINANCE("FINANCE", "๊ธˆ์œต"), + DATA("DATA", "๋ฐ์ดํ„ฐ"), + MEDICAL("MEDICAL", "์˜๋ฃŒ"), + LEGAL("LEGAL", "๋ฒ•๋ฅ "); + + private final String code; + private final String kor; + + JobCategory(String code, String kor) { + this.code = code; + this.kor = kor; + } + + public static JobCategory from(String kor) { + return Arrays.stream(values()) + .filter(e -> e.kor.equals(kor)) + .findFirst() + .orElseThrow( + () -> new IllegalArgumentException("๋งคํ•‘๋˜๋Š” JobCategory๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค: " + kor) + ); + } } \ No newline at end of file diff --git a/src/main/java/es/princip/ringus/domain/mentor/vo/MentoringField.java b/src/main/java/es/princip/ringus/domain/mentor/vo/MentoringField.java index f4d5bd7..318d81f 100644 --- a/src/main/java/es/princip/ringus/domain/mentor/vo/MentoringField.java +++ b/src/main/java/es/princip/ringus/domain/mentor/vo/MentoringField.java @@ -1,8 +1,30 @@ package es.princip.ringus.domain.mentor.vo; +import lombok.Getter; + +import java.util.Arrays; + +@Getter public enum MentoringField { - JOB_PREPARATION, - INTERVIEW_PREPARATION, - PRACTICAL_SKILLS, - PORTFOLIO + JOB_PREPARATION("JOB_PREPARATION", "์ทจ์—… ์ค€๋น„"), + INTERVIEW_PREPARATION("INTERVIEW_PREPARATION", "๋ฉด์ ‘ ๋Œ€๋น„"), + INDUSTRY_TRENDS("INDUSTRY_TRENDS", "์—…๊ณ„ ๋™ํ–ฅ"), + CAREER_PATH("CAREER_PATH", "์ปค๋ฆฌ์–ด ๊ณ ๋ฏผ"); + + private final String code; + private final String kor; + + MentoringField(String code, String kor) { + this.code = code; + this.kor = kor; + } + + public static MentoringField from(String kor) { + return Arrays.stream(values()) + .filter(e -> e.kor.equals(kor)) + .findFirst() + .orElseThrow( + () -> new IllegalArgumentException("๋งคํ•‘๋˜๋Š” MentoringField๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค: " + kor) + ); + } } \ No newline at end of file diff --git a/src/main/java/es/princip/ringus/domain/mentor/vo/Portfolio.java b/src/main/java/es/princip/ringus/domain/mentor/vo/Portfolio.java index 991db01..20b8013 100644 --- a/src/main/java/es/princip/ringus/domain/mentor/vo/Portfolio.java +++ b/src/main/java/es/princip/ringus/domain/mentor/vo/Portfolio.java @@ -1,16 +1,16 @@ package es.princip.ringus.domain.mentor.vo; +import es.princip.ringus.infra.storage.domain.File; import jakarta.persistence.Embeddable; -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; +import lombok.*; @Getter @Embeddable -@AllArgsConstructor @NoArgsConstructor(access = AccessLevel.PROTECTED) -public class Portfolio { - private String url; - private String description; +public class Portfolio extends File { + + @Builder + public Portfolio(String fileName, String filePath, Integer fileSize) { + super(fileName, filePath, fileSize); + } } diff --git a/src/main/java/es/princip/ringus/domain/mentor/vo/Timezone.java b/src/main/java/es/princip/ringus/domain/mentor/vo/Timezone.java index 5770f18..f09d56d 100644 --- a/src/main/java/es/princip/ringus/domain/mentor/vo/Timezone.java +++ b/src/main/java/es/princip/ringus/domain/mentor/vo/Timezone.java @@ -1,12 +1,14 @@ package es.princip.ringus.domain.mentor.vo; import jakarta.persistence.Embeddable; +import jakarta.persistence.Transient; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; import java.time.LocalTime; -import java.util.Set; +import java.util.Arrays; +import java.util.List; import java.util.stream.Collectors; @Getter @@ -27,22 +29,22 @@ private void validateTime(LocalTime time) { } public Timezone( - final Set days, + final List days, final LocalTime startTime, final LocalTime endTime ) { this.days = days.stream() - .map(Days::name) - .collect(Collectors.joining(", ")); + .map(Days::fromCode).collect(Collectors.joining(",")); validateTime(startTime); this.startTime = startTime; validateTime(endTime); this.endTime = endTime; } - public Set getDays() { - return Set.of(days.split(", ")).stream() - .map(String::valueOf) - .collect(Collectors.toSet()); + @Transient + public List getDaysKor() { + return Arrays.stream(days.split(",")) + .map(Days::toKor) + .toList(); } } \ No newline at end of file diff --git a/src/main/java/es/princip/ringus/domain/mentoring/Mentoring.java b/src/main/java/es/princip/ringus/domain/mentoring/Mentoring.java index 03371a6..a845de3 100644 --- a/src/main/java/es/princip/ringus/domain/mentoring/Mentoring.java +++ b/src/main/java/es/princip/ringus/domain/mentoring/Mentoring.java @@ -97,4 +97,18 @@ public boolean isNotWaiting() { public void accept() { this.mentoringStatus = MentoringStatus.ACCEPTED; } + + public void cancel() { + if (mentoringStatus == MentoringStatus.WAITING) { + this.mentoringStatus = MentoringStatus.CANCELLED_BEFORE_PAYMENT; + } else if (mentoringStatus == MentoringStatus.ACCEPTED) { + this.mentoringStatus = MentoringStatus.CANCELLED_AFTER_PAYMENT; + } else { + throw new CustomRuntimeException(MentoringErrorCode.MENTORING_CANCEL_NOT_POSSIBLE); + } + } + + public void reject() { + this.mentoringStatus = MentoringStatus.REJECTED; + } } diff --git a/src/main/java/es/princip/ringus/domain/mentoring/MentoringQueryDslRepository.java b/src/main/java/es/princip/ringus/domain/mentoring/MentoringQueryDslRepository.java new file mode 100644 index 0000000..833e0bd --- /dev/null +++ b/src/main/java/es/princip/ringus/domain/mentoring/MentoringQueryDslRepository.java @@ -0,0 +1,5 @@ +package es.princip.ringus.domain.mentoring; + +public interface MentoringQueryDslRepository { + Long findMentoringCountBy(Long mentorId); +} diff --git a/src/main/java/es/princip/ringus/domain/mentoring/MentoringQueryDslRepositoryImpl.java b/src/main/java/es/princip/ringus/domain/mentoring/MentoringQueryDslRepositoryImpl.java new file mode 100644 index 0000000..45d33b7 --- /dev/null +++ b/src/main/java/es/princip/ringus/domain/mentoring/MentoringQueryDslRepositoryImpl.java @@ -0,0 +1,20 @@ +package es.princip.ringus.domain.mentoring; + +import es.princip.ringus.domain.support.QueryDslSupport; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +import static es.princip.ringus.domain.mentoring.QMentoring.mentoring; + +@Repository +@RequiredArgsConstructor +public class MentoringQueryDslRepositoryImpl extends QueryDslSupport implements MentoringQueryDslRepository { + + @Override + public Long findMentoringCountBy(Long mentorId) { + return queryFactory.select() + .from(mentoring) + .where(mentoring.mentor.id.eq(mentorId).and(mentoring.mentoringStatus.eq(MentoringStatus.COMPLETED))) + .fetchCount(); + } +} diff --git a/src/main/java/es/princip/ringus/domain/mentoring/MentoringRepository.java b/src/main/java/es/princip/ringus/domain/mentoring/MentoringRepository.java index aef0d76..732af2c 100644 --- a/src/main/java/es/princip/ringus/domain/mentoring/MentoringRepository.java +++ b/src/main/java/es/princip/ringus/domain/mentoring/MentoringRepository.java @@ -7,6 +7,6 @@ import java.util.Optional; @Repository -public interface MentoringRepository extends JpaRepository { +public interface MentoringRepository extends JpaRepository, MentoringQueryDslRepository { Optional findByMenteeIdAndMentorId(Long menteeId, Long mentorId); } diff --git a/src/main/java/es/princip/ringus/domain/mentoring/MentoringTopic.java b/src/main/java/es/princip/ringus/domain/mentoring/MentoringTopic.java index c0cfee0..66e1e62 100644 --- a/src/main/java/es/princip/ringus/domain/mentoring/MentoringTopic.java +++ b/src/main/java/es/princip/ringus/domain/mentoring/MentoringTopic.java @@ -3,22 +3,30 @@ import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; +import java.util.Arrays; + @Getter @Schema(description = "๋ฉ˜ํ† ๋ง ์ฃผ์ œ") public enum MentoringTopic { - @Schema(description = "ํ•™์—… ๊ด€๋ จ, STUDY") - STUDY("ํ•™์—… ๊ด€๋ จ"), - @Schema(description = "์—…๊ณ„ ๋™ํ–ฅ, INDUSTRY_TRENDS") - INDUSTRY_TRENDS("์—…๊ณ„ ๋™ํ–ฅ"), - @Schema(description = "๋ฉด์ ‘๋Œ€๋น„, INTERVIEW") - INTERVIEW("๋ฉด์ ‘ ๋Œ€๋น„"), - @Schema(description = "์ทจ์—… ์ค€๋น„, JOB_PREP") - JOB_PREP("์ทจ์—… ์ค€๋น„"); + JOB_PREPARATION("JOB_PREPARATION", "์ทจ์—… ์ค€๋น„"), + INTERVIEW_PREPARATION("INTERVIEW_PREPARATION", "๋ฉด์ ‘ ๋Œ€๋น„"), + INDUSTRY_TRENDS("INDUSTRY_TRENDS", "์—…๊ณ„ ๋™ํ–ฅ"), + CAREER_PATH("CAREER_PATH", "์ปค๋ฆฌ์–ด ๊ณ ๋ฏผ"); - private final String description; + private final String code; + private final String kor; - MentoringTopic(String description) { - this.description = description; + MentoringTopic(String code, String kor) { + this.code = code; + this.kor = kor; } -} + public static MentoringTopic from(String kor) { + return Arrays.stream(values()) + .filter(e -> e.kor.equals(kor)) + .findFirst() + .orElseThrow( + () -> new IllegalArgumentException("๋งคํ•‘๋˜๋Š” MentoringTopic์ด ์—†์Šต๋‹ˆ๋‹ค: " + kor) + ); + } +} \ No newline at end of file diff --git a/src/main/java/es/princip/ringus/domain/notification/Notification.java b/src/main/java/es/princip/ringus/domain/notification/Notification.java new file mode 100644 index 0000000..8b35ca6 --- /dev/null +++ b/src/main/java/es/princip/ringus/domain/notification/Notification.java @@ -0,0 +1,55 @@ +package es.princip.ringus.domain.notification; + +import es.princip.ringus.domain.base.BaseTimeEntity; +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Entity +@Table(name = "notification") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Notification extends BaseTimeEntity { + + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "notification_id") + private Long id; + + @Column(name = "title", nullable = false, length = 255) + private String title; + + @Column(name = "content", nullable = false, length = 500) + private String content; + + @Enumerated(EnumType.STRING) + @Column(name = "type", nullable = false) + private NotificationType type; + + @Column(name = "is_read", nullable = false) + private boolean isRead = false; + + @Column(name = "sender_id", nullable = false) + private Long senderId; + + @Column(name = "receiver_id", nullable = false) + private Long receiverId; + + @Builder + private Notification( + String title, + String content, + NotificationType type, + Long senderId, + Long receiverId + ) { + this.title = title; + this.content = content; + this.type = type; + this.senderId = senderId; + this.receiverId = receiverId; + } + + public void markAsRead() { this.isRead = true; } +} \ No newline at end of file diff --git a/src/main/java/es/princip/ringus/domain/notification/NotificationRepository.java b/src/main/java/es/princip/ringus/domain/notification/NotificationRepository.java new file mode 100644 index 0000000..ec6823f --- /dev/null +++ b/src/main/java/es/princip/ringus/domain/notification/NotificationRepository.java @@ -0,0 +1,6 @@ +package es.princip.ringus.domain.notification; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface NotificationRepository extends JpaRepository { +} diff --git a/src/main/java/es/princip/ringus/domain/notification/NotificationType.java b/src/main/java/es/princip/ringus/domain/notification/NotificationType.java new file mode 100644 index 0000000..5d50513 --- /dev/null +++ b/src/main/java/es/princip/ringus/domain/notification/NotificationType.java @@ -0,0 +1,6 @@ +package es.princip.ringus.domain.notification; + +public enum NotificationType { + MENTORING_REQUEST, + MENTORING_APPROVED +} diff --git a/src/main/java/es/princip/ringus/global/aop/TimeTraceAop.java b/src/main/java/es/princip/ringus/global/aop/TimeTraceAop.java new file mode 100644 index 0000000..4ac5736 --- /dev/null +++ b/src/main/java/es/princip/ringus/global/aop/TimeTraceAop.java @@ -0,0 +1,25 @@ +package es.princip.ringus.global.aop; + +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.springframework.stereotype.Component; + +@Aspect +@Slf4j +@Component +public class TimeTraceAop { + + @Around("execution(* es.princip.ringus..*(..))") + public Object execute(ProceedingJoinPoint joinPoint) throws Throwable { + long start = System.currentTimeMillis(); + try { + return joinPoint.proceed(); + } finally { + long finish = System.currentTimeMillis(); + long timeMs = finish - start; + log.info("END:{} {}ms",joinPoint.toString(), timeMs); + } + } +} diff --git a/src/main/java/es/princip/ringus/global/exception/GlobalExceptionHandler.java b/src/main/java/es/princip/ringus/global/exception/GlobalExceptionHandler.java index 1281d01..7cd91f0 100644 --- a/src/main/java/es/princip/ringus/global/exception/GlobalExceptionHandler.java +++ b/src/main/java/es/princip/ringus/global/exception/GlobalExceptionHandler.java @@ -95,6 +95,7 @@ public ResponseEntity> handleCustomRuntimeException(CustomRu Map response = new LinkedHashMap<>(); response.put("status", ex.getStatus().value()); response.put("message", ex.getMessage()); + response.put("code", ex.getCode()); return ResponseEntity.status(ex.getStatus()).body(response); } diff --git a/src/main/java/es/princip/ringus/global/factory/NotificationMessageFactory.java b/src/main/java/es/princip/ringus/global/factory/NotificationMessageFactory.java new file mode 100644 index 0000000..b7a2699 --- /dev/null +++ b/src/main/java/es/princip/ringus/global/factory/NotificationMessageFactory.java @@ -0,0 +1,28 @@ +package es.princip.ringus.global.factory; + +import es.princip.ringus.domain.notification.Notification; +import es.princip.ringus.domain.notification.NotificationType; +import es.princip.ringus.global.sender.dto.MentoringRequestMessage; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class NotificationMessageFactory { + + public Notification mentoringRequestMessage(MentoringRequestMessage request) { + String title = request.menteeName() + " ๋ฉ˜ํ‹ฐ๋‹˜๊ป˜์„œ " + request.mentorName() + " ๋ฉ˜ํ† ๋‹˜๊ป˜ ๋ฉ˜ํ† ๋ง์„ ์‹ ์ฒญํ–ˆ์Šต๋‹ˆ๋‹ค."; + String content = "[๋ง์–ด์Šค ๋ฉ˜ํ† ๋ง ์‹ ์ฒญ ์•Œ๋ฆผ]\n" + + "๋ฉ˜ํ† ๋ง ์ฃผ์ œ" + request.mentoringTopic().name() + "\n" + + "์‹ ์ฒญ ์‹œ๊ฐ„: " + request.applyTimes().toString() + "\n" + + "๋ฉ˜ํ† ๋ง ์‹ ์ฒญ ๋ฉ”์‹œ์ง€: " + request.mentoringMessage() + "\n"+ + "\n\n"; + return Notification.builder() + .title(title) + .content(content) + .type(NotificationType.MENTORING_REQUEST) + .senderId(request.senderId()) + .receiverId(request.receiverId()) + .build(); + } +} \ No newline at end of file diff --git a/src/main/java/es/princip/ringus/global/sender/EmitterRepository.java b/src/main/java/es/princip/ringus/global/sender/EmitterRepository.java new file mode 100644 index 0000000..3b2cace --- /dev/null +++ b/src/main/java/es/princip/ringus/global/sender/EmitterRepository.java @@ -0,0 +1,40 @@ +package es.princip.ringus.global.sender; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; + +@Slf4j +@Component +@RequiredArgsConstructor +public class EmitterRepository { + private final Map emitters = new ConcurrentHashMap<>(); + @Value("${app.notification.emitter.timeout}") + private Long TIMEOUT; + + public SseEmitter save(Long receiverId) { + log.info("Sending message to emitter {}", receiverId); + log.info("Emitter timeout set to {} ms", TIMEOUT); + + SseEmitter emitter = new SseEmitter(TIMEOUT); + emitters.put(receiverId, emitter); + + emitter.onCompletion(() -> emitters.remove(receiverId)); + emitter.onTimeout(() -> emitters.remove(receiverId)); + return emitter; + } + + public Optional get(Long receiverId) { + return Optional.ofNullable(emitters.get(receiverId)); + } + + public void remove(Long receiverId) { + emitters.remove(receiverId); + } +} \ No newline at end of file diff --git a/src/main/java/es/princip/ringus/global/sender/NotificationChannel.java b/src/main/java/es/princip/ringus/global/sender/NotificationChannel.java new file mode 100644 index 0000000..7a019b9 --- /dev/null +++ b/src/main/java/es/princip/ringus/global/sender/NotificationChannel.java @@ -0,0 +1,8 @@ +package es.princip.ringus.global.sender; + +public enum NotificationChannel { + SSE, + EMAIL, + KAKAO, + SMS +} \ No newline at end of file diff --git a/src/main/java/es/princip/ringus/global/sender/NotificationSender.java b/src/main/java/es/princip/ringus/global/sender/NotificationSender.java new file mode 100644 index 0000000..6b2c00c --- /dev/null +++ b/src/main/java/es/princip/ringus/global/sender/NotificationSender.java @@ -0,0 +1,8 @@ +package es.princip.ringus.global.sender; + +import es.princip.ringus.domain.notification.Notification; + +public interface NotificationSender { + void send(Notification notification); + NotificationChannel getChannelType(); +} \ No newline at end of file diff --git a/src/main/java/es/princip/ringus/global/sender/SseNotificationSender.java b/src/main/java/es/princip/ringus/global/sender/SseNotificationSender.java new file mode 100644 index 0000000..8545f38 --- /dev/null +++ b/src/main/java/es/princip/ringus/global/sender/SseNotificationSender.java @@ -0,0 +1,36 @@ +package es.princip.ringus.global.sender; + +import es.princip.ringus.domain.notification.Notification; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import java.io.IOException; + +@Component +@RequiredArgsConstructor +public class SseNotificationSender implements NotificationSender { + + private final EmitterRepository emitterRepository; + + @Override + public NotificationChannel getChannelType() { + return NotificationChannel.SSE; + } + + @Override + public void send(Notification notification) { + emitterRepository.get(notification.getReceiverId()).ifPresent(emitter -> { + try { + emitter.send( + SseEmitter.event() + .name("notification") + .data(notification) // ์ง๋ ฌํ™” ๊ทœ์น™์€ Jackson ๊ธฐ๋ณธ + ); + } catch (IOException ex) { + emitter.completeWithError(ex); + emitterRepository.remove(notification.getReceiverId()); + } + }); + } +} \ No newline at end of file diff --git a/src/main/java/es/princip/ringus/global/sender/dto/MentoringRequestMessage.java b/src/main/java/es/princip/ringus/global/sender/dto/MentoringRequestMessage.java new file mode 100644 index 0000000..92ac204 --- /dev/null +++ b/src/main/java/es/princip/ringus/global/sender/dto/MentoringRequestMessage.java @@ -0,0 +1,35 @@ +package es.princip.ringus.global.sender.dto; + +import es.princip.ringus.domain.mentee.Mentee; +import es.princip.ringus.domain.mentor.Mentor; +import es.princip.ringus.domain.mentoring.Mentoring; +import es.princip.ringus.domain.mentoring.MentoringTime; +import es.princip.ringus.domain.mentoring.MentoringTopic; + +import java.util.List; + +public record MentoringRequestMessage( + Long receiverId, + Long senderId, + String menteeName, + String mentorName, + String mentoringMessage, + MentoringTopic mentoringTopic, + List applyTimes +) { + public static MentoringRequestMessage from( + final Mentee mentee, + final Mentor mentor, + final Mentoring mentoring + ) { + return new MentoringRequestMessage( + mentor.getMemberId(), + mentee.getMemberId(), + mentee.getNickname(), + mentor.getNickname(), + mentoring.getMentoringMessage(), + mentoring.getMentoringTopic(), + mentoring.getApplyTimes() + ); + } +} diff --git a/src/main/java/es/princip/ringus/global/util/PasswordVaildator.java b/src/main/java/es/princip/ringus/global/util/PasswordVaildator.java new file mode 100644 index 0000000..cee393c --- /dev/null +++ b/src/main/java/es/princip/ringus/global/util/PasswordVaildator.java @@ -0,0 +1,15 @@ +package es.princip.ringus.global.util; + +import es.princip.ringus.domain.exception.MemberErrorCode; +import es.princip.ringus.global.exception.CustomRuntimeException; +import java.util.regex.Pattern; + +public class PasswordVaildator { + private static final String PASSWORD_PATTERN = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[!@#$%^&*_+=/])[A-Za-z\\d!@#$%^&*_+=/]{8,20}$"; + + public static void validate(String password) { + if (!Pattern.matches(PASSWORD_PATTERN, password)) { + throw new CustomRuntimeException(MemberErrorCode.INVAILD_PASSWORD); + } + } +} \ No newline at end of file diff --git a/src/main/java/es/princip/ringus/global/util/SessionUtil.java b/src/main/java/es/princip/ringus/global/util/SessionUtil.java new file mode 100644 index 0000000..568ec9d --- /dev/null +++ b/src/main/java/es/princip/ringus/global/util/SessionUtil.java @@ -0,0 +1,59 @@ +package es.princip.ringus.global.util; + +import es.princip.ringus.domain.exception.MemberErrorCode; +import es.princip.ringus.domain.member.Member; +import es.princip.ringus.domain.member.MemberRepository; +import es.princip.ringus.global.exception.CustomRuntimeException; +import jakarta.servlet.http.HttpSession; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class SessionUtil { + + private final MemberRepository memberRepository; + + public boolean isLoginMentorUser( + HttpSession session, + Long memberId + ){ + if (!isLoginUser(session, memberId)) { + return false; + } + + Member member = memberRepository.findById(memberId) + .orElseThrow(() -> new CustomRuntimeException(MemberErrorCode.MEMBER_NOT_FOUND)); + + return member.isMentor(); + } + + public boolean isLoginMenteeUser( + HttpSession session, + Long memberId + ){ + if (!isLoginUser(session, memberId)) { + return false; + } + + Member member = memberRepository.findById(memberId) + .orElseThrow(() -> new CustomRuntimeException(MemberErrorCode.MEMBER_NOT_FOUND)); + + return member.isMentee(); + } + + public boolean isLoginUser( + HttpSession session, + Long memberId + ){ + return session != null && session.getAttribute("memberId") != null; + } + + public boolean isMentorRole(String role) { + return "MENTOR".equals(role); + } + + public boolean isMenteeRole(String role) { + return "MENTEE".equals(role); + } +} \ No newline at end of file diff --git a/src/main/java/es/princip/ringus/infra/config/RedisConfig.java b/src/main/java/es/princip/ringus/infra/config/RedisConfig.java index fffdd27..7b3c021 100644 --- a/src/main/java/es/princip/ringus/infra/config/RedisConfig.java +++ b/src/main/java/es/princip/ringus/infra/config/RedisConfig.java @@ -5,6 +5,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.listener.RedisMessageListenerContainer; import org.springframework.data.redis.repository.configuration.EnableRedisRepositories; import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; @@ -30,4 +31,11 @@ public RedisTemplate redisTemplate(RedisConnectionFactory connec redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer()); return redisTemplate; } + + @Bean + public RedisMessageListenerContainer redisMessageListenerContainer(RedisConnectionFactory connectionFactory) { + RedisMessageListenerContainer container = new RedisMessageListenerContainer(); + container.setConnectionFactory(connectionFactory); + return container; + } } \ No newline at end of file diff --git a/src/main/java/es/princip/ringus/infra/storage/api/CertificateController.java b/src/main/java/es/princip/ringus/infra/storage/api/CertificateController.java index 4697521..d5f1826 100644 --- a/src/main/java/es/princip/ringus/infra/storage/api/CertificateController.java +++ b/src/main/java/es/princip/ringus/infra/storage/api/CertificateController.java @@ -5,12 +5,16 @@ import es.princip.ringus.global.util.ApiResponseWrapper; import es.princip.ringus.infra.storage.application.StorageCertificateService; import es.princip.ringus.infra.storage.dto.CertificateUploadRequest; +import es.princip.ringus.infra.storage.dto.FilePreviewResponse; import jakarta.servlet.http.HttpSession; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; +import java.util.List; + @RestController @RequestMapping("/storage/certificate") @RequiredArgsConstructor @@ -52,4 +56,6 @@ public ResponseEntity> uploadMentorCertificate( return ResponseEntity.ok(ApiResponseWrapper.success(HttpStatus.OK, filePath)); } + + } diff --git a/src/main/java/es/princip/ringus/infra/storage/api/FileController.java b/src/main/java/es/princip/ringus/infra/storage/api/FileController.java new file mode 100644 index 0000000..2cf48b3 --- /dev/null +++ b/src/main/java/es/princip/ringus/infra/storage/api/FileController.java @@ -0,0 +1,42 @@ +package es.princip.ringus.infra.storage.api; + +import es.princip.ringus.global.util.ApiResponseWrapper; +import es.princip.ringus.infra.storage.application.StorageFileService; +import es.princip.ringus.infra.storage.dto.FilePreviewResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +@RequestMapping("/storage/files") +@RequiredArgsConstructor +public class FileController implements FileControllerDocs{ + + private final StorageFileService storageFileService; + /** + * ๊ด€๋ฆฌ์ž ์ „์šฉ - ์ฆ๋ช…์„œ presigned URL ๋ฐœ๊ธ‰ + */ + @PreAuthorize("hasRole('ADMIN')") + @GetMapping("/{fileMemberId}") + public ResponseEntity> getFile( + @PathVariable Long fileMemberId + ) { + FilePreviewResponse filePreviewResponse = storageFileService.getFile(fileMemberId); + return ResponseEntity.ok(ApiResponseWrapper.success(HttpStatus.OK, "success",filePreviewResponse)); + } + + @PreAuthorize("hasRole('ADMIN')") + @GetMapping + public ResponseEntity>> getFiles( + ) { + List filePreviewResponses = storageFileService.getFiles(); + return ResponseEntity.ok(ApiResponseWrapper.success(HttpStatus.OK, "success",filePreviewResponses)); + } +} diff --git a/src/main/java/es/princip/ringus/infra/storage/api/FileControllerDocs.java b/src/main/java/es/princip/ringus/infra/storage/api/FileControllerDocs.java new file mode 100644 index 0000000..9f54cd9 --- /dev/null +++ b/src/main/java/es/princip/ringus/infra/storage/api/FileControllerDocs.java @@ -0,0 +1,47 @@ +package es.princip.ringus.infra.storage.api; + +import es.princip.ringus.global.util.ApiResponseWrapper; +import es.princip.ringus.infra.storage.dto.FilePreviewResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; + +import java.util.List; + +@Tag(name = "ํŒŒ์ผ ๊ด€๋ฆฌ API", description = "ํŒŒ์ผ ๊ด€๋ฆฌ API") +@RequestMapping("/storage/files") +public interface FileControllerDocs { + + /** + * ๊ด€๋ฆฌ์ž ์ „์šฉ - ๋‹จ์ผ ํŒŒ์ผ ์กฐํšŒ + */ + @Operation(summary = "๋‹จ์ผ ํŒŒ์ผ ์กฐํšŒ", + description = "๊ด€๋ฆฌ์ž๊ฐ€ ํŠน์ • ํŒŒ์ผ์˜ **Presigned URL**์„ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค. ํ•ด๋‹น ํŒŒ์ผ์€ **S3์—์„œ Presigned URL์„ ํ†ตํ•ด ๋‹ค์šด๋กœ๋“œ** ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.", + parameters = { + @Parameter(name = "fileMemberId", description = "์กฐํšŒํ•  ํŒŒ์ผ์˜ ๊ณ ์œ  ID", required = true) + }) + @ApiResponse(responseCode = "200", description = "ํŒŒ์ผ ์กฐํšŒ ์„ฑ๊ณต") + @ApiResponse(responseCode = "401", description = "๊ถŒํ•œ ์—†์Œ (๊ด€๋ฆฌ์ž๋งŒ ์ ‘๊ทผ ๊ฐ€๋Šฅ)") + @ApiResponse(responseCode = "404", description = "ํŒŒ์ผ์„ ์ฐพ์„ ์ˆ˜ ์—†์Œ") + @GetMapping("/{fileMemberId}") + ResponseEntity> getFile( + @Parameter(description = "ํŒŒ์ผ ๋ฉค๋ฒ„ ID", required = true) + @PathVariable Long fileMemberId + ); + + /** + * ๊ด€๋ฆฌ์ž ์ „์šฉ - ํŒŒ์ผ ๋ชฉ๋ก ์กฐํšŒ + */ + @Operation(summary = "ํŒŒ์ผ ๋ชฉ๋ก ์กฐํšŒ", + description = "๊ด€๋ฆฌ์ž๊ฐ€ **ํŒŒ์ผ ๋ชฉ๋ก**์„ ์กฐํšŒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐ ํŒŒ์ผ์˜ **Presigned URL**์ด ํฌํ•จ๋œ ๋ชฉ๋ก์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.") + @ApiResponse(responseCode = "200", description = "ํŒŒ์ผ ๋ชฉ๋ก ์กฐํšŒ ์„ฑ๊ณต") + @ApiResponse(responseCode = "401", description = "๊ถŒํ•œ ์—†์Œ (๊ด€๋ฆฌ์ž๋งŒ ์ ‘๊ทผ ๊ฐ€๋Šฅ)") + @ApiResponse(responseCode = "404", description = "ํŒŒ์ผ์ด ์กด์žฌํ•˜์ง€ ์•Š์Œ") + @GetMapping + ResponseEntity>> getFiles(); +} diff --git a/src/main/java/es/princip/ringus/infra/storage/api/PortfolioController.java b/src/main/java/es/princip/ringus/infra/storage/api/PortfolioController.java new file mode 100644 index 0000000..c10320d --- /dev/null +++ b/src/main/java/es/princip/ringus/infra/storage/api/PortfolioController.java @@ -0,0 +1,34 @@ +package es.princip.ringus.infra.storage.api; + +import es.princip.ringus.domain.exception.MemberErrorCode; +import es.princip.ringus.global.exception.CustomRuntimeException; +import es.princip.ringus.global.util.ApiResponseWrapper; +import es.princip.ringus.infra.storage.application.StoragePortfolioService; +import es.princip.ringus.infra.storage.dto.PortfolioUploadRequest; +import jakarta.servlet.http.HttpSession; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/portfolio") +@RequiredArgsConstructor +public class PortfolioController implements PortfolioControllerDocs{ + + private final StoragePortfolioService storagePortfolioService; + + @PostMapping + public ResponseEntity> uploadPortfolio( + @ModelAttribute PortfolioUploadRequest request, + HttpSession session) { + + Long memberId = (Long) session.getAttribute("memberId"); + if(memberId == null){ + throw new CustomRuntimeException(MemberErrorCode.SESSION_EXPIRED); + } + String filePath = storagePortfolioService.uploadMentorPortfolio(request, memberId); + + return ResponseEntity.ok(ApiResponseWrapper.success(HttpStatus.OK, filePath)); + } +} diff --git a/src/main/java/es/princip/ringus/infra/storage/api/PortfolioControllerDocs.java b/src/main/java/es/princip/ringus/infra/storage/api/PortfolioControllerDocs.java new file mode 100644 index 0000000..8bc4274 --- /dev/null +++ b/src/main/java/es/princip/ringus/infra/storage/api/PortfolioControllerDocs.java @@ -0,0 +1,41 @@ +package es.princip.ringus.infra.storage.api; + +import es.princip.ringus.global.annotation.SessionMemberId; +import es.princip.ringus.global.util.ApiResponseWrapper; +import es.princip.ringus.infra.storage.dto.PortfolioUploadRequest; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.parameters.RequestBody; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpSession; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +@Tag(name = "ํฌํŠธํด๋ฆฌ์˜ค ์—…๋กœ๋“œ API", description = "๋ฉ˜ํ†  ํฌํŠธํด๋ฆฌ์˜ค ํŒŒ์ผ ์—…๋กœ๋“œ") +@RequestMapping("/portfolio") +public interface PortfolioControllerDocs { + @Operation(summary = "๋ฉ˜ํ†  ํฌํŠธํด๋ฆฌ์˜ค ์—…๋กœ๋“œ", + description = "๋ฉ˜ํ† ๊ฐ€ ํฌํŠธํด๋ฆฌ์˜ค ํŒŒ์ผ์„ ์—…๋กœ๋“œํ•ฉ๋‹ˆ๋‹ค. ์‘๋‹ต ๋ฐ์ดํ„ฐ๋Š” S3์— ์ €์žฅ๋œ ํŒŒ์ผ ๊ฒฝ๋กœ์ž…๋‹ˆ๋‹ค.", + requestBody = @RequestBody( + description = "์—…๋กœ๋“œํ•  ํฌํŠธํด๋ฆฌ์˜ค ํŒŒ์ผ", + required = true, + content = @Content(mediaType = "multipart/form-data") + ), + responses = { + @ApiResponse(responseCode = "200", description = "์—…๋กœ๋“œ ์„ฑ๊ณต, data = filePath", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = ApiResponseWrapper.class) + ) + ), + @ApiResponse(responseCode = "401", description = "์„ธ์…˜ ๋งŒ๋ฃŒ ํ˜น์€ ๋กœ๊ทธ์ธ ํ•„์š”") + } + ) + @PostMapping + ResponseEntity> uploadPortfolio(@ModelAttribute PortfolioUploadRequest request, HttpSession session); + +} diff --git a/src/main/java/es/princip/ringus/infra/storage/api/ProfileImageController.java b/src/main/java/es/princip/ringus/infra/storage/api/ProfileImageController.java index d169547..d2dc986 100644 --- a/src/main/java/es/princip/ringus/infra/storage/api/ProfileImageController.java +++ b/src/main/java/es/princip/ringus/infra/storage/api/ProfileImageController.java @@ -1,6 +1,7 @@ package es.princip.ringus.infra.storage.api; import es.princip.ringus.domain.exception.MemberErrorCode; +import es.princip.ringus.global.annotation.SessionCheck; import es.princip.ringus.global.annotation.SessionMemberId; import es.princip.ringus.global.exception.CustomRuntimeException; import es.princip.ringus.global.util.ApiResponseWrapper; @@ -23,10 +24,13 @@ public class ProfileImageController implements ProfileImageControllerDocs { * ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ */ @PostMapping("/image") + @SessionCheck public ResponseEntity> uploadProfileImage( - @ModelAttribute ProfileUploadRequest request + @ModelAttribute ProfileUploadRequest request, + @SessionMemberId Long memberId ) { - String filePath = storageProfileService.uploadProfileImage(request); + + String filePath = storageProfileService.uploadProfileImage(request, memberId); return ResponseEntity.ok(ApiResponseWrapper.success(HttpStatus.OK, filePath)); } diff --git a/src/main/java/es/princip/ringus/infra/storage/api/ProfileImageControllerDocs.java b/src/main/java/es/princip/ringus/infra/storage/api/ProfileImageControllerDocs.java index 5e20b47..b19d91a 100644 --- a/src/main/java/es/princip/ringus/infra/storage/api/ProfileImageControllerDocs.java +++ b/src/main/java/es/princip/ringus/infra/storage/api/ProfileImageControllerDocs.java @@ -1,5 +1,6 @@ package es.princip.ringus.infra.storage.api; +import es.princip.ringus.global.annotation.SessionMemberId; import es.princip.ringus.global.util.ApiResponseWrapper; import es.princip.ringus.infra.storage.dto.ProfileUploadRequest; import io.swagger.v3.oas.annotations.Operation; @@ -32,6 +33,9 @@ public interface ProfileImageControllerDocs { @PostMapping(value = "/image", consumes = "multipart/form-data") ResponseEntity> uploadProfileImage( @Parameter(description = "ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ์š”์ฒญ ๋ฐ์ดํ„ฐ (ํผ๋ฐ์ดํ„ฐ ํ˜•์‹)", required = true) - @ModelAttribute ProfileUploadRequest request + @ModelAttribute ProfileUploadRequest request, + @Parameter(description = "์„ธ์…˜์—์„œ ์กฐํšŒํ•œ ์‚ฌ์šฉ์ž ID", hidden = true) + @SessionMemberId + Long memberId ); } diff --git a/src/main/java/es/princip/ringus/infra/storage/application/S3Service.java b/src/main/java/es/princip/ringus/infra/storage/application/S3Service.java index 55a0b30..65c7ea1 100644 --- a/src/main/java/es/princip/ringus/infra/storage/application/S3Service.java +++ b/src/main/java/es/princip/ringus/infra/storage/application/S3Service.java @@ -4,14 +4,21 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; +import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider; import software.amazon.awssdk.core.sync.RequestBody; +import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.model.GetObjectRequest; import software.amazon.awssdk.services.s3.model.PutObjectRequest; import software.amazon.awssdk.services.s3.model.S3Exception; +import software.amazon.awssdk.services.s3.presigner.S3Presigner; +import software.amazon.awssdk.services.s3.presigner.model.GetObjectPresignRequest; +import software.amazon.awssdk.services.s3.presigner.model.PresignedGetObjectRequest; import java.io.IOException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; +import java.time.Duration; import java.util.UUID; @Service @@ -22,25 +29,31 @@ public class S3Service { @Value("${aws.s3.bucket-name}") private String bucketName; + @Value("${aws.s3.region}") + private String region; /** * S3์— ํŒŒ์ผ ์—…๋กœ๋“œ * @param file ์—…๋กœ๋“œํ•  ํŒŒ์ผ * @param folderPath S3์— ์ €์žฅํ•  ํด๋” ๊ฒฝ๋กœ (์˜ˆ: "images/profile/mentor", "certificates/mentee/ENROLLMENT" ๋“ฑ) * @return ์—…๋กœ๋“œ๋œ ํŒŒ์ผ์˜ S3 URL */ - public String uploadFile(MultipartFile file, String folderPath) { - String fileName = UUID.randomUUID() + "_" + sanitizeFileName(file.getOriginalFilename()); + public String uploadFile(MultipartFile file, String folderPath, boolean isPublic) { + // ํŒŒ์ผ๋ช…: UUID + ํ™•์žฅ์ž๋งŒ ์ถ”์ถœ + String extension = getExtension(file.getOriginalFilename()); + String fileName = UUID.randomUUID() + (extension != null ? "." + extension : ""); String s3Key = folderPath + "/" + fileName; try { + PutObjectRequest.Builder requestBuilder = PutObjectRequest.builder() + .bucket(bucketName) + .key(s3Key) + .contentType(file.getContentType()); + s3Client.putObject( - PutObjectRequest.builder() - .bucket(bucketName) - .key(s3Key) - .contentType(file.getContentType()) - .build(), + requestBuilder.build(), RequestBody.fromInputStream(file.getInputStream(), file.getSize()) ); + } catch (IOException e) { throw new RuntimeException("ํŒŒ์ผ ์ž…๋ ฅ ์ŠคํŠธ๋ฆผ์„ ์ฝ์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค.", e); } catch (S3Exception e) { @@ -68,4 +81,35 @@ private String sanitizeFileName(String originalFilename) { throw new RuntimeException("ํŒŒ์ผ ์ด๋ฆ„ ์ธ์ฝ”๋”ฉ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.", e); } } + + public String generatePresignedUrl(String s3Key, Duration duration) { + try (S3Presigner presigner = S3Presigner.builder() + .region(Region.of(region)) + .credentialsProvider(DefaultCredentialsProvider.create()) + .build()) { + + GetObjectRequest getObjectRequest = GetObjectRequest.builder() + .bucket(bucketName) + .key(s3Key) + .build(); + + GetObjectPresignRequest presignRequest = GetObjectPresignRequest.builder() + .getObjectRequest(getObjectRequest) + .signatureDuration(duration) + .build(); + + PresignedGetObjectRequest presignedRequest = presigner.presignGetObject(presignRequest); + return presignedRequest.url().toString(); + } + } + + /** + * ํŒŒ์ผ ํ™•์žฅ์ž ์ถ”์ถœ + */ + private String getExtension(String fileName) { + if (fileName == null || !fileName.contains(".")) { + return null; + } + return fileName.substring(fileName.lastIndexOf('.') + 1); + } } diff --git a/src/main/java/es/princip/ringus/infra/storage/application/StorageCertificateService.java b/src/main/java/es/princip/ringus/infra/storage/application/StorageCertificateService.java index 326e41c..9691c4e 100644 --- a/src/main/java/es/princip/ringus/infra/storage/application/StorageCertificateService.java +++ b/src/main/java/es/princip/ringus/infra/storage/application/StorageCertificateService.java @@ -1,12 +1,23 @@ package es.princip.ringus.infra.storage.application; +import es.princip.ringus.domain.exception.FileErrorCode; +import es.princip.ringus.domain.exception.MemberErrorCode; +import es.princip.ringus.domain.member.Member; +import es.princip.ringus.domain.member.MemberRepository; +import es.princip.ringus.global.exception.CustomRuntimeException; import es.princip.ringus.global.util.StoragePathUtil; +import es.princip.ringus.infra.storage.domain.FileMember; import es.princip.ringus.infra.storage.domain.FileMemberRepository; import es.princip.ringus.infra.storage.dto.CertificateUploadRequest; +import es.princip.ringus.infra.storage.dto.FilePreviewResponse; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.time.Duration; +import java.util.List; +import java.util.stream.Collectors; + @Service @Transactional(readOnly = true) @RequiredArgsConstructor @@ -20,7 +31,7 @@ public class StorageCertificateService { @Transactional public String uploadMenteeCertificate(CertificateUploadRequest request, Long memberId) { String folderPath = StoragePathUtil.buildCertificateFolderPath(request.certificateType(), false); - String filePath = s3Service.uploadFile(request.file(), folderPath); + String filePath = s3Service.uploadFile(request.file(), folderPath, false); fileMemberRepository.save(request.toFileMemberEntity(filePath, memberId)); @@ -33,11 +44,12 @@ public String uploadMenteeCertificate(CertificateUploadRequest request, Long mem @Transactional public String uploadMentorCertificate(CertificateUploadRequest request, Long memberId) { String folderPath = StoragePathUtil.buildCertificateFolderPath(request.certificateType(), true); - String filePath = s3Service.uploadFile(request.file(), folderPath); + String filePath = s3Service.uploadFile(request.file(), folderPath, false); fileMemberRepository.save(request.toFileMemberEntity(filePath, memberId)); return filePath; } + } diff --git a/src/main/java/es/princip/ringus/infra/storage/application/StorageFileService.java b/src/main/java/es/princip/ringus/infra/storage/application/StorageFileService.java new file mode 100644 index 0000000..e83588c --- /dev/null +++ b/src/main/java/es/princip/ringus/infra/storage/application/StorageFileService.java @@ -0,0 +1,58 @@ +package es.princip.ringus.infra.storage.application; + +import es.princip.ringus.domain.exception.FileErrorCode; +import es.princip.ringus.domain.exception.MemberErrorCode; +import es.princip.ringus.domain.member.Member; +import es.princip.ringus.domain.member.MemberRepository; +import es.princip.ringus.global.exception.CustomRuntimeException; +import es.princip.ringus.infra.storage.domain.FileMember; +import es.princip.ringus.infra.storage.domain.FileMemberRepository; +import es.princip.ringus.infra.storage.dto.FilePreviewResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.Duration; +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class StorageFileService { + private final S3Service s3Service; + private final FileMemberRepository fileMemberRepository; + private final MemberRepository memberRepository; + + @Transactional + public FilePreviewResponse getFile(Long fileMemberId) { + FileMember fileMember = fileMemberRepository.findById(fileMemberId) + .orElseThrow(() -> new CustomRuntimeException(FileErrorCode.FILE_NOT_FOUND)); + + Member member = getMember(fileMember.getMemberId()); + + String presignedUrl = getPresignedUrl(fileMember.getFilePath()); + + return FilePreviewResponse.of(presignedUrl, fileMember, member); + } + + public List getFiles() { + List fileMembers = fileMemberRepository.findAll(); + return fileMembers.stream() + .map(fileMember -> { + Member member = getMember(fileMember.getMemberId()); + String presignedUrl = getPresignedUrl(fileMember.getFilePath()); + return FilePreviewResponse.of(presignedUrl, fileMember, member); + }) + .collect(Collectors.toList()); + } + + private String getPresignedUrl(String filePath) { + return s3Service.generatePresignedUrl(filePath, Duration.ofMinutes(10)); + } + + private Member getMember(Long memberId) { + return memberRepository.findById(memberId) + .orElseThrow(() -> new CustomRuntimeException(MemberErrorCode.MEMBER_NOT_FOUND)); + } +} diff --git a/src/main/java/es/princip/ringus/infra/storage/application/StoragePortfolioService.java b/src/main/java/es/princip/ringus/infra/storage/application/StoragePortfolioService.java new file mode 100644 index 0000000..bed7c2b --- /dev/null +++ b/src/main/java/es/princip/ringus/infra/storage/application/StoragePortfolioService.java @@ -0,0 +1,29 @@ +package es.princip.ringus.infra.storage.application; + +import es.princip.ringus.infra.storage.domain.FileMember; +import es.princip.ringus.infra.storage.domain.FileMemberRepository; +import es.princip.ringus.infra.storage.dto.PortfolioUploadRequest; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class StoragePortfolioService { + private final S3Service s3Service; + private final FileMemberRepository fileMemberRepository; + + /** + * ๋ฉ˜ํ†  ํฌํŠธํด๋ฆฌ์˜ค ์—…๋กœ๋“œ + */ + @Transactional + public String uploadMentorPortfolio(PortfolioUploadRequest request, Long memberId) { + String folderPath = "portfolio/mentor/" + memberId; + String filePath = s3Service.uploadFile(request.file(), folderPath, true); + + fileMemberRepository.save(request.toFileMemberEntity(filePath, memberId)); + return filePath; + } +} diff --git a/src/main/java/es/princip/ringus/infra/storage/application/StorageProfileImageService.java b/src/main/java/es/princip/ringus/infra/storage/application/StorageProfileImageService.java index d19e7af..1171306 100644 --- a/src/main/java/es/princip/ringus/infra/storage/application/StorageProfileImageService.java +++ b/src/main/java/es/princip/ringus/infra/storage/application/StorageProfileImageService.java @@ -1,6 +1,7 @@ package es.princip.ringus.infra.storage.application; import es.princip.ringus.global.util.StoragePathUtil; +import es.princip.ringus.infra.storage.domain.FileMemberRepository; import es.princip.ringus.infra.storage.dto.ProfileUploadRequest; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -12,14 +13,20 @@ public class StorageProfileImageService { private final S3Service s3Service; + private final FileMemberRepository fileMemberRepository; /** * ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ */ @Transactional - public String uploadProfileImage(ProfileUploadRequest request) { + public String uploadProfileImage(ProfileUploadRequest request, Long memberId) { String folderPath = StoragePathUtil.buildProfileFolderPath(request.memberType()); - return s3Service.uploadFile(request.file(), folderPath); + + String filePath = s3Service.uploadFile(request.file(), folderPath, true); + + fileMemberRepository.save(request.toFileMemberEntity(filePath, memberId)); + + return filePath; } } diff --git a/src/main/java/es/princip/ringus/infra/storage/domain/Certificate.java b/src/main/java/es/princip/ringus/infra/storage/domain/Certificate.java index 5a19549..e1ebd74 100644 --- a/src/main/java/es/princip/ringus/infra/storage/domain/Certificate.java +++ b/src/main/java/es/princip/ringus/infra/storage/domain/Certificate.java @@ -18,8 +18,8 @@ public class Certificate extends File { private CertificateType certificateType; @Builder - public Certificate(String fileName, String filePath, CertificateType certificateType) { - super(fileName, filePath); + public Certificate(String fileName, String filePath, CertificateType certificateType, Integer fileSize) { + super(fileName, filePath,fileSize); this.certificateType = certificateType; } } diff --git a/src/main/java/es/princip/ringus/infra/storage/domain/File.java b/src/main/java/es/princip/ringus/infra/storage/domain/File.java index 5caa086..5e8a6a0 100644 --- a/src/main/java/es/princip/ringus/infra/storage/domain/File.java +++ b/src/main/java/es/princip/ringus/infra/storage/domain/File.java @@ -16,7 +16,16 @@ public abstract class File { @Column(nullable = false) private String filePath; // S3 ๊ฒฝ๋กœ - protected File(String fileName, String filePath) { + @Column + private Integer fileSize; // ํŒŒ์ผ ํฌ๊ธฐ MB ๋‹จ์œ„ + + protected File(String fileName, String filePath, Integer fileSize) { + this.fileName = fileName; + this.filePath = filePath; + this.fileSize = fileSize; + } + + public File(String fileName, String filePath) { this.fileName = fileName; this.filePath = filePath; } diff --git a/src/main/java/es/princip/ringus/infra/storage/dto/FilePreviewResponse.java b/src/main/java/es/princip/ringus/infra/storage/dto/FilePreviewResponse.java new file mode 100644 index 0000000..72303de --- /dev/null +++ b/src/main/java/es/princip/ringus/infra/storage/dto/FilePreviewResponse.java @@ -0,0 +1,20 @@ +package es.princip.ringus.infra.storage.dto; + +import es.princip.ringus.domain.member.Member; + +import es.princip.ringus.infra.storage.domain.FileMember; +import es.princip.ringus.presentation.member.dto.MemberResponse; + +public record FilePreviewResponse( + String filePath, + MemberResponse memberResponse, + Boolean isVerified +) { + public static FilePreviewResponse of(String presignedUrl,FileMember fileMember, Member member) { + return new FilePreviewResponse( + presignedUrl, + MemberResponse.of(member), + fileMember.getIsVerified() + ); + } +} diff --git a/src/main/java/es/princip/ringus/infra/storage/dto/PortfolioUploadRequest.java b/src/main/java/es/princip/ringus/infra/storage/dto/PortfolioUploadRequest.java new file mode 100644 index 0000000..1ccf9da --- /dev/null +++ b/src/main/java/es/princip/ringus/infra/storage/dto/PortfolioUploadRequest.java @@ -0,0 +1,16 @@ +package es.princip.ringus.infra.storage.dto; + +import es.princip.ringus.infra.storage.domain.FileMember; +import org.springframework.web.multipart.MultipartFile; + +public record PortfolioUploadRequest( + MultipartFile file +) { + + public FileMember toFileMemberEntity(String filePath, Long memberId) { + return FileMember.builder() + .memberId(memberId) + .filePath(filePath) + .build(); + } +} diff --git a/src/main/java/es/princip/ringus/infra/storage/dto/ProfileUploadRequest.java b/src/main/java/es/princip/ringus/infra/storage/dto/ProfileUploadRequest.java index c9b3cbd..c603df8 100644 --- a/src/main/java/es/princip/ringus/infra/storage/dto/ProfileUploadRequest.java +++ b/src/main/java/es/princip/ringus/infra/storage/dto/ProfileUploadRequest.java @@ -1,6 +1,7 @@ package es.princip.ringus.infra.storage.dto; import es.princip.ringus.domain.member.MemberType; +import es.princip.ringus.infra.storage.domain.FileMember; import es.princip.ringus.infra.storage.domain.ProfileImage; import io.swagger.v3.oas.annotations.media.Schema; import org.springframework.web.multipart.MultipartFile; @@ -27,4 +28,11 @@ public static ProfileImage toEntity(ProfileUploadRequest request, String filePat .filePath(filePath) .build(); } + + public FileMember toFileMemberEntity(String filePath, Long memberId) { + return FileMember.builder() + .memberId(memberId) + .filePath(filePath) + .build(); + } } diff --git a/src/main/java/es/princip/ringus/presentation/auth/AuthController.java b/src/main/java/es/princip/ringus/presentation/auth/AuthController.java index 36c7fcd..c0c4fe4 100644 --- a/src/main/java/es/princip/ringus/presentation/auth/AuthController.java +++ b/src/main/java/es/princip/ringus/presentation/auth/AuthController.java @@ -4,6 +4,7 @@ import es.princip.ringus.application.auth.service.EmailVerificationService; import es.princip.ringus.global.util.ApiResponseWrapper; import es.princip.ringus.global.util.CookieUtil; +import es.princip.ringus.global.util.PasswordVaildator; import es.princip.ringus.presentation.auth.dto.request.EmailVerifyRequest; import es.princip.ringus.presentation.auth.dto.request.GenerateCodeRequest; import es.princip.ringus.presentation.auth.dto.request.LoginRequest; @@ -14,6 +15,7 @@ import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpSession; import jakarta.validation.Valid; +import java.net.URI; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; @@ -23,8 +25,6 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import java.net.URI; - @Slf4j @RestController @RequiredArgsConstructor @@ -38,6 +38,8 @@ public class AuthController implements AuthControllerDocs{ @PostMapping("/signup") public ResponseEntity signUp(@Valid @RequestBody SignUpRequest request, HttpSession session, HttpServletResponse httpResponse) { + PasswordVaildator.validate(request.password()); + SignUpResponse response = authService.signUp(request, session); CookieUtil.deleteCookie(httpResponse, "JSESSIONID"); @@ -76,7 +78,7 @@ public ResponseEntity> logout(HttpSession session, Http @PostMapping("/email/code") public ResponseEntity> requestCode(@Valid @RequestBody GenerateCodeRequest request) { - emailVerificationService.generateVerificationCode(request.email()); + emailVerificationService.generateVerificationCode(request); return ResponseEntity.ok(ApiResponseWrapper.success(HttpStatus.OK, "์ธ์ฆ๋ฒˆํ˜ธ๊ฐ€ ๋ฐœ๊ธ‰๋˜์—ˆ์Šต๋‹ˆ๋‹ค")); } diff --git a/src/main/java/es/princip/ringus/presentation/auth/dto/request/GenerateCodeRequest.java b/src/main/java/es/princip/ringus/presentation/auth/dto/request/GenerateCodeRequest.java index e3880ed..aaafff2 100644 --- a/src/main/java/es/princip/ringus/presentation/auth/dto/request/GenerateCodeRequest.java +++ b/src/main/java/es/princip/ringus/presentation/auth/dto/request/GenerateCodeRequest.java @@ -5,5 +5,7 @@ public record GenerateCodeRequest( @Email @NotBlank - String email + String email, + + Boolean isPasswordReset ) { } \ No newline at end of file diff --git a/src/main/java/es/princip/ringus/presentation/auth/dto/request/SignUpRequest.java b/src/main/java/es/princip/ringus/presentation/auth/dto/request/SignUpRequest.java index 7e0dde3..336b167 100644 --- a/src/main/java/es/princip/ringus/presentation/auth/dto/request/SignUpRequest.java +++ b/src/main/java/es/princip/ringus/presentation/auth/dto/request/SignUpRequest.java @@ -17,10 +17,6 @@ public record SignUpRequest( @NotBlank(message = "๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š˜.") @Size(min = 8, max = 20, message = "๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” 8~20์ž์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค.") - @Pattern( - regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[!@#$%^&*_+=/])[A-Za-z\\d!@#$%^&*_+=/]{8,20}$", - message = "๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” 8~20์ž์˜ ๋Œ€์†Œ๋ฌธ์ž, ์ˆซ์ž, ํŠน์ˆ˜๋ฌธ์ž๋กœ ๊ตฌ์„ฑํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค." - ) String password, @NotNull Set serviceTerms diff --git a/src/main/java/es/princip/ringus/presentation/common/dto/OrganizationRequest.java b/src/main/java/es/princip/ringus/presentation/common/dto/OrganizationRequest.java index 9ef51bf..e71dd1a 100644 --- a/src/main/java/es/princip/ringus/presentation/common/dto/OrganizationRequest.java +++ b/src/main/java/es/princip/ringus/presentation/common/dto/OrganizationRequest.java @@ -11,6 +11,19 @@ public record OrganizationRequest( int experience ) { public Organization toEntity() { - return new Organization(name, JobCategory.valueOf(jobCategory), DetailedJob.valueOf(detailedJob), experience); + + JobCategory jobCategoryKor = JobCategory.from(jobCategory); + DetailedJob detailedJobKor = DetailedJob.from(detailedJob); + + if (!detailedJobKor.getCategory().getCode().equals(jobCategoryKor.getCode())) { + throw new IllegalArgumentException( + "์„ธ๋ถ€ ์ง๋ฌด์˜ ์ง๋ฌด ์นดํ…Œ๊ณ ๋ฆฌ๊ฐ€ ์ง๋ฌด ์นดํ…Œ๊ณ ๋ฆฌ์™€ ์ผ์น˜ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค."); + } + return new Organization( + name, + jobCategoryKor, + detailedJobKor, + experience + ); } } \ No newline at end of file diff --git a/src/main/java/es/princip/ringus/presentation/common/dto/OrganizationResponse.java b/src/main/java/es/princip/ringus/presentation/common/dto/OrganizationResponse.java index 0232cf4..e110c43 100644 --- a/src/main/java/es/princip/ringus/presentation/common/dto/OrganizationResponse.java +++ b/src/main/java/es/princip/ringus/presentation/common/dto/OrganizationResponse.java @@ -11,8 +11,8 @@ public record OrganizationResponse( public static OrganizationResponse from(final Organization organization) { return new OrganizationResponse( organization.getName(), - organization.getJobCategory().name(), - organization.getDetailedJob().name(), + organization.getJobCategory().getKor(), + organization.getDetailedJob().getKor(), organization.getExperience() ); } diff --git a/src/main/java/es/princip/ringus/presentation/common/dto/PortfolioRequest.java b/src/main/java/es/princip/ringus/presentation/common/dto/PortfolioRequest.java index e6b4e4e..6c12d00 100644 --- a/src/main/java/es/princip/ringus/presentation/common/dto/PortfolioRequest.java +++ b/src/main/java/es/princip/ringus/presentation/common/dto/PortfolioRequest.java @@ -4,9 +4,14 @@ public record PortfolioRequest( String url, - String description + String description, + Integer fileSize ) { public Portfolio toEntity() { - return new Portfolio(url, description); + return Portfolio.builder() + .filePath(url) + .fileName(description) + .fileSize(fileSize) + .build(); } } diff --git a/src/main/java/es/princip/ringus/presentation/common/dto/PortfolioResponse.java b/src/main/java/es/princip/ringus/presentation/common/dto/PortfolioResponse.java index 2c20226..aa9ee93 100644 --- a/src/main/java/es/princip/ringus/presentation/common/dto/PortfolioResponse.java +++ b/src/main/java/es/princip/ringus/presentation/common/dto/PortfolioResponse.java @@ -4,9 +4,10 @@ public record PortfolioResponse( String url, - String description + String description, + Integer fileSize ) { public static PortfolioResponse from(final Portfolio portfolio) { - return new PortfolioResponse(portfolio.getUrl(), portfolio.getDescription()); + return new PortfolioResponse(portfolio.getFilePath(), portfolio.getFileName(), portfolio.getFileSize()); } } diff --git a/src/main/java/es/princip/ringus/presentation/common/dto/TimezoneResponse.java b/src/main/java/es/princip/ringus/presentation/common/dto/TimezoneResponse.java index 3ef2943..335e823 100644 --- a/src/main/java/es/princip/ringus/presentation/common/dto/TimezoneResponse.java +++ b/src/main/java/es/princip/ringus/presentation/common/dto/TimezoneResponse.java @@ -3,16 +3,16 @@ import es.princip.ringus.domain.mentor.vo.Timezone; import java.time.LocalTime; -import java.util.Set; +import java.util.List; public record TimezoneResponse( - Set days, + List days, LocalTime startTime, LocalTime endTime ) { public static TimezoneResponse from(final Timezone timezone) { return new TimezoneResponse( - timezone.getDays(), + timezone.getDaysKor(), timezone.getStartTime(), timezone.getEndTime() ); diff --git a/src/main/java/es/princip/ringus/presentation/member/MemberController.java b/src/main/java/es/princip/ringus/presentation/member/MemberController.java index cd77070..ba0efb4 100644 --- a/src/main/java/es/princip/ringus/presentation/member/MemberController.java +++ b/src/main/java/es/princip/ringus/presentation/member/MemberController.java @@ -4,7 +4,12 @@ import es.princip.ringus.global.annotation.SessionCheck; import es.princip.ringus.global.annotation.SessionMemberId; import es.princip.ringus.global.util.ApiResponseWrapper; +import es.princip.ringus.global.util.CookieUtil; +import es.princip.ringus.global.util.PasswordVaildator; import es.princip.ringus.presentation.member.dto.MemberResponse; +import es.princip.ringus.presentation.member.dto.PasswordUpdateRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -39,4 +44,21 @@ public ResponseEntity> isUniqueNickname(@RequestPara return ResponseEntity.ok(ApiResponseWrapper.success(HttpStatus.OK, response)); } + @PatchMapping("/password") + public ResponseEntity> updatePassword( + @RequestBody PasswordUpdateRequest request, + HttpSession session, + HttpServletResponse httpResponse + ){ + PasswordVaildator.validate(request.newPassword()); + + memberService.updatePassword(request, session); + + CookieUtil.deleteCookie(httpResponse, "JSESSIONID"); + + session.invalidate(); + + return ResponseEntity.ok(ApiResponseWrapper.success(HttpStatus.OK, "๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ๋ณ€๊ฒฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.")); + } + } \ No newline at end of file diff --git a/src/main/java/es/princip/ringus/presentation/member/dto/MentorProfileResponse.java b/src/main/java/es/princip/ringus/presentation/member/dto/MentorProfileResponse.java index 65e7a3e..1a73129 100644 --- a/src/main/java/es/princip/ringus/presentation/member/dto/MentorProfileResponse.java +++ b/src/main/java/es/princip/ringus/presentation/member/dto/MentorProfileResponse.java @@ -8,9 +8,14 @@ public record MentorProfileResponse( String nickname, ProfileImageResponse image, OrganizationResponse organization, - int mentoringCount + Long mentoringCount ) { - public static MentorProfileResponse from(final Mentor mentor){ - return new MentorProfileResponse(mentor.getNickname(), ProfileImageResponse.from(mentor.getProfileImage()), OrganizationResponse.from(mentor.getOrganization()),0); + public static MentorProfileResponse from(final Mentor mentor, Long mentoringCount){ + return new MentorProfileResponse( + mentor.getNickname(), + ProfileImageResponse.from(mentor.getProfileImage()), + OrganizationResponse.from(mentor.getOrganization()), + mentoringCount + ); } } \ No newline at end of file diff --git a/src/main/java/es/princip/ringus/presentation/member/dto/PasswordUpdateRequest.java b/src/main/java/es/princip/ringus/presentation/member/dto/PasswordUpdateRequest.java new file mode 100644 index 0000000..8555d10 --- /dev/null +++ b/src/main/java/es/princip/ringus/presentation/member/dto/PasswordUpdateRequest.java @@ -0,0 +1,6 @@ +package es.princip.ringus.presentation.member.dto; + +public record PasswordUpdateRequest( + String email, + String newPassword +) { } diff --git a/src/main/java/es/princip/ringus/presentation/mentee/MenteeController.java b/src/main/java/es/princip/ringus/presentation/mentee/MenteeController.java index 8b92990..f67dd76 100644 --- a/src/main/java/es/princip/ringus/presentation/mentee/MenteeController.java +++ b/src/main/java/es/princip/ringus/presentation/mentee/MenteeController.java @@ -1,10 +1,13 @@ package es.princip.ringus.presentation.mentee; import es.princip.ringus.application.mentee.service.MenteeService; +import es.princip.ringus.domain.exception.MentorErrorCode; import es.princip.ringus.domain.support.CursorResponse; import es.princip.ringus.global.annotation.SessionCheck; import es.princip.ringus.global.annotation.SessionMemberId; +import es.princip.ringus.global.exception.CustomRuntimeException; import es.princip.ringus.global.util.ApiResponseWrapper; +import es.princip.ringus.global.util.SessionUtil; import es.princip.ringus.presentation.mentee.dto.*; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @@ -24,6 +27,7 @@ @RequestMapping("/mentee") public class MenteeController implements MenteeControllerDocs{ private final MenteeService menteeService; + private final SessionUtil sessionUtil; @SessionCheck @PostMapping @@ -56,8 +60,9 @@ public ResponseEntity>> ge @SessionMemberId Long memberId ) { - log.info(request.toString()); - log.info(pageable.toString()); + if (!sessionUtil.isLoginMentorUser(httpServletRequest.getSession(), memberId)) { + throw new CustomRuntimeException(MentorErrorCode.MENTOR_NOT_FOUND); + } CursorResponse response = menteeService.getMenteeBy(request, pageable, memberId); return ResponseEntity.ok(ApiResponseWrapper.success(HttpStatus.OK, "์„ฑ๊ณต", response)); diff --git a/src/main/java/es/princip/ringus/presentation/mentee/dto/MyMenteeResponse.java b/src/main/java/es/princip/ringus/presentation/mentee/dto/MyMenteeResponse.java index 07140d9..da4bf71 100644 --- a/src/main/java/es/princip/ringus/presentation/mentee/dto/MyMenteeResponse.java +++ b/src/main/java/es/princip/ringus/presentation/mentee/dto/MyMenteeResponse.java @@ -1,17 +1,23 @@ package es.princip.ringus.presentation.mentee.dto; +import es.princip.ringus.domain.member.Member; import es.princip.ringus.domain.mentee.Mentee; import es.princip.ringus.presentation.common.dto.EducationResponse; import es.princip.ringus.presentation.common.dto.ProfileImageResponse; public record MyMenteeResponse( + String email, String nickname, EducationResponse education, String introduction, ProfileImageResponse image ) { - public static MyMenteeResponse from(final Mentee mentee) { + public static MyMenteeResponse from( + final Member member, + final Mentee mentee + ) { return new MyMenteeResponse( + member.getEmail(), mentee.getNickname(), EducationResponse.from(mentee.getEducation()), mentee.getIntroduction(), diff --git a/src/main/java/es/princip/ringus/presentation/mentor/MentorController.java b/src/main/java/es/princip/ringus/presentation/mentor/MentorController.java index 9fcf8fe..7005ff2 100644 --- a/src/main/java/es/princip/ringus/presentation/mentor/MentorController.java +++ b/src/main/java/es/princip/ringus/presentation/mentor/MentorController.java @@ -20,7 +20,6 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -@Slf4j @RestController @RequiredArgsConstructor @RequestMapping("/mentor") @@ -55,14 +54,11 @@ public ResponseEntity>> ge HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse ) { - Long memberId = Optional.ofNullable(request) .filter(CursorRequest::isBookmarked) .map(req -> SessionToMemberId.getSessionMemberId(httpServletRequest, httpServletResponse)) .orElse(null); - log.info(request.toString()); - log.info(pageable.toString()); CursorResponse response = mentorService.getMentorBy(request, pageable, memberId); return ResponseEntity.ok(ApiResponseWrapper.success(HttpStatus.OK, "์„ฑ๊ณต", response)); } diff --git a/src/main/java/es/princip/ringus/presentation/mentor/dto/EditMentorRequest.java b/src/main/java/es/princip/ringus/presentation/mentor/dto/EditMentorRequest.java index 44ff616..0d94c16 100644 --- a/src/main/java/es/princip/ringus/presentation/mentor/dto/EditMentorRequest.java +++ b/src/main/java/es/princip/ringus/presentation/mentor/dto/EditMentorRequest.java @@ -2,14 +2,14 @@ import es.princip.ringus.presentation.common.dto.*; import jakarta.validation.constraints.NotBlank; +import org.hibernate.validator.constraints.UniqueElements; import java.util.List; -import java.util.Set; public record EditMentorRequest( @NotBlank String nickname, IntroductionRequest introduction, - Set mentoringField, + @UniqueElements List mentoringField, EducationRequest education, OrganizationRequest organization, TimezoneRequest timezone, diff --git a/src/main/java/es/princip/ringus/presentation/mentor/dto/MentorCardResponse.java b/src/main/java/es/princip/ringus/presentation/mentor/dto/MentorCardResponse.java index 494ddd6..e94682f 100644 --- a/src/main/java/es/princip/ringus/presentation/mentor/dto/MentorCardResponse.java +++ b/src/main/java/es/princip/ringus/presentation/mentor/dto/MentorCardResponse.java @@ -17,7 +17,8 @@ public record MentorCardResponse( IntroductionResponse introduction, OrganizationResponse organization, String message, - int mentoringCount, + Long mentoringCount, + @JsonInclude(NON_NULL) Boolean bookmarked, @JsonInclude(NON_NULL) String status ) { public static MentorCardResponse of( @@ -26,7 +27,31 @@ public static MentorCardResponse of( final ProfileImage profileImage, final Introduction introduction, final Organization organization, - final String status + final String message, + final Long mentoringCount + ) { + return new MentorCardResponse( + mentorId, + nickname, + ProfileImageResponse.from(profileImage), + IntroductionResponse.from(introduction), + OrganizationResponse.from(organization), + message, + mentoringCount, + null, + null + ); + } + + public static MentorCardResponse of( + final Long mentorId, + final String nickname, + final ProfileImage profileImage, + final Introduction introduction, + final Organization organization, + final String message, + final String status, + final Long mentoringCount ) { return new MentorCardResponse( mentorId, @@ -34,8 +59,9 @@ public static MentorCardResponse of( ProfileImageResponse.from(profileImage), IntroductionResponse.from(introduction), OrganizationResponse.from(organization), + message, + mentoringCount, null, - 0, status ); } @@ -47,7 +73,8 @@ public static MentorCardResponse of( final Introduction introduction, final Organization organization, final String message, - final int mentoringCount + final Long mentoringCount, + final Boolean bookmarked ) { return new MentorCardResponse( mentorId, @@ -57,6 +84,7 @@ public static MentorCardResponse of( OrganizationResponse.from(organization), message, mentoringCount, + bookmarked, null ); } diff --git a/src/main/java/es/princip/ringus/presentation/mentor/dto/MentorDetailResponse.java b/src/main/java/es/princip/ringus/presentation/mentor/dto/MentorDetailResponse.java index 0dc51ff..9283e18 100644 --- a/src/main/java/es/princip/ringus/presentation/mentor/dto/MentorDetailResponse.java +++ b/src/main/java/es/princip/ringus/presentation/mentor/dto/MentorDetailResponse.java @@ -2,11 +2,10 @@ import es.princip.ringus.domain.mentor.Mentor; import es.princip.ringus.domain.mentor.vo.Hashtag; +import es.princip.ringus.domain.mentor.vo.MentoringField; import es.princip.ringus.presentation.common.dto.*; import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; public record MentorDetailResponse( String nickname, @@ -14,24 +13,27 @@ public record MentorDetailResponse( OrganizationResponse organization, IntroductionResponse introduction, TimezoneResponse timezone, - Set mentoringField, + List mentoringField, List hashtags, String message, PortfolioResponse portfolio, - int mentoringCount + Long mentoringCount ) { - public static MentorDetailResponse from(final Mentor mentor) { + public static MentorDetailResponse from( + final Mentor mentor, + Long mentoringCount + ) { return new MentorDetailResponse( mentor.getNickname(), EducationResponse.from(mentor.getEducation()), OrganizationResponse.from(mentor.getOrganization()), IntroductionResponse.from(mentor.getIntroduction()), TimezoneResponse.from(mentor.getTimezone()), - mentor.getMentoringField().stream().map(String::valueOf).collect(Collectors.toSet()), + mentor.getMentoringField().stream().map(MentoringField::getKor).toList(), mentor.getHashtags().stream().map(Hashtag::getValue).toList(), mentor.getMessage(), PortfolioResponse.from(mentor.getPortfolio()), - 0 + mentoringCount ); } } diff --git a/src/main/java/es/princip/ringus/presentation/mentor/dto/MentorRequest.java b/src/main/java/es/princip/ringus/presentation/mentor/dto/MentorRequest.java index dd46f7c..6fb0011 100644 --- a/src/main/java/es/princip/ringus/presentation/mentor/dto/MentorRequest.java +++ b/src/main/java/es/princip/ringus/presentation/mentor/dto/MentorRequest.java @@ -6,15 +6,14 @@ import es.princip.ringus.domain.mentor.vo.MentoringField; import es.princip.ringus.presentation.common.dto.*; import jakarta.validation.constraints.NotBlank; +import org.hibernate.validator.constraints.UniqueElements; import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; public record MentorRequest( @NotBlank String nickname, IntroductionRequest introduction, - Set mentoringField, + @UniqueElements List mentoringField, EducationRequest education, OrganizationRequest organization, TimezoneRequest timezone, @@ -30,7 +29,7 @@ public Mentor toEntity(Long memberId) { .organization(organization.toEntity()) .introduction(introduction.toEntity()) .timezone(timezone.toEntity()) - .mentoringField(mentoringField.stream().map(MentoringField::valueOf).collect(Collectors.toSet())) + .mentoringField(mentoringField.stream().map(MentoringField::from).toList()) .hashtags(hashtags.stream().map(Hashtag::new).toList()) .message(message) .portfolio(portfolio.toEntity()) diff --git a/src/main/java/es/princip/ringus/presentation/mentor/dto/MyMentorResponse.java b/src/main/java/es/princip/ringus/presentation/mentor/dto/MyMentorResponse.java index 9750fc0..99b14f2 100644 --- a/src/main/java/es/princip/ringus/presentation/mentor/dto/MyMentorResponse.java +++ b/src/main/java/es/princip/ringus/presentation/mentor/dto/MyMentorResponse.java @@ -1,37 +1,41 @@ package es.princip.ringus.presentation.mentor.dto; +import es.princip.ringus.domain.member.Member; import es.princip.ringus.domain.mentor.Mentor; import es.princip.ringus.domain.mentor.vo.Hashtag; +import es.princip.ringus.domain.mentor.vo.MentoringField; import es.princip.ringus.presentation.common.dto.*; import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; public record MyMentorResponse( + String email, String nickname, EducationResponse education, OrganizationResponse organization, IntroductionResponse introduction, TimezoneResponse timezone, - Set mentoringField, + List mentoringField, List hashtags, String message, PortfolioResponse portfolio, - int mentoringCount + ProfileImageResponse image, + Long mentoringCount ) { - public static MyMentorResponse from(final Mentor mentor) { + public static MyMentorResponse from(final Member member, final Mentor mentor, Long mentoringCount) { return new MyMentorResponse( + member.getEmail(), mentor.getNickname(), EducationResponse.from(mentor.getEducation()), OrganizationResponse.from(mentor.getOrganization()), IntroductionResponse.from(mentor.getIntroduction()), TimezoneResponse.from(mentor.getTimezone()), - mentor.getMentoringField().stream().map(String::valueOf).collect(Collectors.toSet()), + mentor.getMentoringField().stream().map(MentoringField::getKor).toList(), mentor.getHashtags().stream().map(Hashtag::getValue).toList(), mentor.getMessage(), PortfolioResponse.from(mentor.getPortfolio()), - 0 + ProfileImageResponse.from(mentor.getProfileImage()), + mentoringCount ); } } diff --git a/src/main/java/es/princip/ringus/presentation/mentor/dto/TimezoneRequest.java b/src/main/java/es/princip/ringus/presentation/mentor/dto/TimezoneRequest.java index 52288e3..66e3e6d 100644 --- a/src/main/java/es/princip/ringus/presentation/mentor/dto/TimezoneRequest.java +++ b/src/main/java/es/princip/ringus/presentation/mentor/dto/TimezoneRequest.java @@ -1,13 +1,13 @@ package es.princip.ringus.presentation.mentor.dto; -import es.princip.ringus.domain.mentor.vo.Days; import es.princip.ringus.domain.mentor.vo.Timezone; +import org.hibernate.validator.constraints.UniqueElements; import java.time.LocalTime; -import java.util.Set; +import java.util.List; public record TimezoneRequest ( - Set days, + @UniqueElements List days, LocalTime startTime, LocalTime endTime ) { diff --git a/src/main/java/es/princip/ringus/presentation/mentoring/MentoringController.java b/src/main/java/es/princip/ringus/presentation/mentoring/MentoringController.java index 6c33cc4..282b7ff 100644 --- a/src/main/java/es/princip/ringus/presentation/mentoring/MentoringController.java +++ b/src/main/java/es/princip/ringus/presentation/mentoring/MentoringController.java @@ -4,8 +4,7 @@ import es.princip.ringus.global.annotation.SessionCheck; import es.princip.ringus.global.annotation.SessionMemberId; import es.princip.ringus.global.util.ApiResponseWrapper; -import es.princip.ringus.presentation.mentoring.dto.CreateMentoringRequest; -import es.princip.ringus.presentation.mentoring.dto.MentoringResponse; +import es.princip.ringus.presentation.mentoring.dto.*; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; @@ -30,4 +29,24 @@ public ResponseEntity> suggest( MentoringResponse response = mentoringService.createMentoring(request, memberId); return ResponseEntity.ok(ApiResponseWrapper.success(HttpStatus.OK, "์„ฑ๊ณต", response)); } + + @SessionCheck + @PostMapping("/cancel") + public ResponseEntity> cancel( + @SessionMemberId Long memberId, + @Valid @RequestBody CancelMentoringRequest request) { + MentoringCancelResponse response = mentoringService.cancelMentoring(request, memberId); + + return ResponseEntity.ok(ApiResponseWrapper.success(HttpStatus.OK, "์ทจ์†Œ ์„ฑ๊ณต", response)); + } + + @SessionCheck + @PostMapping("/reject") + public ResponseEntity> reject( + @SessionMemberId Long memberId, + @Valid @RequestBody RejectMentoringRequest request) { + MentoringRejectResponse response = mentoringService.rejectMentoring(request, memberId); + return ResponseEntity.ok(ApiResponseWrapper.success(HttpStatus.OK, "๊ฑฐ์ ˆ ์„ฑ๊ณต", response)); + } + } diff --git a/src/main/java/es/princip/ringus/presentation/mentoring/dto/CancelMentoringRequest.java b/src/main/java/es/princip/ringus/presentation/mentoring/dto/CancelMentoringRequest.java new file mode 100644 index 0000000..5e9de02 --- /dev/null +++ b/src/main/java/es/princip/ringus/presentation/mentoring/dto/CancelMentoringRequest.java @@ -0,0 +1,9 @@ +package es.princip.ringus.presentation.mentoring.dto; + +import jakarta.validation.constraints.NotNull; + +public record CancelMentoringRequest( + @NotNull + Long mentoringId +) { +} diff --git a/src/main/java/es/princip/ringus/presentation/mentoring/dto/EditMentoringTimeRequest.java b/src/main/java/es/princip/ringus/presentation/mentoring/dto/EditMentoringTimeRequest.java new file mode 100644 index 0000000..477dcc9 --- /dev/null +++ b/src/main/java/es/princip/ringus/presentation/mentoring/dto/EditMentoringTimeRequest.java @@ -0,0 +1,10 @@ +package es.princip.ringus.presentation.mentoring.dto; + +import es.princip.ringus.domain.mentoring.MentoringTime; +import java.util.List; + +public record EditMentoringTimeRequest( + Long mentoringId, + List applyTimes +) { +} \ No newline at end of file diff --git a/src/main/java/es/princip/ringus/presentation/mentoring/dto/MentoringCancelResponse.java b/src/main/java/es/princip/ringus/presentation/mentoring/dto/MentoringCancelResponse.java new file mode 100644 index 0000000..f87877f --- /dev/null +++ b/src/main/java/es/princip/ringus/presentation/mentoring/dto/MentoringCancelResponse.java @@ -0,0 +1,16 @@ +package es.princip.ringus.presentation.mentoring.dto; + +import es.princip.ringus.domain.mentoring.Mentoring; +import es.princip.ringus.domain.mentoring.MentoringStatus; + +public record MentoringCancelResponse( + Long mentoringId, + MentoringStatus status +) { + public static MentoringCancelResponse from(Mentoring mentoring) { + return new MentoringCancelResponse( + mentoring.getId(), + mentoring.getMentoringStatus() + ); + } +} diff --git a/src/main/java/es/princip/ringus/presentation/mentoring/dto/MentoringRejectResponse.java b/src/main/java/es/princip/ringus/presentation/mentoring/dto/MentoringRejectResponse.java new file mode 100644 index 0000000..29b4fe8 --- /dev/null +++ b/src/main/java/es/princip/ringus/presentation/mentoring/dto/MentoringRejectResponse.java @@ -0,0 +1,16 @@ +package es.princip.ringus.presentation.mentoring.dto; + +import es.princip.ringus.domain.mentoring.Mentoring; +import es.princip.ringus.domain.mentoring.MentoringStatus; + +public record MentoringRejectResponse( + Long mentoringId, + MentoringStatus status +) { + public static MentoringRejectResponse from(Mentoring mentoring) { + return new MentoringRejectResponse( + mentoring.getId(), + mentoring.getMentoringStatus() + ); + } +} diff --git a/src/main/java/es/princip/ringus/presentation/mentoring/dto/MentoringResponse.java b/src/main/java/es/princip/ringus/presentation/mentoring/dto/MentoringResponse.java index 1458f11..ec26b19 100644 --- a/src/main/java/es/princip/ringus/presentation/mentoring/dto/MentoringResponse.java +++ b/src/main/java/es/princip/ringus/presentation/mentoring/dto/MentoringResponse.java @@ -3,14 +3,13 @@ import es.princip.ringus.domain.mentoring.Mentoring; import es.princip.ringus.domain.mentoring.MentoringStatus; import es.princip.ringus.domain.mentoring.MentoringTime; -import es.princip.ringus.domain.mentoring.MentoringTopic; import java.util.List; public record MentoringResponse( Long mentoringId, MentoringStatus status, - MentoringTopic topic, + String topic, List applyTimes, String mentoringMessage, String mentorName, @@ -22,7 +21,7 @@ public static MentoringResponse from( return new MentoringResponse( mentoring.getId(), mentoring.getMentoringStatus(), - mentoring.getMentoringTopic(), + mentoring.getMentoringTopic().getKor(), mentoring.getApplyTimes(), mentoring.getMentoringMessage(), mentoring.getMentor().getNickname(), diff --git a/src/main/java/es/princip/ringus/presentation/mentoring/dto/RejectMentoringRequest.java b/src/main/java/es/princip/ringus/presentation/mentoring/dto/RejectMentoringRequest.java new file mode 100644 index 0000000..666fc63 --- /dev/null +++ b/src/main/java/es/princip/ringus/presentation/mentoring/dto/RejectMentoringRequest.java @@ -0,0 +1,9 @@ +package es.princip.ringus.presentation.mentoring.dto; + +import jakarta.validation.constraints.NotNull; + +public record RejectMentoringRequest( + @NotNull + Long mentoringId +) { +} diff --git a/src/main/java/es/princip/ringus/presentation/notification/NotificationController.java b/src/main/java/es/princip/ringus/presentation/notification/NotificationController.java new file mode 100644 index 0000000..b4e2f59 --- /dev/null +++ b/src/main/java/es/princip/ringus/presentation/notification/NotificationController.java @@ -0,0 +1,64 @@ +package es.princip.ringus.presentation.notification; + +import es.princip.ringus.domain.notification.Notification; +import es.princip.ringus.domain.notification.NotificationType; +import es.princip.ringus.global.annotation.SessionCheck; +import es.princip.ringus.global.annotation.SessionMemberId; +import es.princip.ringus.global.sender.EmitterRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import java.io.IOException; + +@RestController +@RequestMapping("/notifications") +@RequiredArgsConstructor +public class NotificationController { + + private final EmitterRepository emitterRepository; + + @GetMapping("/{receiverId}") + public void testSend(@PathVariable Long receiverId) { + Notification n = Notification.builder() + .title("ํ…Œ์ŠคํŠธ ์•Œ๋ฆผ") + .content("Postman SSE ํ…Œ์ŠคํŠธ์ž…๋‹ˆ๋‹ค.") + .receiverId(receiverId) + .type(NotificationType.MENTORING_APPROVED) + .build(); + try { + emitterRepository.get(receiverId).ifPresent(emitter -> { + try { + emitter.send( + SseEmitter.event() + .name("notification") + .data(n) // ์ง๋ ฌํ™” ๊ทœ์น™์€ Jackson ๊ธฐ๋ณธ + ); + } catch (IOException e) { + emitter.completeWithError(e); + emitterRepository.remove(receiverId); + } + }); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @SessionCheck + @GetMapping(value = "/subscribe", produces = MediaType.TEXT_EVENT_STREAM_VALUE) + public SseEmitter subscribe(@SessionMemberId Long memberId) { + SseEmitter emitter = emitterRepository.save(memberId); + try { + emitter.send( + SseEmitter.event() + .name("connected") + .data("SSE ์—ฐ๊ฒฐ ์™„๋ฃŒ") + ); + } catch (IOException e) { + emitter.completeWithError(e); + emitterRepository.remove(memberId); + } + return emitter; + } +} \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index af76a90..65e6336 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -64,3 +64,7 @@ springdoc: swagger-ui: path: /swagger +app: + notification: + emitter: + timeout: ${NOTIFICATION_EMITTER_TIMEOUT} diff --git a/src/main/resources/db/data/mentor-cursor-test.sql b/src/main/resources/db/data/mentor-cursor-test.sql index 903b89c..8dcb35a 100644 --- a/src/main/resources/db/data/mentor-cursor-test.sql +++ b/src/main/resources/db/data/mentor-cursor-test.sql @@ -100,50 +100,71 @@ INSERT INTO member (email, password, member_type, is_file_verified, is_profile_r ('user99@example.com', 'password99', 'ROLE_MENTEE', false, false, false), ('user100@example.com', 'password100', 'ROLE_MENTEE', false, false, false); +INSERT INTO mentor +(end_time, experience, start_time, member_id, + introduction_title, message, introduction_content, description, + file_name, file_path, major, name, nickname, school_name, url, + days, detailed_job, job_category) +VALUES + ('18:00:00',5,'09:00:00', 1,'Intro1','Message1','Content1','Desc1','file1.jpg','/path/to/file1','Major1','Name1','Nick1','School1','http://url1.com','MON','BRAND_MARKETING','MARKETING'), + ('18:00:00',5,'09:00:00', 2,'Intro2','Message2','Content2','Desc2','file2.jpg','/path/to/file2','Major2','Name2','Nick2','School2','http://url2.com','TUE','PERFORMANCE_MARKETING','MARKETING'), + ('18:00:00',5,'09:00:00', 3,'Intro3','Message3','Content3','Desc3','file3.jpg','/path/to/file3','Major3','Name3','Nick3','School3','http://url3.com','WED','DIGITAL_SOCIAL_MARKETING','MARKETING'), + ('18:00:00',5,'09:00:00', 4,'Intro4','Message4','Content4','Desc4','file4.jpg','/path/to/file4','Major4','Name4','Nick4','School4','http://url4.com','THU','GROWTH_MARKETING','MARKETING'), + ('18:00:00',5,'09:00:00', 5,'Intro5','Message5','Content5','Desc5','file5.jpg','/path/to/file5','Major5','Name5','Nick5','School5','http://url5.com','FRI','BRAND_MARKETING','MARKETING'), -INSERT INTO mentor (end_time, experience, start_time, member_id, introduction_title, message, introduction_content, description, file_name, file_path, major, name, nickname, school_name, url, days, detailed_job, job_category) VALUES - ('18:00:00', 5, '09:00:00', 1, 'Intro1', 'Message1', 'Content1', 'Description1', 'file1.jpg', '/path/to/file1', 'Major1', 'Name1', 'Nickname1', 'School1', 'http://url1.com', 'Mon-Fri', 'BACKEND', 'DEVELOPMENT'), - ('18:00:00', 5, '09:00:00', 2, 'Intro2', 'Message2', 'Content2', 'Description2', 'file2.jpg', '/path/to/file2', 'Major2', 'Name2', 'Nickname2', 'School2', 'http://url2.com', 'Mon-Fri', 'FRONTEND', 'DEVELOPMENT'), - ('18:00:00', 5, '09:00:00', 3, 'Intro3', 'Message3', 'Content3', 'Description3', 'file3.jpg', '/path/to/file3', 'Major3', 'Name3', 'Nickname3', 'School3', 'http://url3.com', 'Mon-Fri', 'DATA_SCIENTIST', 'DATA'), - ('18:00:00', 5, '09:00:00', 4, 'Intro4', 'Message4', 'Content4', 'Description4', 'file4.jpg', '/path/to/file4', 'Major4', 'Name4', 'Nickname4', 'School4', 'http://url4.com', 'Mon-Fri', 'UX_UI_DESIGN', 'DESIGN'), - ('18:00:00', 5, '09:00:00', 5, 'Intro5', 'Message5', 'Content5', 'Description5', 'file5.jpg', '/path/to/file5', 'Major5', 'Name5', 'Nickname5', 'School5', 'http://url5.com', 'Mon-Fri', 'BRAND_MARKETING', 'MARKETING'), - ('18:00:00', 5, '09:00:00', 6, 'Intro6', 'Message6', 'Content6', 'Description6', 'file6.jpg', '/path/to/file6', 'Major6', 'Name6', 'Nickname6', 'School6', 'http://url6.com', 'Mon-Fri', 'CLINICAL_DOCTOR', 'MEDICAL'), - ('18:00:00', 5, '09:00:00', 7, 'Intro7', 'Message7', 'Content7', 'Description7', 'file7.jpg', '/path/to/file7', 'Major7', 'Name7', 'Nickname7', 'School7', 'http://url7.com', 'Mon-Fri', 'LAWYER', 'LEGAL'), - ('18:00:00', 5, '09:00:00', 8, 'Intro8', 'Message8', 'Content8', 'Description8', 'file8.jpg', '/path/to/file8', 'Major8', 'Name8', 'Nickname8', 'School8', 'http://url8.com', 'Mon-Fri', 'HR_OPERATION', 'HR_SUPPORT'), - ('18:00:00', 5, '09:00:00', 9, 'Intro9', 'Message9', 'Content9', 'Description9', 'file9.jpg', '/path/to/file9', 'Major9', 'Name9', 'Nickname9', 'School9', 'http://url9.com', 'Mon-Fri', 'CONSULTANT', 'FINANCE_CONSULTING_VC'), - ('18:00:00', 5, '09:00:00', 10, 'Intro10', 'Message10', 'Content10', 'Description10', 'file10.jpg', '/path/to/file10', 'Major10', 'Name10', 'Nickname10', 'School10', 'http://url10.com', 'Mon-Fri', 'SYSTEM_NETWORK', 'DEVELOPMENT'), - ('18:00:00', 5, '09:00:00', 11, 'Intro11', 'Message11', 'Content11', 'Description11', 'file11.jpg', '/path/to/file11', 'Major11', 'Name11', 'Nickname11', 'School11', 'http://url11.com', 'Mon-Fri', 'PRODUCT_DESIGN', 'DESIGN'), - ('18:00:00', 5, '09:00:00', 12, 'Intro12', 'Message12', 'Content12', 'Description12', 'file12.jpg', '/path/to/file12', 'Major12', 'Name12', 'Nickname12', 'School12', 'http://url12.com', 'Mon-Fri', 'RECRUITER', 'HR_SUPPORT'), - ('18:00:00', 5, '09:00:00', 13, 'Intro13', 'Message13', 'Content13', 'Description13', 'file13.jpg', '/path/to/file13', 'Major13', 'Name13', 'Nickname13', 'School13', 'http://url13.com', 'Mon-Fri', 'DATA_ANALYST', 'DATA'), - ('18:00:00', 5, '09:00:00', 14, 'Intro14', 'Message14', 'Content14', 'Description14', 'file14.jpg', '/path/to/file14', 'Major14', 'Name14', 'Nickname14', 'School14', 'http://url14.com', 'Mon-Fri', 'COPYWRITER', 'MARKETING'), - ('18:00:00', 5, '09:00:00', 15, 'Intro15', 'Message15', 'Content15', 'Description15', 'file15.jpg', '/path/to/file15', 'Major15', 'Name15', 'Nickname15', 'School15', 'http://url15.com', 'Mon-Fri', 'CLINICAL_RESEARCHER', 'MEDICAL'), - ('18:00:00', 5, '09:00:00', 16, 'Intro16', 'Message16', 'Content16', 'Description16', 'file16.jpg', '/path/to/file16', 'Major16', 'Name16', 'Nickname16', 'School16', 'http://url16.com', 'Mon-Fri', 'LEGAL_ADVISOR', 'LEGAL'), - ('18:00:00', 5, '09:00:00', 17, 'Intro17', 'Message17', 'Content17', 'Description17', 'file17.jpg', '/path/to/file17', 'Major17', 'Name17', 'Nickname17', 'School17', 'http://url17.com', 'Mon-Fri', 'HR_PLANNING', 'HR_SUPPORT'), - ('18:00:00', 5, '09:00:00', 18, 'Intro18', 'Message18', 'Content18', 'Description18', 'file18.jpg', '/path/to/file18', 'Major18', 'Name18', 'Nickname18', 'School18', 'http://url18.com', 'Mon-Fri', 'STRATEGY_PLANNING', 'FINANCE_CONSULTING_VC'), - ('18:00:00', 5, '09:00:00', 19, 'Intro19', 'Message19', 'Content19', 'Description19', 'file19.jpg', '/path/to/file19', 'Major19', 'Name19', 'Nickname19', 'School19', 'http://url19.com', 'Mon-Fri', 'DEVOPS', 'DEVELOPMENT'), - ('18:00:00', 5, '09:00:00', 20, 'Intro20', 'Message20', 'Content20', 'Description20', 'file20.jpg', '/path/to/file20', 'Major20', 'Name20', 'Nickname20', 'School20', 'http://url20.com', 'Mon-Fri', 'GRAPHIC_DESIGN', 'DESIGN'), - ('18:00:00', 5, '09:00:00', 21, 'Intro21', 'Message21', 'Content21', 'Description21', 'file21.jpg', '/path/to/file21', 'Major21', 'Name21', 'Nickname21', 'School21', 'http://url21.com', 'Mon-Fri', 'TALENT_DEVELOPMENT', 'HR_SUPPORT'), - ('18:00:00', 5, '09:00:00', 22, 'Intro22', 'Message22', 'Content22', 'Description22', 'file22.jpg', '/path/to/file22', 'Major22', 'Name22', 'Nickname22', 'School22', 'http://url22.com', 'Mon-Fri', 'DATA_ARCHITECT', 'DATA'), - ('18:00:00', 5, '09:00:00', 23, 'Intro23', 'Message23', 'Content23', 'Description23', 'file23.jpg', '/path/to/file23', 'Major23', 'Name23', 'Nickname23', 'School23', 'http://url23.com', 'Mon-Fri', 'CONTENT_MARKETING', 'MARKETING'), - ('18:00:00', 5, '09:00:00', 24, 'Intro24', 'Message24', 'Content24', 'Description24', 'file24.jpg', '/path/to/file24', 'Major24', 'Name24', 'Nickname24', 'School24', 'http://url24.com', 'Mon-Fri', 'PHARMACEUTICAL_RESEARCHER', 'MEDICAL'), - ('18:00:00', 5, '09:00:00', 25, 'Intro25', 'Message25', 'Content25', 'Description25', 'file25.jpg', '/path/to/file25', 'Major25', 'Name25', 'Nickname25', 'School25', 'http://url25.com', 'Mon-Fri', 'LEGAL_COUNSEL', 'LEGAL'), - ('18:00:00', 5, '09:00:00', 26, 'Intro26', 'Message26', 'Content26', 'Description26', 'file26.jpg', '/path/to/file26', 'Major26', 'Name26', 'Nickname26', 'School26', 'http://url26.com', 'Mon-Fri', 'HR_OPERATION', 'HR_SUPPORT'), - ('18:00:00', 5, '09:00:00', 27, 'Intro27', 'Message27', 'Content27', 'Description27', 'file27.jpg', '/path/to/file27', 'Major27', 'Name27', 'Nickname27', 'School27', 'http://url27.com', 'Mon-Fri', 'CONSULTANT', 'FINANCE_CONSULTING_VC'), - ('18:00:00', 5, '09:00:00', 28, 'Intro28', 'Message28', 'Content28', 'Description28', 'file28.jpg', '/path/to/file28', 'Major28', 'Name28', 'Nickname28', 'School28', 'http://url28.com', 'Mon-Fri', 'SYSTEM_NETWORK', 'DEVELOPMENT'), - ('18:00:00', 5, '09:00:00', 29, 'Intro29', 'Message29', 'Content29', 'Description29', 'file29.jpg', '/path/to/file29', 'Major29', 'Name29', 'Nickname29', 'School29', 'http://url29.com', 'Mon-Fri', 'PRODUCT_DESIGN', 'DESIGN'), - ('18:00:00', 5, '09:00:00', 30, 'Intro30', 'Message30', 'Content30', 'Description30', 'file30.jpg', '/path/to/file30', 'Major30', 'Name30', 'Nickname30', 'School30', 'http://url30.com', 'Mon-Fri', 'RECRUITER', 'HR_SUPPORT'), - ('18:00:00', 5, '09:00:00', 31, 'Intro31', 'Message31', 'Content31', 'Description31', 'file31.jpg', '/path/to/file31', 'Major31', 'Name31', 'Nickname31', 'School31', 'http://url31.com', 'Mon-Fri', 'DATA_ANALYST', 'DATA'), - ('18:00:00', 5, '09:00:00', 32, 'Intro32', 'Message32', 'Content32', 'Description32', 'file32.jpg', '/path/to/file32', 'Major32', 'Name32', 'Nickname32', 'School32', 'http://url32.com', 'Mon-Fri', 'COPYWRITER', 'MARKETING'), - ('18:00:00', 5, '09:00:00', 33, 'Intro33', 'Message33', 'Content33', 'Description33', 'file33.jpg', '/path/to/file33', 'Major33', 'Name33', 'Nickname33', 'School33', 'http://url33.com', 'Mon-Fri', 'CLINICAL_RESEARCHER', 'MEDICAL'), - ('18:00:00', 5, '09:00:00', 34, 'Intro34', 'Message34', 'Content34', 'Description34', 'file34.jpg', '/path/to/file34', 'Major34', 'Name34', 'Nickname34', 'School34', 'http://url34.com', 'Mon-Fri', 'LEGAL_ADVISOR', 'LEGAL'), - ('18:00:00', 5, '09:00:00', 35, 'Intro35', 'Message35', 'Content35', 'Description35', 'file35.jpg', '/path/to/file35', 'Major35', 'Name35', 'Nickname35', 'School35', 'http://url35.com', 'Mon-Fri', 'HR_PLANNING', 'HR_SUPPORT'), - ('18:00:00', 5, '09:00:00', 36, 'Intro36', 'Message36', 'Content36', 'Description36', 'file36.jpg', '/path/to/file36', 'Major36', 'Name36', 'Nickname36', 'School36', 'http://url36.com', 'Mon-Fri', 'STRATEGY_PLANNING', 'FINANCE_CONSULTING_VC'), - ('18:00:00', 5, '09:00:00', 37, 'Intro37', 'Message37', 'Content37', 'Description37', 'file37.jpg', '/path/to/file37', 'Major37', 'Name37', 'Nickname37', 'School37', 'http://url37.com', 'Mon-Fri', 'DEVOPS', 'DEVELOPMENT'), - ('18:00:00', 5, '09:00:00', 38, 'Intro38', 'Message38', 'Content38', 'Description38', 'file38.jpg', '/path/to/file38', 'Major38', 'Name38', 'Nickname38', 'School38', 'http://url38.com', 'Mon-Fri', 'GRAPHIC_DESIGN', 'DESIGN'), - ('18:00:00', 5, '09:00:00', 39, 'Intro39', 'Message39', 'Content39', 'Description39', 'file39.jpg', '/path/to/file39', 'Major39', 'Name39', 'Nickname39', 'School39', 'http://url39.com', 'Mon-Fri', 'TALENT_DEVELOPMENT', 'HR_SUPPORT'), - ('18:00:00', 5, '09:00:00', 40, 'Intro40', 'Message40', 'Content40', 'Description40', 'file40.jpg', '/path/to/file40', 'Major40', 'Name40', 'Nickname40', 'School40', 'http://url40.com', 'Mon-Fri', 'DATA_ARCHITECT', 'DATA'), - ('18:00:00', 5, '09:00:00', 41, 'Intro41', 'Message41', 'Content41', 'Description41', 'file41.jpg', '/path/to/file41', 'Major41', 'Name41', 'Nickname41', 'School41', 'http://url41.com', 'Mon-Fri', 'CONTENT_MARKETING', 'MARKETING'), - ('18:00:00', 5, '09:00:00', 42, 'Intro42', 'Message42', 'Content42', 'Description42', 'file42.jpg', '/path/to/file42', 'Major42', 'Name42', 'Nickname42', 'School42', 'http://url42.com', 'Mon-Fri', 'PHARMACEUTICAL_RESEARCHER', 'MEDICAL'); + ('18:00:00',5,'09:00:00', 6,'Intro6','Message6','Content6','Desc6','file6.jpg','/path/to/file6','Major6','Name6','Nick6','School6','http://url6.com','MON','PERFORMANCE_MARKETING','MARKETING'), + ('18:00:00',5,'09:00:00', 7,'Intro7','Message7','Content7','Desc7','file7.jpg','/path/to/file7','Major7','Name7','Nick7','School7','http://url7.com','TUE','DIGITAL_SOCIAL_MARKETING','MARKETING'), + ('18:00:00',5,'09:00:00', 8,'Intro8','Message8','Content8','Desc8','file8.jpg','/path/to/file8','Major8','Name8','Nick8','School8','http://url8.com','WED','GROWTH_MARKETING','MARKETING'), + ('18:00:00',5,'09:00:00', 9,'Intro9','Message9','Content9','Desc9','file9.jpg','/path/to/file9','Major9','Name9','Nick9','School9','http://url9.com','THU','BRAND_MARKETING','MARKETING'), + ('18:00:00',5,'09:00:00',10,'Intro10','Message10','Content10','Desc10','file10.jpg','/path/to/file10','Major10','Name10','Nick10','School10','http://url10.com','FRI','PERFORMANCE_MARKETING','MARKETING'), + + ('18:00:00',5,'09:00:00',11,'Intro11','Message11','Content11','Desc11','file11.jpg','/path/to/file11','Major11','Name11','Nick11','School11','http://url11.com','MON','DIGITAL_SOCIAL_MARKETING','MARKETING'), + ('18:00:00',5,'09:00:00',12,'Intro12','Message12','Content12','Desc12','file12.jpg','/path/to/file12','Major12','Name12','Nick12','School12','http://url12.com','TUE','GROWTH_MARKETING','MARKETING'), + ('18:00:00',5,'09:00:00',13,'Intro13','Message13','Content13','Desc13','file13.jpg','/path/to/file13','Major13','Name13','Nick13','School13','http://url13.com','WED','BRAND_MARKETING','MARKETING'), + ('18:00:00',5,'09:00:00',14,'Intro14','Message14','Content14','Desc14','file14.jpg','/path/to/file14','Major14','Name14','Nick14','School14','http://url14.com','THU','PERFORMANCE_MARKETING','MARKETING'), + ('18:00:00',5,'09:00:00',15,'Intro15','Message15','Content15','Desc15','file15.jpg','/path/to/file15','Major15','Name15','Nick15','School15','http://url15.com','FRI','DIGITAL_SOCIAL_MARKETING','MARKETING'), + + ('18:00:00',5,'09:00:00',16,'Intro16','Message16','Content16','Desc16','file16.jpg','/path/to/file16','Major16','Name16','Nick16','School16','http://url16.com','MON','GROWTH_MARKETING','MARKETING'), + ('18:00:00',5,'09:00:00',17,'Intro17','Message17','Content17','Desc17','file17.jpg','/path/to/file17','Major17','Name17','Nick17','School17','http://url17.com','TUE','BRAND_MARKETING','MARKETING'), + ('18:00:00',5,'09:00:00',18,'Intro18','Message18','Content18','Desc18','file18.jpg','/path/to/file18','Major18','Name18','Nick18','School18','http://url18.com','WED','PERFORMANCE_MARKETING','MARKETING'), + ('18:00:00',5,'09:00:00',19,'Intro19','Message19','Content19','Desc19','file19.jpg','/path/to/file19','Major19','Name19','Nick19','School19','http://url19.com','THU','DIGITAL_SOCIAL_MARKETING','MARKETING'), + ('18:00:00',5,'09:00:00',20,'Intro20','Message20','Content20','Desc20','file20.jpg','/path/to/file20','Major20','Name20','Nick20','School20','http://url20.com','FRI','GROWTH_MARKETING','MARKETING'), + + ('18:00:00',5,'09:00:00',21,'Intro21','Message21','Content21','Desc21','file21.jpg','/path/to/file21','Major21','Name21','Nick21','School21','http://url21.com','MON','BRAND_MARKETING','MARKETING'), + ('18:00:00',5,'09:00:00',22,'Intro22','Message22','Content22','Desc22','file22.jpg','/path/to/file22','Major22','Name22','Nick22','School22','http://url22.com','TUE','PERFORMANCE_MARKETING','MARKETING'), + ('18:00:00',5,'09:00:00',23,'Intro23','Message23','Content23','Desc23','file23.jpg','/path/to/file23','Major23','Name23','Nick23','School23','http://url23.com','WED','DIGITAL_SOCIAL_MARKETING','MARKETING'), + ('18:00:00',5,'09:00:00',24,'Intro24','Message24','Content24','Desc24','file24.jpg','/path/to/file24','Major24','Name24','Nick24','School24','http://url24.com','THU','GROWTH_MARKETING','MARKETING'), + ('18:00:00',5,'09:00:00',25,'Intro25','Message25','Content25','Desc25','file25.jpg','/path/to/file25','Major25','Name25','Nick25','School25','http://url25.com','FRI','BRAND_MARKETING','MARKETING'), + + ('18:00:00',5,'09:00:00',26,'Intro26','Message26','Content26','Desc26','file26.jpg','/path/to/file26','Major26','Name26','Nick26','School26','http://url26.com','MON','PERFORMANCE_MARKETING','MARKETING'), + ('18:00:00',5,'09:00:00',27,'Intro27','Message27','Content27','Desc27','file27.jpg','/path/to/file27','Major27','Name27','Nick27','School27','http://url27.com','TUE','DIGITAL_SOCIAL_MARKETING','MARKETING'), + ('18:00:00',5,'09:00:00',28,'Intro28','Message28','Content28','Desc28','file28.jpg','/path/to/file28','Major28','Name28','Nick28','School28','http://url28.com','WED','GROWTH_MARKETING','MARKETING'), + ('18:00:00',5,'09:00:00',29,'Intro29','Message29','Content29','Desc29','file29.jpg','/path/to/file29','Major29','Name29','Nick29','School29','http://url29.com','THU','BRAND_MARKETING','MARKETING'), + ('18:00:00',5,'09:00:00',30,'Intro30','Message30','Content30','Desc30','file30.jpg','/path/to/file30','Major30','Name30','Nick30','School30','http://url30.com','FRI','PERFORMANCE_MARKETING','MARKETING'), + + ('18:00:00',5,'09:00:00',31,'Intro31','Message31','Content31','Desc31','file31.jpg','/path/to/file31','Major31','Name31','Nick31','School31','http://url31.com','MON','DIGITAL_SOCIAL_MARKETING','MARKETING'), + ('18:00:00',5,'09:00:00',32,'Intro32','Message32','Content32','Desc32','file32.jpg','/path/to/file32','Major32','Name32','Nick32','School32','http://url32.com','TUE','GROWTH_MARKETING','MARKETING'), + ('18:00:00',5,'09:00:00',33,'Intro33','Message33','Content33','Desc33','file33.jpg','/path/to/file33','Major33','Name33','Nick33','School33','http://url33.com','WED','BRAND_MARKETING','MARKETING'), + ('18:00:00',5,'09:00:00',34,'Intro34','Message34','Content34','Desc34','file34.jpg','/path/to/file34','Major34','Name34','Nick34','School34','http://url34.com','THU','PERFORMANCE_MARKETING','MARKETING'), + ('18:00:00',5,'09:00:00',35,'Intro35','Message35','Content35','Desc35','file35.jpg','/path/to/file35','Major35','Name35','Nick35','School35','http://url35.com','FRI','DIGITAL_SOCIAL_MARKETING','MARKETING'), + + ('18:00:00',5,'09:00:00',36,'Intro36','Message36','Content36','Desc36','file36.jpg','/path/to/file36','Major36','Name36','Nick36','School36','http://url36.com','MON','GROWTH_MARKETING','MARKETING'), + ('18:00:00',5,'09:00:00',37,'Intro37','Message37','Content37','Desc37','file37.jpg','/path/to/file37','Major37','Name37','Nick37','School37','http://url37.com','TUE','BRAND_MARKETING','MARKETING'), + ('18:00:00',5,'09:00:00',38,'Intro38','Message38','Content38','Desc38','file38.jpg','/path/to/file38','Major38','Name38','Nick38','School38','http://url38.com','WED','PERFORMANCE_MARKETING','MARKETING'), + ('18:00:00',5,'09:00:00',39,'Intro39','Message39','Content39','Desc39','file39.jpg','/path/to/file39','Major39','Name39','Nick39','School39','http://url39.com','THU','DIGITAL_SOCIAL_MARKETING','MARKETING'), + ('18:00:00',5,'09:00:00',40,'Intro40','Message40','Content40','Desc40','file40.jpg','/path/to/file40','Major40','Name40','Nick40','School40','http://url40.com','FRI','GROWTH_MARKETING','MARKETING'), + + ('18:00:00',5,'09:00:00',41,'Intro41','Message41','Content41','Desc41','file41.jpg','/path/to/file41','Major41','Name41','Nick41','School41','http://url41.com','MON','BRAND_MARKETING','MARKETING'), + ('18:00:00',5,'09:00:00',42,'Intro42','Message42','Content42','Desc42','file42.jpg','/path/to/file42','Major42','Name42','Nick42','School42','http://url42.com','TUE','PERFORMANCE_MARKETING','MARKETING'), + ('18:00:00',5,'09:00:00',43,'Intro43','Message43','Content43','Desc43','file43.jpg','/path/to/file43','Major43','Name43','Nick43','School43','http://url43.com','WED','DIGITAL_SOCIAL_MARKETING','MARKETING'), + ('18:00:00',5,'09:00:00',44,'Intro44','Message44','Content44','Desc44','file44.jpg','/path/to/file44','Major44','Name44','Nick44','School44','http://url44.com','THU','GROWTH_MARKETING','MARKETING'), + ('18:00:00',5,'09:00:00',45,'Intro45','Message45','Content45','Desc45','file45.jpg','/path/to/file45','Major45','Name45','Nick45','School45','http://url45.com','FRI','BRAND_MARKETING','MARKETING'), + + ('18:00:00',5,'09:00:00',46,'Intro46','Message46','Content46','Desc46','file46.jpg','/path/to/file46','Major46','Name46','Nick46','School46','http://url46.com','MON','PERFORMANCE_MARKETING','MARKETING'), + ('18:00:00',5,'09:00:00',47,'Intro47','Message47','Content47','Desc47','file47.jpg','/path/to/file47','Major47','Name47','Nick47','School47','http://url47.com','TUE','DIGITAL_SOCIAL_MARKETING','MARKETING'), + ('18:00:00',5,'09:00:00',48,'Intro48','Message48','Content48','Desc48','file48.jpg','/path/to/file48','Major48','Name48','Nick48','School48','http://url48.com','WED','GROWTH_MARKETING','MARKETING'), + ('18:00:00',5,'09:00:00',49,'Intro49','Message49','Content49','Desc49','file49.jpg','/path/to/file49','Major49','Name49','Nick49','School49','http://url49.com','THU','BRAND_MARKETING','MARKETING'), + ('18:00:00',5,'09:00:00',50,'Intro50','Message50','Content50','Desc50','file50.jpg','/path/to/file50','Major50','Name50','Nick50','School50','http://url50.com','FRI','PERFORMANCE_MARKETING','MARKETING'); INSERT INTO mentee (member_id, introduction, file_name, file_path, major, nickname, school_name) VALUES (51, 'Intro51', 'file51.jpg', 'path/to/file51', 'Major51', 'Name51', 'School51'), diff --git a/src/main/resources/db/migration/V5__fix_mentoring_enum.sql b/src/main/resources/db/migration/V5__fix_mentoring_enum.sql new file mode 100644 index 0000000..0e53b70 --- /dev/null +++ b/src/main/resources/db/migration/V5__fix_mentoring_enum.sql @@ -0,0 +1,17 @@ +ALTER TABLE mentor_mentoring_fields + MODIFY COLUMN mentoring_field + ENUM( + 'JOB_PREPARATION', + 'INTERVIEW_PREPARATION', + 'INDUSTRY_TRENDS', + 'CAREER_PATH' +) NOT NULL; + +ALTER TABLE mentoring + MODIFY COLUMN mentoring_topic + ENUM( + 'JOB_PREPARATION', + 'INTERVIEW_PREPARATION', + 'INDUSTRY_TRENDS', + 'CAREER_PATH' +) NOT NULL; \ No newline at end of file diff --git a/src/main/resources/db/migration/V6__fix_mentoring_status.sql b/src/main/resources/db/migration/V6__fix_mentoring_status.sql new file mode 100644 index 0000000..5925c68 --- /dev/null +++ b/src/main/resources/db/migration/V6__fix_mentoring_status.sql @@ -0,0 +1,9 @@ +ALTER TABLE mentoring + MODIFY COLUMN mentoring_status ENUM ( + 'WAITING', + 'ACCEPTED', + 'REJECTED', + 'CANCELLED_BEFORE_PAYMENT', + 'CANCELLED_AFTER_PAYMENT', + 'COMPLETED' + ) NOT NULL; \ No newline at end of file diff --git a/src/main/resources/db/migration/V7__fix_mentor_category.sql b/src/main/resources/db/migration/V7__fix_mentor_category.sql new file mode 100644 index 0000000..46c07ca --- /dev/null +++ b/src/main/resources/db/migration/V7__fix_mentor_category.sql @@ -0,0 +1,33 @@ +ALTER TABLE mentor + MODIFY job_category ENUM( + 'DATA','DESIGN','DEVELOPMENT', + 'FINANCE','FINANCE_CONSULTING_VC', + 'GRADUATE_SCHOOL','HR_SUPPORT','LEGAL', + 'MARKETING','MEDICAL','SALES_CUSTOMER','SERVICE_PLANNING' + ); + +ALTER TABLE mentor + MODIFY detailed_job ENUM( + 'ACCOUNTING_FINANCE','AE','ANALYST','ART_DIRECTOR', + 'B2B_SALES','B2C_SALES','BACKEND','BIO_RESEARCHER','BI_ENGINEER', + 'BRAND_DESIGN','BRAND_MARKETING','BROADCAST_PD','BUSINESS_DEVELOPMENT', + 'CLINICAL_DOCTOR','CLINICAL_RESEARCHER','CLOUD','COMPLIANCE','CONSULTANT', + 'CONTENT_MARKETING','COPYWRITER','CREATIVE_DIRECTING','CSM_CX','CX_MANAGER', + 'DATA_ANALYST','DATA_ARCHITECT','DATA_ENGINEER','DATA_SCIENTIST','DEVOPS', + 'DIGITAL_SOCIAL_MARKETING','DOMESTIC_GRADUATE_SCHOOL','FRONTEND','FULLSTACK', + 'GENERAL_AFFAIRS','GRAPHIC_DESIGN','GROWTH_MARKETING','HR_OPERATION', + 'HR_PLANNING','IB_PE_ALTERNATIVE_INVESTMENT','IOS_ANDROID','KAM','LABOR', + 'LAWYER','LAW_FIRM_STAFF','LEGAL_ADVISOR','LEGAL_COUNSEL', + 'MACHINE_LEARNING_ENGINEER','MEDIA_PLANNER','MEDICAL_DEVICE_RND', + 'OPERATION_PLANNING','ORGANIZATION_CULTURE', + 'OTHER_DATA','OTHER_DESIGN','OTHER_DEVELOPMENT', + 'OTHER_FINANCE','OTHER_FINANCE_CONSULTING_VC', + 'OTHER_GRADUATE_SCHOOL','OTHER_HR_SUPPORT','OTHER_LEGAL','OTHER_MARKETING', + 'OTHER_MEDICAL','OTHER_SALES_CUSTOMER','OTHER_SERVICE_PLANNING', + 'OVERSEAS_GRADUATE_SCHOOL','OVERSEAS_SALES','PATENT','PATENT_ENGINEER', + 'PERFORMANCE_MARKETING','PHARMACEUTICAL_RESEARCHER','PM_PO','PR', + 'PRODUCT_DESIGN','RECRUITER','RECRUITMENT','RESEARCH_ANALYST', + 'SALES_SUPPORT','SECURITY','SERVICE_PLANNING','SOLUTION_CONSULTANT', + 'STARTUP','STRATEGY_PLANNING','SYSTEM_NETWORK','TALENT_DEVELOPMENT', + 'TECHNICAL_SALES','UX_UI_DESIGN','VC_INVESTMENT','WEB_DESIGN' + ); \ No newline at end of file diff --git a/src/main/resources/db/migration/V8__fix_file_schema.sql b/src/main/resources/db/migration/V8__fix_file_schema.sql new file mode 100644 index 0000000..45f8ee0 --- /dev/null +++ b/src/main/resources/db/migration/V8__fix_file_schema.sql @@ -0,0 +1,14 @@ +-- mentor ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ๋ช… ๋ณ€๊ฒฝ ๋ฐ ์ปฌ๋Ÿผ ์ถ”๊ฐ€ +ALTER TABLE mentor + CHANGE COLUMN file_name portfolio_file_name VARCHAR(255) NOT NULL, + CHANGE COLUMN file_path portfolio_file_path VARCHAR(255) NOT NULL, + ADD COLUMN profile_image_file_name VARCHAR(255), + ADD COLUMN profile_image_file_path VARCHAR(255), + ADD COLUMN portfolio_file_size INT, + ADD COLUMN profile_image_file_size INT; + +-- mentee ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ๋ช… ๋ณ€๊ฒฝ ๋ฐ ์ปฌ๋Ÿผ ์ถ”๊ฐ€ +ALTER TABLE mentee + CHANGE COLUMN file_name profile_image_file_name VARCHAR(255) NOT NULL, + CHANGE COLUMN file_path profile_image_file_path VARCHAR(255) NOT NULL, + ADD COLUMN profile_image_file_size INT; \ No newline at end of file diff --git a/src/main/resources/db/migration/V9__create_notification.sql b/src/main/resources/db/migration/V9__create_notification.sql new file mode 100644 index 0000000..76798f7 --- /dev/null +++ b/src/main/resources/db/migration/V9__create_notification.sql @@ -0,0 +1,21 @@ +CREATE TABLE notification ( + notification_id BIGINT NOT NULL AUTO_INCREMENT, + title VARCHAR(255) NOT NULL, + content VARCHAR(500) NOT NULL, + type ENUM ( + 'MENTORING_REQUEST', + 'MENTORING_APPROVED', + 'MENTORING_REJECTED' + ) NOT NULL, + is_read TINYINT(1) NOT NULL DEFAULT 0, + sender_id BIGINT NOT NULL, + receiver_id BIGINT NOT NULL, + created_at DATETIME(6), + updated_at DATETIME(6), + + PRIMARY KEY (notification_id), + CONSTRAINT fk_notification_sender + FOREIGN KEY (sender_id) REFERENCES member(member_id) ON DELETE CASCADE, + CONSTRAINT fk_notification_receiver + FOREIGN KEY (receiver_id) REFERENCES member(member_id) ON DELETE CASCADE +); \ No newline at end of file