Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
0bbcf06
refactor: 학생회 게시글 목록 조회 기능을 학생회 타입별 제휴/행사 목록 조회 기능으로 수정(학생회 타입, 게시글카테…
1winhyun Jan 7, 2026
fd3ba18
refactor: 72시간 이내 행사 조회 기능을 학생회 타입별 필터링 되도록 수정
1winhyun Jan 7, 2026
4043201
refactor: 학생회 타입별 게시글 목록 조회 로직 행사면 행사날, 제휴면 끝나는 날이 가까운 순서로 정렬되게 수정
1winhyun Jan 7, 2026
a938340
refactor: 학생회 타입별 게시글 목록 조회 아직 지나지 않은 제휴나 행사만 조회되도록 수정
1winhyun Jan 7, 2026
74ba3b0
feat: 학생회 타입별 이용 가능한 제휴 목록 조회 기능 구현(일반 유저 전용, 홈 화면)
1winhyun Jan 7, 2026
fa7d37f
feat: 카카오 이름 외 서비스 내 닉네임 설정이 가능하도록 컬럼 및 로직 추가 (닉네임 변경 구현)
1winhyun Jan 7, 2026
3fce97f
feat: 사용자 정보 조회 기능 구현(홈 화면에 유저 정보 노출을 위함)
1winhyun Jan 7, 2026
4d8b8f5
refactor: CouncilType 필터링 수정
1winhyun Jan 7, 2026
42d2727
refactor: UserWithdrawRequest 필드명 수정
1winhyun Jan 7, 2026
23b9d9c
refactor: User 내 컬럼명 하이픈 수정
1winhyun Jan 8, 2026
d8ee8e3
refactor: getUserInfo 삭제된 사용자 필터링
1winhyun Jan 8, 2026
0161c88
refactor: 닉네임 변경 예외처리 관련 수정
1winhyun Jan 8, 2026
1dee857
refactor: dto 필드 명명법 camelCase 수정
1winhyun Jan 8, 2026
5d2466b
refactor: conflict(충돌) 해결
1winhyun Jan 9, 2026
36b30ba
refactor: 유저전용 학생회 게시글 조회 service,controller 클래스명 변경 및 앤드포인트 변경, 트랜잭션…
1winhyun Jan 9, 2026
49e67a7
refactor: 사용하지 않는 의존성 제거 및 타임존 추가
1winhyun Jan 9, 2026
843d1d0
refactor: findActivePartnershipForUser 사용자 조회 메서드 수정
1winhyun Jan 9, 2026
887371a
refactor: 학생회 제휴/행사 목록 조회 기능 학생회 타입 필터링 삭제 및 로그인한 본인이 작성한 글만 조회되도록 수정
1winhyun Jan 9, 2026
ef6943d
refactor: 학생회 72시간 이내 행사 조회 기능 학생회 타입 필터링 삭제 및 로그인한 본인이 작성한 글만 조회되도록 수정
1winhyun Jan 9, 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
@@ -0,0 +1,18 @@
package com.campus.campus.domain.councilpost.application.dto.response;

import io.swagger.v3.oas.annotations.media.Schema;

public record GetActivePartnershipListForUserResponse(
@Schema(description = "제휴글 id", example = "1")
Long postId,

@Schema(description = "제휴글 이름", example = "투썸 제휴")
String title,

@Schema(description = "제휴 장소", example = "투썸 플레이스")
String place,

@Schema(description = "썸네일 image url", example = "https://www.example.com.png")
String thumbnailImageUrl
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.campus.campus.domain.councilpost.application.dto.response;

import java.time.LocalDateTime;

import com.campus.campus.domain.councilpost.domain.entity.PostCategory;
import com.campus.campus.domain.councilpost.domain.entity.ThumbnailIcon;

import io.swagger.v3.oas.annotations.media.Schema;

public record GetPostListForCouncilResponse(
@Schema(description = "게시글 id", example = "1")
Long postId,

@Schema(description = "게시글 카테고리", example = "EVENT")
PostCategory category,

@Schema(description = "게시글 이름", example = "중간고사 간식생사")
String title,

@Schema(description = "장소", example = "310관 1층")
String place,

@Schema(description = "시간(끝나는 시간 or 행사날짜", example = "2026-01-10T18:00:00")
LocalDateTime dateTime,

@Schema(description = "썸네일 image url", example = "https://www.example.com.png")
String thumbnailImageUrl,

@Schema(description = "썸네일 아이콘", example = "FOOD")
ThumbnailIcon thumbnailIcon
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.campus.campus.domain.councilpost.application.dto.response;

import java.time.LocalDateTime;

import com.campus.campus.domain.councilpost.domain.entity.PostCategory;
import com.campus.campus.domain.councilpost.domain.entity.ThumbnailIcon;

import io.swagger.v3.oas.annotations.media.Schema;

public record GetUpcomingEventListForCouncilResponse(
@Schema(description = "게시글 id", example = "1")
Long postId,

@Schema(description = "게시글 카테고리", example = "EVENT")
PostCategory category,

@Schema(description = "게시글 이름", example = "중간고사 간식생사")
String title,

@Schema(description = "장소", example = "310관 1층")
String place,

@Schema(description = "시간(끝나는 시간 or 행사날짜", example = "2026-01-10T18:00:00")
LocalDateTime dateTime,

@Schema(description = "썸네일 아이콘", example = "FOOD")
ThumbnailIcon thumbnailIcon
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
import org.springframework.stereotype.Component;

import com.campus.campus.domain.council.domain.entity.StudentCouncil;
import com.campus.campus.domain.councilpost.application.dto.response.GetPostListForCouncilResponse;
import com.campus.campus.domain.councilpost.application.dto.response.GetUpcomingEventListForCouncilResponse;
import com.campus.campus.domain.councilpost.application.dto.response.GetActivePartnershipListForUserResponse;
import com.campus.campus.domain.councilpost.application.dto.response.PostListItemResponse;
import com.campus.campus.domain.councilpost.application.dto.request.PostRequest;
import com.campus.campus.domain.councilpost.application.dto.response.PostResponse;
Expand All @@ -19,7 +22,7 @@
@RequiredArgsConstructor
public class StudentCouncilPostMapper {

public PostListItemResponse toPostListItemResponse(StudentCouncilPost post, Long currentUserId) {
public PostListItemResponse toPostListItemResponse(StudentCouncilPost post, Long councilId) {
return new PostListItemResponse(
post.getId(),
post.getCategory(),
Expand All @@ -30,7 +33,39 @@ public PostListItemResponse toPostListItemResponse(StudentCouncilPost post, Long
: post.getEndDateTime(),
post.getThumbnailImageUrl(),
post.getThumbnailIcon(),
post.isWrittenByCouncil(currentUserId)
post.isWrittenByCouncil(councilId)
);
}

public GetPostListForCouncilResponse toGetPostListForCouncilResponse(StudentCouncilPost post) {
return new GetPostListForCouncilResponse(
post.getId(),
post.getCategory(),
post.getTitle(),
post.getPlace(),
post.isEvent() ? post.getStartDateTime() : post.getEndDateTime(),
post.getThumbnailImageUrl(),
post.getThumbnailIcon()
);
}

public GetUpcomingEventListForCouncilResponse toGetUpcomingEventListForCouncilResponse(StudentCouncilPost post) {
return new GetUpcomingEventListForCouncilResponse(
post.getId(),
post.getCategory(),
post.getTitle(),
post.getPlace(),
post.getStartDateTime(),
post.getThumbnailIcon()
);
}

public GetActivePartnershipListForUserResponse toGetActivePartnershipListForUserResponse(StudentCouncilPost post) {
return new GetActivePartnershipListForUserResponse(
post.getId(),
post.getTitle(),
post.getPlace(),
post.getThumbnailImageUrl()
);
}

Expand Down Expand Up @@ -59,7 +94,8 @@ public PostResponse toPostResponse(StudentCouncilPost post, List<String> images,
return builder.build();
}

public StudentCouncilPost createStudentCouncilPost(StudentCouncil writer, PostRequest dto, LocalDateTime startDateTime,
public StudentCouncilPost createStudentCouncilPost(StudentCouncil writer, PostRequest dto,
LocalDateTime startDateTime,
LocalDateTime endDateTime) {
return StudentCouncilPost.builder()
.writer(writer)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import org.springframework.transaction.annotation.Transactional;

import com.campus.campus.domain.council.domain.entity.CouncilType;
import com.campus.campus.domain.councilpost.application.dto.response.GetActivePartnershipListForUserResponse;
import com.campus.campus.domain.councilpost.application.dto.response.PostListItemResponse;
import com.campus.campus.domain.councilpost.application.dto.response.PostResponse;
import com.campus.campus.domain.councilpost.application.exception.CollegeNotSetException;
Expand All @@ -34,7 +35,8 @@
@Service
@RequiredArgsConstructor
@Slf4j
public class UserPostService {
@Transactional(readOnly = true)
public class StudentCouncilPostForUserService {

private static final int UPCOMING_HOURS = 72;
private static final ZoneId KST = ZoneId.of("Asia/Seoul");
Expand All @@ -44,7 +46,6 @@ public class UserPostService {
private final UserRepository userRepository;
private final PostAccessPolicy postAccessPolicy;

@Transactional(readOnly = true)
public Page<PostListItemResponse> findSchoolPosts(PostCategory category, int page, int size, Long userId) {
User user = userRepository.findByIdWithAcademicInfo(userId).orElseThrow(UserNotFoundException::new);

Expand All @@ -56,7 +57,6 @@ public Page<PostListItemResponse> findSchoolPosts(PostCategory category, int pag
return posts.map(post -> studentCouncilPostMapper.toPostListItemResponse(post, userId));
}

@Transactional(readOnly = true)
public Page<PostListItemResponse> findCollegePosts(PostCategory category, int page, int size, Long userId) {
User user = userRepository.findByIdWithAcademicInfo(userId).orElseThrow(UserNotFoundException::new);

Expand All @@ -72,7 +72,6 @@ public Page<PostListItemResponse> findCollegePosts(PostCategory category, int pa
return posts.map(post -> studentCouncilPostMapper.toPostListItemResponse(post, userId));
}

@Transactional(readOnly = true)
public Page<PostListItemResponse> findMajorPosts(PostCategory category, int page, int size, Long userId) {
User user = userRepository.findByIdWithAcademicInfo(userId).orElseThrow(UserNotFoundException::new);

Expand All @@ -84,7 +83,6 @@ public Page<PostListItemResponse> findMajorPosts(PostCategory category, int page
return posts.map(post -> studentCouncilPostMapper.toPostListItemResponse(post, userId));
}

@Transactional(readOnly = true)
public Page<PostListItemResponse> findUpcomingSchoolEvents72h(int page, int size, Long userId) {
User user = userRepository.findByIdWithAcademicInfo(userId).orElseThrow(UserNotFoundException::new);

Expand All @@ -105,7 +103,6 @@ public Page<PostListItemResponse> findUpcomingSchoolEvents72h(int page, int size
return posts.map(post -> studentCouncilPostMapper.toPostListItemResponse(post, userId));
}

@Transactional(readOnly = true)
public Page<PostListItemResponse> findUpcomingCollegeEvents72h(int page, int size, Long userId) {
User user = userRepository.findByIdWithAcademicInfo(userId).orElseThrow(UserNotFoundException::new);

Expand Down Expand Up @@ -134,7 +131,6 @@ public Page<PostListItemResponse> findUpcomingCollegeEvents72h(int page, int siz
return posts.map(post -> studentCouncilPostMapper.toPostListItemResponse(post, userId));
}

@Transactional(readOnly = true)
public Page<PostListItemResponse> findUpcomingMajorEvents72h(int page, int size, Long userId) {
User user = userRepository.findByIdWithAcademicInfo(userId).orElseThrow(UserNotFoundException::new);

Expand Down Expand Up @@ -163,7 +159,6 @@ public Page<PostListItemResponse> findUpcomingMajorEvents72h(int page, int size,
return posts.map(post -> studentCouncilPostMapper.toPostListItemResponse(post, userId));
}

@Transactional(readOnly = true)
public PostResponse findById(Long postId, Long userId) {
User user = userRepository.findByIdWithAcademicInfo(userId).orElseThrow(UserNotFoundException::new);

Expand All @@ -180,4 +175,42 @@ public PostResponse findById(Long postId, Long userId) {

return studentCouncilPostMapper.toPostResponse(post, imageUrls, userId);
}

public List<GetActivePartnershipListForUserResponse> findActivePartnershipForUser(CouncilType councilType,
Long userId) {
User user = userRepository.findByIdWithAcademicInfo(userId)
.orElseThrow(UserNotFoundException::new);

if (user.getSchool() == null) {
return List.of();
}

Long schoolId = user.getSchool().getSchoolId();
Long collegeId = null;
Long majorId = null;

if (CouncilType.COLLEGE_COUNCIL.equals(councilType)) {
if (user.getCollege() == null) {
return List.of();
}
collegeId = user.getCollege().getCollegeId();
}

if (CouncilType.MAJOR_COUNCIL.equals(councilType)) {
if (user.getMajor() == null) {
return List.of();
}
majorId = user.getMajor().getMajorId();
}

LocalDateTime now = LocalDateTime.now(KST);
Pageable partnershipCount = PageRequest.of(0, 3);

List<StudentCouncilPost> partnerships = studentCouncilPostRepository.findRandomActivePartnerships(schoolId,
councilType, PostCategory.PARTNERSHIP, collegeId, majorId, now, partnershipCount);

return partnerships.stream()
.map(studentCouncilPostMapper::toGetActivePartnershipListForUserResponse)
.toList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@
import org.springframework.transaction.annotation.Transactional;

import com.campus.campus.domain.council.application.exception.StudentCouncilNotFoundException;
import com.campus.campus.domain.council.domain.entity.CouncilType;
import com.campus.campus.domain.council.domain.entity.StudentCouncil;
import com.campus.campus.domain.council.domain.repository.StudentCouncilRepository;
import com.campus.campus.domain.councilpost.application.dto.response.GetPostListForCouncilResponse;
import com.campus.campus.domain.councilpost.application.dto.response.GetUpcomingEventListForCouncilResponse;
import com.campus.campus.domain.councilpost.application.dto.response.NormalizedDateTime;
import com.campus.campus.domain.councilpost.application.dto.response.PostListItemResponse;
import com.campus.campus.domain.councilpost.application.dto.request.PostRequest;
import com.campus.campus.domain.councilpost.application.dto.response.PostResponse;
import com.campus.campus.domain.councilpost.application.exception.NotPostWriterException;
Expand Down Expand Up @@ -100,34 +102,44 @@ public PostResponse findById(Long postId, Long currentUserId) {
}

@Transactional(readOnly = true)
public Page<PostListItemResponse> findAll(PostCategory category, int page, int size, Long currentUserId) {
Pageable pageable = PageRequest.of(Math.max(page - 1, 0), size, Sort.by(Sort.Direction.DESC, "createdAt"));
public Page<GetPostListForCouncilResponse> findPostListForCouncil(Long councilId, PostCategory category,
int page, int size) {
studentCouncilRepository.findByIdAndManagerApprovedIsTrueAndDeletedAtIsNull(councilId)
.orElseThrow(StudentCouncilNotFoundException::new);

Page<StudentCouncilPost> posts = (category == null)
? postRepository.findAll(pageable)
: postRepository.findAllByCategory(category, pageable);
Sort sort;
if (category == PostCategory.EVENT) {
sort = Sort.by(Sort.Direction.ASC, "startDateTime");
} else {
sort = Sort.by(Sort.Direction.ASC, "endDateTime");
}

return posts.map(post ->
studentCouncilPostMapper.toPostListItemResponse(post, currentUserId)
);
Pageable pageable = PageRequest.of(Math.max(page - 1, 0), size, sort);

LocalDateTime now = LocalDateTime.now();
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

타임존 불일치: LocalDateTime.now() 대신 KST 사용 권장

StudentCouncilPostForUserService에서는 LocalDateTime.now(KST)를 사용하지만, 이 서비스에서는 시스템 기본 타임존을 사용합니다. 배포 환경에 따라 시간 필터링 결과가 달라질 수 있습니다.

🔧 제안하는 수정 사항
+	private static final ZoneId KST = ZoneId.of("Asia/Seoul");
+
 	@Transactional(readOnly = true)
 	public Page<GetPostListForCouncilResponse> findPostListForCouncil(Long councilId, PostCategory category,
 		int page, int size) {
 		// ...
 		Pageable pageable = PageRequest.of(Math.max(page - 1, 0), size, sort);

-		LocalDateTime now = LocalDateTime.now();
+		LocalDateTime now = LocalDateTime.now(KST);

Line 136도 동일하게 수정 필요합니다.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
LocalDateTime now = LocalDateTime.now();
private static final ZoneId KST = ZoneId.of("Asia/Seoul");
@Transactional(readOnly = true)
public Page<GetPostListForCouncilResponse> findPostListForCouncil(Long councilId, PostCategory category,
int page, int size) {
// ...
Pageable pageable = PageRequest.of(Math.max(page - 1, 0), size, sort);
LocalDateTime now = LocalDateTime.now(KST);
🤖 Prompt for AI Agents
In
@src/main/java/com/campus/campus/domain/councilpost/application/service/StudentCouncilPostService.java
at line 119, The code in StudentCouncilPostService uses LocalDateTime.now()
which can cause timezone mismatches; replace both occurrences (the one at
LocalDateTime now = LocalDateTime.now(); and the similar one at line 136) to use
KST-aware time (e.g., LocalDateTime.now(ZoneId.of("Asia/Seoul")) or the existing
KST constant if present) so the service matches
StudentCouncilPostForUserService's timezone handling and yields consistent
filtering; update any imports/usages in the class accordingly.


Page<StudentCouncilPost> posts = postRepository.findPostsByCouncilAndFilters(councilId, category, now,
pageable);

return posts.map(studentCouncilPostMapper::toGetPostListForCouncilResponse);
}

@Transactional(readOnly = true)
public Page<PostListItemResponse> findUpcomingEvents(int page, int size, Long currentUserId) {
Pageable pageable = PageRequest.of(
Math.max(page - 1, 0),
size,
Sort.by(Sort.Direction.ASC, "startDateTime")
);
public Page<GetUpcomingEventListForCouncilResponse> findUpcomingEventsForCouncil(Long councilId,
int page, int size) {
Pageable pageable = PageRequest.of(Math.max(page - 1, 0), size,
Sort.by(Sort.Direction.ASC, "startDateTime"));

studentCouncilRepository.findByIdAndManagerApprovedIsTrueAndDeletedAtIsNull(councilId)
.orElseThrow(StudentCouncilNotFoundException::new);

LocalDateTime now = LocalDateTime.now();
LocalDateTime limit = now.plusHours(UPCOMING_EVENT_WINDOW_HOURS);

Page<StudentCouncilPost> posts = postRepository.findUpcomingEvents(PostCategory.EVENT, now, limit, pageable);
Page<StudentCouncilPost> posts = postRepository.findUpcomingEventsByCouncil(councilId, PostCategory.EVENT,
now, limit, pageable);

return posts.map(post ->
studentCouncilPostMapper.toPostListItemResponse(post, currentUserId)
);
return posts.map(studentCouncilPostMapper::toGetUpcomingEventListForCouncilResponse);
}

@Transactional
Expand Down
Loading