Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
d53473c
feat/#10: 학생회 제휴 게시글 생성/수정/조회/삭제 기능 구현
jjaeroong Dec 7, 2025
286901a
refactor/#10: 학생회 제휴/행사 게시글 생성 중, 이미지 presignedUrl temp 호출 후 게시글 생성 시…
jjaeroong Dec 22, 2025
88a18ec
refactor/#10: 불필요한 임포트 및 미사용 파일 제거
jjaeroong Dec 22, 2025
0ce0f50
refactor/#10: 게시글 생성 시, date 및 place 필드 저장 에러 수정
jjaeroong Dec 22, 2025
95a182a
refactor/#10: 게시글 생성 시, presingedUrl 로직 수정
jjaeroong Dec 23, 2025
c88440d
refactor/#10: 게시글 생성 시, 이미지 전용 presingedUrl API 생성
jjaeroong Dec 23, 2025
4644afd
refactor/#10: 게시글 생성 시, 이미지 sequence 필드 삭제 및 스웨거 API 설명 추가
jjaeroong Dec 23, 2025
dd47065
Merge dev into feature/#10
jjaeroong Dec 23, 2025
474738e
refactor/#10: ociConfig 필요한 필드에 getter 추가
jjaeroong Dec 23, 2025
fe1a223
refactor/#10: 행사/제휴 게시글 날짜·시간 처리 로직 분리
jjaeroong Dec 23, 2025
d8ba38c
refactor/#10: builder -> mapper 전환 및 presignedUrl 파일명 수정
jjaeroong Dec 27, 2025
3f950f8
refactor/#10: 사용하지 않는 import 제거
jjaeroong Dec 27, 2025
b4cab94
style/#10: 네이버 코드 포매터 적용
jjaeroong Dec 27, 2025
9f29cb4
style/#10: 파일별 마지막 줄 개행적용
jjaeroong Dec 27, 2025
9a97b17
refactor/#10: deletePost 컨트롤러 사용하지 않는 어노테이션 제거
jjaeroong Dec 27, 2025
dbe58c2
refactor/#10: ociConfig 수정 및 application-test 파일 추가
jjaeroong Dec 27, 2025
b71f9d1
refactor/#10: application-test 파일 수정
jjaeroong Dec 27, 2025
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
9 changes: 9 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,15 @@ dependencies {
// mail
implementation 'org.springframework.boot:spring-boot-starter-mail'

// OCI SDK BOM
implementation platform('com.oracle.oci.sdk:oci-java-sdk-bom:3.31.0')

// OCI ObjectStorage
implementation 'com.oracle.oci.sdk:oci-java-sdk-objectstorage'
implementation 'com.oracle.oci.sdk:oci-java-sdk-common'

//HTTP 클라이언트
implementation 'com.oracle.oci.sdk:oci-java-sdk-common-httpclient-jersey3'

compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@
import com.campus.campus.domain.school.domain.repository.MajorRepository;
import com.campus.campus.domain.school.domain.repository.SchoolRepository;
import com.campus.campus.global.config.SecurityConfig;
import com.campus.campus.global.util.jwt.application.service.RedisTokenService;
import com.campus.campus.global.util.jwt.JwtProvider;
import com.campus.campus.global.util.jwt.application.service.RedisTokenService;

import lombok.RequiredArgsConstructor;

Expand Down Expand Up @@ -133,13 +133,6 @@ public void findPassword(StudentCouncilFindPasswordRequest studentCouncilFindPas
studentCouncilRepository.save(studentCouncil);
}

private record CouncilScope(
College college,
Major major
) {

}

private CouncilScope validateCouncilScope(
StudentCouncilSignUpRequest studentCouncilSignUpRequest,
School school
Expand Down Expand Up @@ -196,4 +189,11 @@ private void checkVerifiedEmail(String email, VerificationType verificationType)
throw new EmailNotVerifiedException();
}
}

private record CouncilScope(
College college,
Major major
) {

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,15 @@ public class StudentCouncil extends BaseEntity {
public void changePassword(String newPassword) {
this.password = newPassword;
}

public String getFullCouncilName() {
StringBuilder fullName = new StringBuilder();
if (school != null)
fullName.append(school.getSchoolName());
if (college != null)
fullName.append(" ").append(college.getCollegeName());
if (major != null)
fullName.append(" ").append(major.getMajorName());
return fullName.append(" 학생회").toString().trim();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,24 @@
import java.util.Optional;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import com.campus.campus.domain.council.domain.entity.StudentCouncil;

public interface StudentCouncilRepository extends JpaRepository<StudentCouncil, Long> {

Optional<StudentCouncil> findByLoginId(String loginId);

Optional<StudentCouncil> findByEmail(String email);

@Query("SELECT sc FROM StudentCouncil sc " +
"LEFT JOIN FETCH sc.school " +
"LEFT JOIN FETCH sc.college " +
"LEFT JOIN FETCH sc.major " +
"WHERE sc.id = :councilId")
Optional<StudentCouncil> findByIdWithDetails(@Param("councilId") Long councilId);

boolean existsByLoginId(String loginId);

boolean existsByEmail(String email);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.campus.campus.domain.studentcouncilpost.application.dto.request;

import java.time.LocalDateTime;
import java.util.List;

import com.campus.campus.domain.studentcouncilpost.domain.entity.PostCategory;
import com.campus.campus.domain.studentcouncilpost.domain.entity.ThumbnailIcon;
import com.fasterxml.jackson.annotation.JsonFormat;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;

public record PostRequestDto(

@NotNull
PostCategory category,

@NotBlank
String title,

@NotBlank
String content,

String place,

@Schema(example = "2025-04-10T18:00")
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm")
LocalDateTime startDateTime,

@Schema(example = "2025-04-30T23:59")
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm")
LocalDateTime endDateTime,

// 썸네일 (둘 중 하나는 필수)
String thumbnailImageUrl,
ThumbnailIcon thumbnailIcon,

// 본문 이미지들
List<String> imageUrls
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.campus.campus.domain.studentcouncilpost.application.dto.response;

import java.time.LocalDateTime;

public record NormalizedDateTime(
LocalDateTime startDateTime,
LocalDateTime endDateTime
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.campus.campus.domain.studentcouncilpost.application.dto.response;

import java.time.LocalDateTime;

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

public record PostListItemResponseDto(
Long id,
PostCategory category,
String title,
String place,
LocalDateTime endDateTime,
String thumbnailImageUrl,
ThumbnailIcon thumbnailIcon,
Boolean isWriter
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.campus.campus.domain.studentcouncilpost.application.dto.response;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;

import com.campus.campus.domain.studentcouncilpost.domain.entity.PostCategory;
import com.campus.campus.domain.studentcouncilpost.domain.entity.ThumbnailIcon;
import com.fasterxml.jackson.annotation.JsonInclude;

import lombok.Builder;

@Builder
@JsonInclude(JsonInclude.Include.NON_NULL)
public record PostResponseDto(

Long id,
Long writerId,
String writerName,
Boolean isWriter,

PostCategory category,
String title,
String content,
String place,
LocalDate startDate,
LocalDate endDate,
LocalDateTime startDateTime,

String thumbnailImageUrl,
ThumbnailIcon thumbnailIcon,

List<String> images
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.campus.campus.domain.studentcouncilpost.application.exception;

import com.campus.campus.global.common.exception.ErrorCodeInterface;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.http.HttpStatus;

@Getter
@AllArgsConstructor
public enum ErrorCode implements ErrorCodeInterface {

POST_NOT_FOUND(2401, HttpStatus.NOT_FOUND, "게시글을 찾을 수 없습니다."),
NOT_POST_WRITER(2402, HttpStatus.FORBIDDEN, "작성자만 해당 작업을 수행할 수 있습니다."),
THUMBNAIL_REQUIRED(2403, HttpStatus.BAD_REQUEST, "썸네일(이미지 또는 아이콘)은 반드시 필요합니다."),
POST_IMAGE_LIMIT_EXCEEDED(2404, HttpStatus.BAD_REQUEST, "게시글 이미지는 최대 10개까지 등록할 수 있습니다."),

// EVENT 관련
EVENT_START_DATETIME_REQUIRED(2405, HttpStatus.BAD_REQUEST, "행사는 시작 일시가 필요합니다."),
EVENT_END_DATETIME_NOT_ALLOWED(2406, HttpStatus.BAD_REQUEST, "행사는 종료 일시를 가질 수 없습니다."),

// PARTNERSHIP 관련
PARTNERSHIP_DATE_REQUIRED(2407, HttpStatus.BAD_REQUEST, "제휴는 시작일과 종료일이 필요합니다.");

private final int code;
private final HttpStatus status;
private final String message;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.campus.campus.domain.studentcouncilpost.application.exception;

import com.campus.campus.global.common.exception.ApplicationException;

public class EventEndDateTimeNotAllowedException extends ApplicationException {
public EventEndDateTimeNotAllowedException() {
super(ErrorCode.EVENT_END_DATETIME_NOT_ALLOWED);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.campus.campus.domain.studentcouncilpost.application.exception;

import com.campus.campus.global.common.exception.ApplicationException;

public class EventStartDateTimeRequiredException extends ApplicationException {
public EventStartDateTimeRequiredException() {
super(ErrorCode.EVENT_START_DATETIME_REQUIRED);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.campus.campus.domain.studentcouncilpost.application.exception;

import com.campus.campus.global.common.exception.ApplicationException;

public class NotPostWriterException extends ApplicationException {
public NotPostWriterException() {
super(ErrorCode.NOT_POST_WRITER);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.campus.campus.domain.studentcouncilpost.application.exception;

import com.campus.campus.global.common.exception.ApplicationException;

public class PartnershipDateRequiredException extends ApplicationException {
public PartnershipDateRequiredException() {
super(ErrorCode.PARTNERSHIP_DATE_REQUIRED);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.campus.campus.domain.studentcouncilpost.application.exception;

import com.campus.campus.global.common.exception.ApplicationException;

public class PostImageLimitExceededException extends ApplicationException {
public PostImageLimitExceededException() {
super(ErrorCode.POST_IMAGE_LIMIT_EXCEEDED);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.campus.campus.domain.studentcouncilpost.application.exception;

import com.campus.campus.global.common.exception.ApplicationException;

public class PostNotFoundException extends ApplicationException {
public PostNotFoundException() {
super(ErrorCode.POST_NOT_FOUND);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.campus.campus.domain.studentcouncilpost.application.exception;

import com.campus.campus.global.common.exception.ApplicationException;

public class ThumbnailRequiredException extends ApplicationException {
public ThumbnailRequiredException() {
super(ErrorCode.THUMBNAIL_REQUIRED);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package com.campus.campus.domain.studentcouncilpost.application.mapper;

import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;

import com.campus.campus.domain.council.domain.entity.StudentCouncil;
import com.campus.campus.domain.studentcouncilpost.application.dto.response.PostListItemResponseDto;
import com.campus.campus.domain.studentcouncilpost.application.dto.request.PostRequestDto;
import com.campus.campus.domain.studentcouncilpost.application.dto.response.PostResponseDto;
import com.campus.campus.domain.studentcouncilpost.domain.entity.PostImage;
import com.campus.campus.domain.studentcouncilpost.domain.entity.StudentCouncilPost;

public class StudentCouncilPostMapper {

public static PostListItemResponseDto toListItem(
StudentCouncilPost post,
Long currentUserId
) {
return new PostListItemResponseDto(
post.getId(),
post.getCategory(),
post.getTitle(),
post.getPlace(),
post.getEndDateTime(),
post.getThumbnailImageUrl(),
post.getThumbnailIcon(),
post.getWriter().getId().equals(currentUserId)
);

}

public static PostResponseDto toDetail(
StudentCouncilPost post,
List<String> images,
Long currentUserId
) {

var writer = post.getWriter();
var builder = PostResponseDto.builder()
.id(post.getId())
.writerId(writer.getId())
.writerName(writer.getFullCouncilName())
.isWriter(post.isWrittenBy(currentUserId))
.category(post.getCategory())
.title(post.getTitle())
.content(post.getContent())
.place(post.getPlace())
.thumbnailImageUrl(post.getThumbnailImageUrl())
.thumbnailIcon(post.getThumbnailIcon())
.images(images != null ? images : Collections.emptyList());

if (post.isEvent()) {
builder.startDateTime(post.getStartDateTime());
} else {
builder.startDate(post.getDisplayStartDate());
builder.endDate(post.getDisplayEndDate());
}

return builder.build();
}

public static StudentCouncilPost toEntity(
StudentCouncil writer,
PostRequestDto dto,
LocalDateTime startDateTime,
LocalDateTime endDateTime
) {
return StudentCouncilPost.builder()
.writer(writer)
.category(dto.category())
.title(dto.title())
.content(dto.content())
.place(dto.place())
.startDateTime(startDateTime)
.endDateTime(endDateTime)
.thumbnailImageUrl(dto.thumbnailImageUrl())
.thumbnailIcon(dto.thumbnailIcon())
.build();
}

public static PostImage toEntity(
StudentCouncilPost post,
String imageUrl
) {
return PostImage.builder()
.post(post)
.imageUrl(imageUrl)
.build();
}

}
Loading