Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
9c96d6e
refactor: 신청 마감일 필드 제거
sooooooobinn Aug 28, 2026
f027798
refactor: 신청 마감일 로직 수정 및 서비스 로직 분리
sooooooobinn Aug 28, 2026
6c726fd
refactor: test 코드 수정
sooooooobinn Aug 28, 2026
4641dfb
refactor: 주석 제거
sooooooobinn Aug 28, 2026
4fe4409
refactor: 불필요한 필드 제거
sooooooobinn Aug 28, 2026
49c80eb
refactor: ci 수정 및 test 코드 분리
sooooooobinn Sep 1, 2026
f59a59a
refactor: ci 수정
sooooooobinn Sep 1, 2026
4d9a0d0
refactor: ci 수정
sooooooobinn Sep 1, 2026
14f443f
Merge pull request #40 from VOID-GSM/feat/lecture-time
sooooooobinn Sep 1, 2026
0b40a1a
feat: 강연 승인 시간 필드 추가 및 조회 시 포함
sooooooobinn Sep 1, 2026
910f1a0
Merge pull request #41 from VOID-GSM/feature/lecture-approval-timestamp
sooooooobinn Sep 1, 2026
0058a87
feat: 연사자 필드 추가
sooooooobinn Sep 1, 2026
13fcac2
feat: dto에 연사자 필드 추가
sooooooobinn Sep 1, 2026
994dbb5
feat: dto에 강연 생성자 필드 추가
sooooooobinn Sep 1, 2026
44d7d20
feat: LectureSpeakerResponse 추가
sooooooobinn Sep 1, 2026
f9512db
feat: 연사자 필드 추가
sooooooobinn Sep 1, 2026
d811570
feat: 연사자 추가 로직 및 관련 예외 로직 추가
sooooooobinn Sep 1, 2026
90f5719
refactor: test 코드 수정
sooooooobinn Sep 1, 2026
be500df
Merge pull request #42 from VOID-GSM/feat/speaker
sooooooobinn Sep 1, 2026
26da83d
feat: 대기 신청 승인 요청 DTO 추가
sooooooobinn Sep 1, 2026
0447d9f
feat: 대기 신청 거절 상태 추가
sooooooobinn Sep 1, 2026
c07ba4c
feat: 대기 신청 수락 및 거절 처리 추가
sooooooobinn Sep 1, 2026
5001d7d
feat: 대기 거절 목록 응답 추가
sooooooobinn Sep 1, 2026
0c4a84b
feat: 마감 후 신청 허용 처리 추가
sooooooobinn Sep 1, 2026
cb9fa62
feat: 대기 승인 및 수동 인원 관리 추가
sooooooobinn Sep 1, 2026
45775c3
feat: 대기 신청 승인 API 추가
sooooooobinn Sep 1, 2026
aba42c5
test: 대기 승인 및 마감 후 신청 검증 추가
sooooooobinn Sep 1, 2026
91b62bb
feat: 리프레시 토큰 API 추가
sooooooobinn Sep 1, 2026
6e14e9a
Merge pull request #44 from VOID-GSM/feat/auth-refresh
sooooooobinn Sep 1, 2026
fddff00
refactor: 최대 인원 30명 제한 제거
sooooooobinn Sep 1, 2026
7222adb
refactor: test 코드 수정
sooooooobinn Sep 1, 2026
1047005
Merge pull request #43 from VOID-GSM/feat/speaker
sooooooobinn Sep 1, 2026
dc950a1
Merge branch 'develop' of https://github.com/VOID-GSM/Rels-back into …
sooooooobinn Sep 1, 2026
f3fd5b3
Merge pull request #45 from VOID-GSM/feat/lecture-capacity-limit
sooooooobinn Sep 1, 2026
a5a870a
feat: 사용자 검색 API 추가 및 신청자 명단 공개 범위 조정
jyuuuuu0 Sep 1, 2026
9e9e923
feat: 신청 마감 시각을 개설 요청에서 다시 입력받도록 변경
jyuuuuu0 Sep 1, 2026
6aa03af
feat: 승인 시각 기준 신청 오픈 시간 적용
sooooooobinn Sep 2, 2026
782f6bb
merge: 연사 및 대기 신청 변경 충돌 해결
sooooooobinn Sep 2, 2026
370c3d2
Merge pull request #49 from VOID-GSM/feat/application-open-time
sooooooobinn Sep 2, 2026
0a5e20b
feat: 신청 마감일 필드 추가
sooooooobinn Sep 2, 2026
802b8a8
refactor: 신청 마감일 필드 추가 및 관련 예외 처리 로직 추가
sooooooobinn Sep 2, 2026
00f4220
refactor: test 코드 수정
sooooooobinn Sep 2, 2026
8d61499
Merge pull request #50 from VOID-GSM/feat/lecture-time
sooooooobinn Sep 2, 2026
de289b4
merge: develop의 신청 마감일 구현으로 충돌 해결
jyuuuuu0 Sep 3, 2026
9755cbb
Merge pull request #46 from VOID-GSM/feat/user-search-and-enrollment-…
jyuuuuu0 Sep 3, 2026
a502366
merge: main의 핫픽스를 develop에 반영
jyuuuuu0 Sep 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import com.example.rels.domain.auth.dto.CurrentUserResponse;
import com.example.rels.domain.auth.dto.OAuthSignInRequest;
import com.example.rels.domain.auth.dto.OAuthSignInResponse;
import com.example.rels.domain.auth.dto.RefreshTokenRequest;
import com.example.rels.domain.auth.service.AuthService;
import com.example.rels.domain.auth.service.DgOAuthFlowService;
import com.example.rels.global.security.AuthenticatedUser;
Expand Down Expand Up @@ -47,6 +48,11 @@ public OAuthSignInResponse signIn(@Valid @RequestBody OAuthSignInRequest request
return authService.signIn(request);
}

@PostMapping("/refresh")
public OAuthSignInResponse refresh(@Valid @RequestBody RefreshTokenRequest request) {
return authService.refresh(request);
}

@GetMapping("/me")
public CurrentUserResponse me(@AuthenticationPrincipal AuthenticatedUser currentUser) {
if (currentUser == null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.example.rels.domain.lecture.controller;

import com.example.rels.domain.lecture.dto.request.AttendanceUpdateRequest;
import com.example.rels.domain.lecture.dto.request.EnrollmentDecisionRequest;
import com.example.rels.domain.lecture.dto.request.LectureApprovalRequest;
import com.example.rels.domain.lecture.dto.request.LectureCreateRequest;
import com.example.rels.domain.lecture.dto.request.LectureUpdateRequest;
Expand Down Expand Up @@ -122,8 +123,21 @@ public EnrollmentResponse cancelEnrollment(
}

@GetMapping("/{lectureId}/enrollments")
public EnrollmentListResponse getEnrollments(@PathVariable Long lectureId) {
return lectureService.getEnrollments(lectureId);
public EnrollmentListResponse getEnrollments(
@PathVariable Long lectureId,
@AuthenticationPrincipal AuthenticatedUser currentUser) {
AuthenticatedUser authenticatedUser = requireUser(currentUser);
return lectureService.getEnrollments(lectureId, authenticatedUser.userId(), authenticatedUser.role());
}

@PatchMapping("/{lectureId}/enrollments/{userId}/decision")
public EnrollmentResponse decideWaitingEnrollment(
@PathVariable Long lectureId,
@PathVariable Long userId,
@AuthenticationPrincipal AuthenticatedUser currentUser,
@Valid @RequestBody EnrollmentDecisionRequest request) {
AuthenticatedUser authenticatedUser = requireUser(currentUser);
return lectureService.decideWaitingEnrollment(lectureId, userId, authenticatedUser.userId(), authenticatedUser.role(), request);
}

@GetMapping("/enrollments/me")
Expand Down Expand Up @@ -156,4 +170,4 @@ private AuthenticatedUser requireUser(AuthenticatedUser currentUser) {
}
return currentUser;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.example.rels.domain.lecture.dto.request;

import jakarta.validation.constraints.NotNull;

public record EnrollmentDecisionRequest(
@NotNull Boolean approved
) {
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.example.rels.domain.lecture.dto.request;



import jakarta.validation.constraints.Future;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
Expand All @@ -11,16 +10,17 @@
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Map;

import java.util.Set;

public record LectureCreateRequest(
@NotBlank @Size(max = 100) String title,
@NotBlank @Size(max = 800) String description,
Map<@NotNull @Min(1) @Max(3) Integer, @NotNull @Min(0) Integer> capacityByGrade,
Integer totalCapacity,
@NotBlank @Size(max = 255) String lectureLocation,
@NotNull LocalDate lectureDate,
@NotNull LocalTime lectureTime,
@NotNull LocalDateTime applicationDeadline
@NotBlank @Size(max = 100) String title,
@NotBlank @Size(max = 800) String description,
Map<@NotNull @Min(1) @Max(3) Integer, @NotNull @Min(0) Integer> capacityByGrade,
Integer totalCapacity,
@NotBlank @Size(max = 255) String lectureLocation,
@NotNull LocalDate lectureDate,
@NotNull LocalTime lectureTime,
@NotNull @Future(message = "신청 마감 시각은 현재보다 미래여야 합니다.") LocalDateTime applicationDeadline,
Set<@NotNull Long> speakerIds
) {
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package com.example.rels.domain.lecture.dto.request;



import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
Expand All @@ -11,16 +9,17 @@
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Map;

import java.util.Set;

public record LectureUpdateRequest(
@NotBlank @Size(max = 100) String title,
@NotBlank @Size(max = 800) String description,
Map<@NotNull @Min(1) @Max(3) Integer, @NotNull @Min(0) Integer> capacityByGrade,
Integer totalCapacity,
@NotBlank @Size(max = 255) String lectureLocation,
@NotNull LocalDate lectureDate,
@NotNull LocalTime lectureTime,
@NotNull LocalDateTime applicationDeadline
@NotBlank @Size(max = 100) String title,
@NotBlank @Size(max = 800) String description,
Map<@NotNull @Min(1) @Max(3) Integer, @NotNull @Min(0) Integer> capacityByGrade,
Integer totalCapacity,
@NotBlank @Size(max = 255) String lectureLocation,
@NotNull LocalDate lectureDate,
@NotNull LocalTime lectureTime,
@NotNull LocalDateTime applicationDeadline,
Set<@NotNull Long> speakerIds
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

public record EnrollmentListResponse(
List<EnrollmentUserResponse> enrolled,
List<EnrollmentUserResponse> waiting
List<EnrollmentUserResponse> waiting,
List<EnrollmentUserResponse> rejected
) {}

Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Map;
import java.util.List;



Expand All @@ -15,6 +16,7 @@ public record LectureDetailResponse(
Long creatorId,
String creatorName,
String creatorStudentNumber,
List<LectureSpeakerResponse> speakers,
String lectureStatus,
String approvalStatus,
String rejectionReason,
Expand All @@ -26,6 +28,7 @@ public record LectureDetailResponse(
LocalTime lectureTime,
LocalDateTime applicationDeadline,
LocalDateTime createdAt,
LocalDateTime approvedAt,
Map<Integer, Integer> capacityByGrade,
Integer totalCapacity
) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.example.rels.domain.lecture.dto.response;

public record LectureSpeakerResponse(
Long userId,
String name,
String studentNumber
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Map;
import java.util.List;


public record LectureSummaryResponse(
Expand All @@ -13,6 +14,7 @@ public record LectureSummaryResponse(
Long creatorId,
String creatorName,
String creatorStudentNumber,
List<LectureSpeakerResponse> speakers,
String lectureStatus,
String approvalStatus,
String rejectionReason,
Expand All @@ -23,6 +25,7 @@ public record LectureSummaryResponse(
LocalTime lectureTime,
LocalDateTime applicationDeadline,
LocalDateTime createdAt,
LocalDateTime approvedAt,
Map<Integer, Integer> capacityByGrade,
Integer totalCapacity
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
public record MyCreatedLectureResponse(
Long lectureId,
String title,
boolean creator,
String lectureStatus,
String approvalStatus,
String rejectionReason,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

public enum EnrollmentStatus {
ENROLLED,
WAITING
WAITING,
REJECTED
}

Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ public void promoteToEnrolled() {
this.status = EnrollmentStatus.ENROLLED;
}

public void reject() {
this.status = EnrollmentStatus.REJECTED;
}

@Enumerated(EnumType.STRING)
@Column(nullable = false)
private AttendanceStatus attendanceStatus = AttendanceStatus.NONE;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
package com.example.rels.domain.lecture.entity;

import java.util.HashMap;
import java.util.Map;
import jakarta.persistence.ElementCollection;
import jakarta.persistence.CollectionTable;
import jakarta.persistence.MapKeyColumn;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;

import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;

import com.example.rels.domain.user.entity.UserEntity;

import jakarta.persistence.CollectionTable;
import jakarta.persistence.Column;
import jakarta.persistence.ElementCollection;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
Expand All @@ -24,12 +24,16 @@
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.JoinTable;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.MapKeyColumn;
import jakarta.persistence.Table;

@Entity
@Table(name = "lectures")
public class LectureEntity {

@ElementCollection
@CollectionTable(name = "lecture_capacity_by_grade", joinColumns = @JoinColumn(name = "lecture_id"))
@MapKeyColumn(name = "grade")
Expand All @@ -50,6 +54,12 @@ public class LectureEntity {
@JoinColumn(name = "creator_id", nullable = false)
private UserEntity creator;

@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(name = "lecture_speakers",
joinColumns = @JoinColumn(name = "lecture_id"),
inverseJoinColumns = @JoinColumn(name = "user_id"))
private Set<UserEntity> speakers = new LinkedHashSet<>();

@Enumerated(EnumType.STRING)
@Column(nullable = false)
private LectureStatus status;
Expand All @@ -63,7 +73,7 @@ public class LectureEntity {
@Column(name = "lecture_time")
private LocalTime lectureTime;

@Column(name = "application_deadline", nullable = false)
@Column(name = "application_deadline")
private LocalDateTime applicationDeadline;

@Column(name = "total_capacity")
Expand All @@ -84,13 +94,17 @@ public class LectureEntity {
@Column(name = "rejection_reason")
private String rejectionReason;

@Column(name = "approved_at")
private LocalDateTime approvedAt;

protected LectureEntity() {
}

public LectureEntity(String title, String description, UserEntity creator, String lectureLocation, LocalDate lectureDate, LocalTime lectureTime, LocalDateTime applicationDeadline, Integer totalCapacity) {
this.title = title;
this.description = description;
this.creator = creator;
this.speakers.add(creator);
this.status = LectureStatus.OPEN;
this.capacityByGrade = new HashMap<>();
this.lectureLocation = lectureLocation;
Expand Down Expand Up @@ -134,6 +148,22 @@ public UserEntity getCreator() {
return creator;
}

public Set<UserEntity> getSpeakers() {
return Set.copyOf(speakers);
}

public boolean isSpeaker(Long userId) {
return userId != null && speakers.stream().anyMatch(speaker -> speaker.getId().equals(userId));
}

public void updateSpeakers(Set<UserEntity> speakers) {
this.speakers.clear();
this.speakers.add(creator);
if (speakers != null) {
this.speakers.addAll(speakers);
}
}

public LectureStatus getStatus() {
return status;
}
Expand Down Expand Up @@ -205,8 +235,15 @@ public String getRejectionReason() {
return rejectionReason;
}

public LocalDateTime getApprovedAt() {
return approvedAt;
}

public void updateApprovalStatus(ApprovalStatus approvalStatus, String rejectionReason) {
this.approvalStatus = approvalStatus;
this.rejectionReason = rejectionReason;
if (approvalStatus == ApprovalStatus.APPROVED) {
this.approvedAt = LocalDateTime.now();
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,21 @@

public interface LectureRepository extends JpaRepository<LectureEntity, Long> {

@EntityGraph(attributePaths = "creator")
@EntityGraph(attributePaths = {"creator", "speakers"})
Page<LectureEntity> findAllByOrderByCreatedAtDesc(Pageable pageable);

@EntityGraph(attributePaths = "creator")
@EntityGraph(attributePaths = {"creator", "speakers"})
Page<LectureEntity> findAllByApprovalStatusOrderByCreatedAtDesc(ApprovalStatus approvalStatus, Pageable pageable);

// 승인된 강연 + 조회한 본인이 개설한 강연(승인 대기/거절 포함)
@EntityGraph(attributePaths = "creator")
Page<LectureEntity> findAllByApprovalStatusOrCreatorIdOrderByCreatedAtDesc(ApprovalStatus approvalStatus, Long creatorId, Pageable pageable);
@EntityGraph(attributePaths = {"creator", "speakers"})
@Query("select distinct l from LectureEntity l left join l.speakers s where l.approvalStatus = :approvalStatus or l.creator.id = :userId or s.id = :userId")
Page<LectureEntity> findVisibleToUser(ApprovalStatus approvalStatus, Long userId, Pageable pageable);

@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("select l from LectureEntity l where l.id = :lectureId")
Optional<LectureEntity> findByIdForUpdate(Long lectureId);

List<LectureEntity> findAllByCreatorIdOrderByCreatedAtDesc(Long creatorId);
}
@EntityGraph(attributePaths = {"creator", "speakers"})
@Query("select distinct l from LectureEntity l join l.speakers s where s.id = :userId order by l.createdAt desc")
List<LectureEntity> findAllBySpeakerIdOrderByCreatedAtDesc(Long userId);
}
Loading
Loading