Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
96fbdc0
[fix] SecurityConfig 응원 API 엔드포인트 추가
floreo1242 May 1, 2025
e6f4ebd
Merge pull request #146 from DguFarmSystem/#145-cheer
floreo1242 May 1, 2025
78a866f
[fix] SecurityConfig 화이트리스트에 응원 API 엔드포인트 삭제
floreo1242 May 1, 2025
d5a3647
[chore] #148 카프카 의존성 추가
hysong4u May 6, 2025
7a0986c
[feat] #148 카프카 씨앗 적립 이벤트 발생 시 메세지 전송 로직 구현
hysong4u May 6, 2025
467450e
[refactor] #148 redis Zset으로 랭킹 시스템 개선 (실시간 조회로 방식 변경)
hysong4u May 6, 2025
cd90515
[fix] #148 임시토큰 API 운영서버 스웨거에서 보이지 않도록 설정(적용여부 확인 필요)
hysong4u May 6, 2025
76fa17f
[remove] #148 스프링 캐시 의존성 삭제
hysong4u May 6, 2025
1f1cddd
[fix] #148 출석 API에 비관적 락 적용해 동시성 이슈 해결
hysong4u May 14, 2025
4528495
Merge pull request #147 from DguFarmSystem/#145-cheer
floreo1242 May 14, 2025
61ccbaa
[refactor] #89 fromEntity로 수정
saokiritoni May 18, 2025
fbca397
[refactor] #89 schema 추가
saokiritoni May 18, 2025
cea39c1
[feat] #89 작성 시간 반환 추가
saokiritoni May 18, 2025
1de8bfd
Merge pull request #150 from DguFarmSystem/feat/#89-news
saokiritoni May 18, 2025
e4b1073
[fix] JVM 힙 메모리 제한 추가
saokiritoni May 18, 2025
f5db69a
Merge pull request #152 from DguFarmSystem/fix/#151-devMem
saokiritoni May 18, 2025
563b698
[feat] #89 태그 추가
saokiritoni May 18, 2025
38f8d02
Merge pull request #154 from DguFarmSystem/develop
floreo1242 May 18, 2025
e5c0454
[merge] #148 develop 브랜치 merge
hysong4u May 18, 2025
a197d31
Merge pull request #149 from DguFarmSystem/feat/#148-ranking
hysong4u May 18, 2025
b21339b
Merge pull request #155 from DguFarmSystem/develop
hysong4u May 18, 2025
a9f53ac
[fix] #148 @Lock으로 인해 발생한 READ ONLY 트랜잭션 오류 해결
hysong4u May 18, 2025
0e5c611
Merge pull request #156 from DguFarmSystem/feat/#148-ranking
hysong4u May 19, 2025
5c5e9ab
Merge pull request #157 from DguFarmSystem/develop
hysong4u May 19, 2025
e284859
Merge pull request #153 from DguFarmSystem/feat/#89-news
saokiritoni May 19, 2025
c7f9327
Merge pull request #158 from DguFarmSystem/develop
saokiritoni May 19, 2025
b07f718
[docs] #148 운영환경 임시토큰 발급 API Swagger 문서에서 제거
hysong4u May 19, 2025
853e865
[feat] #160 Swagger에 Profile 설정 추가
floreo1242 May 20, 2025
9adcf6e
[docs] #89 API 문서 보충
saokiritoni May 21, 2025
9d90713
[fix] #89 home 경로 토큰 인증 없도록 추가
saokiritoni May 21, 2025
6413696
Merge pull request #161 from DguFarmSystem/feat/#89-news
dongmin0204 May 21, 2025
05c2260
Merge pull request #159 from DguFarmSystem/feat/#148-ranking
saokiritoni May 21, 2025
741843b
Merge branch 'develop' into feat/#160-swagger
floreo1242 May 21, 2025
4298a82
Merge pull request #162 from DguFarmSystem/feat/#160-swagger
floreo1242 May 21, 2025
c92e319
Merge pull request #163 from DguFarmSystem/develop
floreo1242 May 21, 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
4 changes: 3 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,6 @@ ARG JAR_FILE=build/libs/HomePage-BE-0.0.1-SNAPSHOT.jar
COPY ${JAR_FILE} app.jar

# JVM 시간대 설정을 포함한 애플리케이션 실행
ENTRYPOINT ["java", "-Duser.timezone=Asia/Seoul", "-jar", "/app.jar"]
# -Xmx256m 최대 힙 메모리를 256MB로 제한
# -Xms128m 초기 힙 메모리 크기를 128MB로 설정
ENTRYPOINT ["java", "-Duser.timezone=Asia/Seoul", "-Xmx256m", "-Xms128m", "-jar", "/app.jar"]
5 changes: 3 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,9 @@ dependencies {
//csv
implementation 'org.apache.commons:commons-csv:1.10.0'

//spring cache
implementation 'org.springframework.boot:spring-boot-starter-cache'
// kafka
implementation 'org.springframework.kafka:spring-kafka'

}

tasks.named('test') {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,24 @@
package org.farmsystem.homepage.domain.news.dto.request;

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

import java.util.List;

public record NewsRequestDTO(String title, String content, String thumbnailUrl, List<String> imageUrls) { }
@Schema(description = "소식 작성 요청 DTO")
public record NewsRequestDTO(
@Schema(description = "소식 제목", example = "파밍 시스템 리뉴얼 안내")
String title,

@Schema(description = "소식 본문", example = "파밍 시스템이 새롭게 리뉴얼되었습니다. 자세한 사항은 홈페이지를 참고하세요.")
String content,

@Schema(description = "썸네일 이미지 URL", example = "https://s3-bucket/thumbnail.jpg")
String thumbnailUrl,

@Schema(description = "본문에 첨부될 이미지 URL 목록", example = "[\"https://s3-bucket/image1.jpg\", \"https://s3-bucket/image2.jpg\"]")
List<String> imageUrls,

@Schema(description = "태그 목록", example = "[\"#활동\", \"#이벤트\"]")
List<String> tags
) {}

Original file line number Diff line number Diff line change
@@ -1,5 +1,47 @@
package org.farmsystem.homepage.domain.news.dto.response;

import io.swagger.v3.oas.annotations.media.Schema;
import org.farmsystem.homepage.domain.news.entity.News;

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

public record NewsDetailResponseDTO(Long newsId, String title, String thumbnailUrl, String content, List<String> imageUrls) { }
@Schema(description = "소식 상세 응답 DTO")
public record NewsDetailResponseDTO(
@Schema(description = "소식 ID", example = "1")
Long newsId,

@Schema(description = "소식 제목", example = "5월 활동 요약")
String title,

@Schema(description = "썸네일 이미지 URL", example = "https://s3-bucket/thumbnail.jpg")
String thumbnailUrl,

@Schema(description = "소식 본문", example = "이번 달 팜시스템은 다양한 활동을 진행했습니다...윤석이가 개발을 하는데...")
String content,

@Schema(description = "본문 이미지 URL 목록", example = "[\"https://s3/image1.jpg\", \"https://s3/image2.jpg\"]")
List<String> imageUrls,

@Schema(description = "태그 목록", example = "[\"#공지\", \"#활동\"]")
List<String> tags,

@Schema(description = "작성일", example = "2025-05-18T12:34:56")
LocalDateTime createdAt,

@Schema(description = "수정일", example = "2025-05-18T12:35:56")
LocalDateTime updatedAt
) {
public static NewsDetailResponseDTO fromEntity(News news) {
return new NewsDetailResponseDTO(
news.getNewsId(),
news.getTitle(),
news.getThumbnailUrl(),
news.getContent(),
news.getImageUrls(),
news.getTags(),
news.getCreatedAt(),
news.getUpdatedAt()
);
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,48 @@
package org.farmsystem.homepage.domain.news.dto.response;

public record NewsListResponseDTO(Long newsId, String title, String thumbnailUrl) { }
import io.swagger.v3.oas.annotations.media.Schema;
import org.farmsystem.homepage.domain.news.entity.News;

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

@Schema(description = "소식 목록 응답 DTO")
public record NewsListResponseDTO(
@Schema(description = "소식 ID", example = "1")
Long newsId,

@Schema(description = "소식 제목", example = "5월 활동 요약")
String title,

@Schema(description = "썸네일 이미지 URL", example = "https://s3-bucket/thumbnail.jpg")
String thumbnailUrl,

@Schema(description = "본문 미리보기", example = "이번 달 팜시스템 활동은... (최대 50자)")
String contentPreview,

@Schema(description = "태그 목록", example = "[\"#공지\", \"#이벤트\"]")
List<String> tags,

@Schema(description = "작성일", example = "2025-05-18T12:34:56")
LocalDateTime createdAt,

@Schema(description = "수정일", example = "2025-05-18T12:35:56")
LocalDateTime updatedAt
) {
public static NewsListResponseDTO fromEntity(News news) {
return new NewsListResponseDTO(
news.getNewsId(),
news.getTitle(),
news.getThumbnailUrl(),
extractPreview(news.getContent()),
news.getTags(),
news.getCreatedAt(),
news.getUpdatedAt()
);
}

private static String extractPreview(String content) {
if (content == null) return "";
return content.length() <= 50 ? content : content.substring(0, 50) + "...";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,26 @@ public class News extends BaseTimeEntity {
@Column(name = "image_url")
private List<String> imageUrls;

@ElementCollection
@CollectionTable(name = "news_tags", joinColumns = @JoinColumn(name = "news_id"))
@Column(name = "tag")
private List<String> tags;

@Builder
public News(String title, String content, String thumbnailUrl, List<String> imageUrls) {
public News(String title, String content, String thumbnailUrl, List<String> imageUrls, List<String> tags) {
this.title = title;
this.content = content;
this.thumbnailUrl = thumbnailUrl;
this.imageUrls = imageUrls;
this.tags = tags;
}

public void updateNews(String title, String content, String thumbnailUrl, List<String> imageUrls) {
public void updateNews(String title, String content, String thumbnailUrl, List<String> imageUrls, List<String> tags) {
this.title = title;
this.content = content;
this.thumbnailUrl = thumbnailUrl;
this.imageUrls = imageUrls;
this.tags = tags;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -22,36 +22,42 @@ public class NewsService {

public List<NewsListResponseDTO> getAllNews() {
return newsRepository.findAll().stream()
.map(news -> new NewsListResponseDTO(news.getNewsId(), news.getTitle(), news.getThumbnailUrl()))
.map(NewsListResponseDTO::fromEntity)
.collect(Collectors.toList());
}

public NewsDetailResponseDTO getNewsById(Long newsId) {
News news = newsRepository.findById(newsId)
.orElseThrow(() -> new BusinessException(ErrorCode.NEWS_NOT_FOUND));
return new NewsDetailResponseDTO(news.getNewsId(), news.getTitle(), news.getThumbnailUrl(), news.getContent(), news.getImageUrls());
return NewsDetailResponseDTO.fromEntity(news);
}

@Transactional
public NewsDetailResponseDTO createNews(NewsRequestDTO request) {
News news = News.builder()
.title(request.title())
.content(request.content())
.thumbnailUrl(request.thumbnailUrl())
.imageUrls(request.imageUrls())
.build();
News news = new News(
request.title(),
request.content(),
request.thumbnailUrl(),
request.imageUrls(),
request.tags()
);
newsRepository.save(news);
return new NewsDetailResponseDTO(news.getNewsId(), news.getTitle(), news.getThumbnailUrl(), news.getContent(), news.getImageUrls());
return NewsDetailResponseDTO.fromEntity(news);
}

@Transactional
public NewsDetailResponseDTO updateNews(Long newsId, NewsRequestDTO request) {
News news = newsRepository.findById(newsId)
.orElseThrow(() -> new BusinessException(ErrorCode.NEWS_NOT_FOUND));
news.updateNews(request.title(), request.content(), request.thumbnailUrl(), request.imageUrls());
return new NewsDetailResponseDTO(news.getNewsId(), news.getTitle(), news.getThumbnailUrl(), news.getContent(), news.getImageUrls());
news.updateNews(
request.title(),
request.content(),
request.thumbnailUrl(),
request.imageUrls(),
request.tags()
);
return NewsDetailResponseDTO.fromEntity(news);
}


@Transactional
public void deleteNews(Long newsId) {
if (!newsRepository.existsById(newsId)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import org.farmsystem.homepage.domain.user.dto.request.UserTokenRequestDTO;
import org.farmsystem.homepage.domain.user.dto.response.UserTokenResponseDTO;
import org.farmsystem.homepage.global.common.SuccessResponse;
import org.springframework.context.annotation.Profile;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package org.farmsystem.homepage.domain.user.repository;

import jakarta.persistence.LockModeType;
import org.farmsystem.homepage.domain.user.entity.DailySeed;
import org.farmsystem.homepage.domain.user.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Lock;

import java.util.Optional;

public interface DailySeedRepository extends JpaRepository<DailySeed, Long> {
@Lock(LockModeType.PESSIMISTIC_WRITE)
Optional<DailySeed> findByUser(User user);
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public class DailySeedService {

private final DailySeedRepository dailySeedRepository;
private final UserRepository userRepository;
private final SeedEventProducer seedEventProducer;

//TODO : 적립 필요한 이벤트에 earnSeed 함수 호출 추가하기
// 예 : dailySeedService.earnSeed(userId, SeedEventType.ATTENDANCE);
Expand All @@ -38,37 +39,46 @@ public void earnSeed(Long userId, SeedEventType eventType) {
DailySeed dailySeed = dailySeedRepository.findByUser(user)
.orElseGet(() -> new DailySeed(user));

handleSeedEvent(dailySeed, eventType, user);
boolean success = handleSeedEvent(dailySeed, eventType, user);

dailySeedRepository.save(dailySeed);
userRepository.save(user);

// Kafka에 이벤트 전송 (중복 요청 시 메세지 전송 방지)
if (success) {
seedEventProducer.sendSeedEvent(userId, eventType);
}
}

// 중복 적립 방지
private void handleSeedEvent(DailySeed dailySeed, SeedEventType eventType, User user) {
private boolean handleSeedEvent(DailySeed dailySeed, SeedEventType eventType, User user) {
switch (eventType) {
case ATTENDANCE:
// 중복 출석 방지
case ATTENDANCE: //중복 적립, 요청 불가
if (dailySeed.isAttendance()) {
throw new BusinessException(ALREADY_ATTENDANCE);
}
dailySeed.updateAttendance();
user.addTotalSeed(eventType.getSeedAmount());
break;
return true;

case CHEER:
if (dailySeed.isCheer()) return;
case CHEER: //중복 적립 불가, 요청은 가능
if (dailySeed.isCheer()) return false;
dailySeed.updateCheer();
user.addTotalSeed(eventType.getSeedAmount());
break;
return true;

case FARMING_LOG:
if (dailySeed.isFarminglog()) return;
if (dailySeed.isFarminglog()) return false;
dailySeed.updateFarminglog();
user.addTotalSeed(eventType.getSeedAmount());
break;
return true;

default:
return false;
}
}

@Transactional
public TodaySeedResponseDTO getTodaySeed(Long userId) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new EntityNotFoundException(USER_NOT_FOUND));
Expand Down

This file was deleted.

Loading
Loading