Skip to content

Commit ae9413a

Browse files
authored
Merge branch 'develop' into fix/delete-member-api
2 parents af88f4f + e54717d commit ae9413a

12 files changed

Lines changed: 132 additions & 53 deletions

File tree

src/main/java/in/koreatech/koin/domain/bus/service/express/client/StaticExpressBusClient.java

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -60,17 +60,15 @@ private List<ExpressBusCacheInfo> getKoreatechToTerminal() {
6060
new ExpressBusCacheInfo(LocalTime.of(8, 35), LocalTime.of(8, 55), 1900),
6161
new ExpressBusCacheInfo(LocalTime.of(9, 35), LocalTime.of(9, 55), 1900),
6262
new ExpressBusCacheInfo(LocalTime.of(10, 35), LocalTime.of(10, 55), 1900),
63-
new ExpressBusCacheInfo(LocalTime.of(11, 5), LocalTime.of(11, 25), 1900),
6463
new ExpressBusCacheInfo(LocalTime.of(11, 35), LocalTime.of(11, 55), 1900),
6564
new ExpressBusCacheInfo(LocalTime.of(13, 35), LocalTime.of(13, 55), 1900),
6665
new ExpressBusCacheInfo(LocalTime.of(14, 35), LocalTime.of(14, 55), 1900),
67-
new ExpressBusCacheInfo(LocalTime.of(15, 5), LocalTime.of(15, 25), 1900),
6866
new ExpressBusCacheInfo(LocalTime.of(15, 35), LocalTime.of(15, 55), 1900),
6967
new ExpressBusCacheInfo(LocalTime.of(16, 35), LocalTime.of(16, 55), 1900),
7068
new ExpressBusCacheInfo(LocalTime.of(17, 35), LocalTime.of(17, 55), 1900),
71-
new ExpressBusCacheInfo(LocalTime.of(18, 55), LocalTime.of(19, 15), 1900),
69+
new ExpressBusCacheInfo(LocalTime.of(18, 35), LocalTime.of(18, 55), 1900),
7270
new ExpressBusCacheInfo(LocalTime.of(19, 35), LocalTime.of(19, 55), 1900),
73-
new ExpressBusCacheInfo(LocalTime.of(21, 5), LocalTime.of(21, 25), 1900),
71+
new ExpressBusCacheInfo(LocalTime.of(20, 35), LocalTime.of(20, 55), 1900),
7472
new ExpressBusCacheInfo(LocalTime.of(22, 5), LocalTime.of(22, 25), 1900)
7573
);
7674
}
@@ -81,14 +79,14 @@ private List<ExpressBusCacheInfo> getTerminalToKoreatech() {
8179
new ExpressBusCacheInfo(LocalTime.of(8, 30), LocalTime.of(9, 3), 1900),
8280
new ExpressBusCacheInfo(LocalTime.of(9, 0), LocalTime.of(9, 33), 1900),
8381
new ExpressBusCacheInfo(LocalTime.of(10, 0), LocalTime.of(10, 33), 1900),
82+
new ExpressBusCacheInfo(LocalTime.of(11, 0), LocalTime.of(11, 33), 1900),
8483
new ExpressBusCacheInfo(LocalTime.of(12, 0), LocalTime.of(12, 33), 1900),
85-
new ExpressBusCacheInfo(LocalTime.of(12, 30), LocalTime.of(13, 3), 1900),
8684
new ExpressBusCacheInfo(LocalTime.of(13, 0), LocalTime.of(13, 33), 1900),
8785
new ExpressBusCacheInfo(LocalTime.of(15, 0), LocalTime.of(15, 33), 1900),
8886
new ExpressBusCacheInfo(LocalTime.of(16, 0), LocalTime.of(16, 33), 1900),
89-
new ExpressBusCacheInfo(LocalTime.of(16, 40), LocalTime.of(17, 13), 1900),
87+
new ExpressBusCacheInfo(LocalTime.of(17, 0), LocalTime.of(17, 33), 1900),
9088
new ExpressBusCacheInfo(LocalTime.of(18, 0), LocalTime.of(18, 33), 1900),
91-
new ExpressBusCacheInfo(LocalTime.of(19, 30), LocalTime.of(20, 3), 1900),
89+
new ExpressBusCacheInfo(LocalTime.of(19, 0), LocalTime.of(19, 33), 1900),
9290
new ExpressBusCacheInfo(LocalTime.of(20, 30), LocalTime.of(21, 3), 1900)
9391
);
9492
}

src/main/java/in/koreatech/koin/domain/callvan/controller/CallvanApi.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ ResponseEntity<CallvanPostCreateResponse> createCallvanPost(
9595
- `arrival_keyword`: 도착지 직접 입력 검색어.
9696
- `title`: 게시글 제목 검색어.
9797
- `sort`: 정렬 기준 (`DEPARTURE_ASC`(출발일 오름차순), `DEPARTURE_DESC`(출발일 내림차순), `LATEST_ASC`(게시글 등록순 오름차순), `LATEST_DESC`(게시글 등록순 내림차순)). 기본값은 `LATEST_DESC`입니다.
98+
- `joined`: `true`일 경우 로그인 사용자가 참여한 게시글만 조회합니다. 비로그인 사용자는 자동으로 `false` 처리됩니다.
9899
- `page`: 페이지 번호 (1부터 시작, 기본값 1)
99100
- `limit`: 한 페이지당 게시글 수 (최대 50, 기본값 10)
100101
@@ -104,7 +105,8 @@ ResponseEntity<CallvanPostCreateResponse> createCallvanPost(
104105
3. `statuses`가 전달되면 해당 상태들만 조회하고, 미전달 시 상태 조건 없이 전체를 조회합니다.
105106
4. `DEPARTURE_ASC`는 출발 날짜+시간 기준 오름차순, `DEPARTURE_DESC`는 출발 날짜+시간 기준 내림차순으로 정렬됩니다.
106107
5. `LATEST_ASC`는 게시글 등록순 오름차순, `LATEST_DESC`는 게시글 등록순 내림차순으로 정렬됩니다.
107-
6. 로그인된 사용자의 경우, 해당 콜벤 게시글에 합류한 상태면 `isJoined` 필드가 true로 표시됩니다.
108+
6. `joined=true`이면 로그인 사용자가 참여한 게시글만 반환합니다. 비로그인 사용자는 자동으로 `false` 처리됩니다. `false`는 모든 게시글이 반환됩니다.
109+
7. 로그인된 사용자의 경우, 해당 콜벤 게시글에 합류한 상태면 `isJoined` 필드가 true로 표시됩니다.
108110
""")
109111
@GetMapping
110112
ResponseEntity<CallvanPostSearchResponse> getCallvanPosts(
@@ -116,6 +118,7 @@ ResponseEntity<CallvanPostSearchResponse> getCallvanPosts(
116118
@RequestParam(required = false, name = "statuses") List<CallvanPostStatusFilter> statuses,
117119
@RequestParam(required = false) String title,
118120
@RequestParam(required = false, defaultValue = "LATEST_DESC") CallvanPostSortCriteria sort,
121+
@RequestParam(required = false, name = "joined", defaultValue = "false") Boolean isJoined,
119122
@RequestParam(required = false) Integer page,
120123
@RequestParam(required = false) Integer limit,
121124
@UserId Integer userId

src/main/java/in/koreatech/koin/domain/callvan/controller/CallvanController.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,12 +73,13 @@ public ResponseEntity<CallvanPostSearchResponse> getCallvanPosts(
7373
List<CallvanPostStatusFilter> statuses,
7474
String title,
7575
CallvanPostSortCriteria sort,
76+
Boolean isJoined,
7677
Integer page,
7778
Integer limit,
7879
@UserId Integer userId
7980
) {
8081
CallvanPostSearchResponse response = callvanPostQueryService.getCallvanPosts(
81-
author, departures, departureKeyword, arrivals, arrivalKeyword, statuses, title, sort, page, limit,
82+
author, departures, departureKeyword, arrivals, arrivalKeyword, statuses, title, sort, isJoined, page, limit,
8283
userId);
8384
return ResponseEntity.ok().body(response);
8485
}

src/main/java/in/koreatech/koin/domain/callvan/repository/CallvanPostQueryRepository.java

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package in.koreatech.koin.domain.callvan.repository;
22

3+
import static in.koreatech.koin.domain.callvan.model.QCallvanParticipant.callvanParticipant;
34
import static in.koreatech.koin.domain.callvan.model.QCallvanPost.callvanPost;
45

56
import java.util.List;
@@ -9,6 +10,7 @@
910

1011
import com.querydsl.core.types.OrderSpecifier;
1112
import com.querydsl.core.types.dsl.BooleanExpression;
13+
import com.querydsl.jpa.JPAExpressions;
1214
import com.querydsl.jpa.impl.JPAQueryFactory;
1315

1416
import in.koreatech.koin.common.model.Criteria;
@@ -32,6 +34,7 @@ public List<CallvanPost> findCallvanPosts(
3234
String arrivalKeyword,
3335
List<CallvanStatus> statuses,
3436
String title,
37+
Integer joinedMemberId,
3538
CallvanPostSortCriteria sort,
3639
Criteria criteria
3740
) {
@@ -42,7 +45,8 @@ public List<CallvanPost> findCallvanPosts(
4245
departureFilter(departures, departureKeyword),
4346
arrivalFilter(arrivals, arrivalKeyword),
4447
statusIn(statuses),
45-
titleContains(title))
48+
titleContains(title),
49+
joinedByMemberId(joinedMemberId))
4650
.orderBy(getOrderSpecifiers(sort))
4751
.offset((long)criteria.getPage() * criteria.getLimit())
4852
.limit(criteria.getLimit())
@@ -56,7 +60,8 @@ public Long countCallvanPosts(
5660
List<CallvanLocation> arrivals,
5761
String arrivalKeyword,
5862
List<CallvanStatus> statuses,
59-
String title
63+
String title,
64+
Integer joinedMemberId
6065
) {
6166
return queryFactory
6267
.select(callvanPost.count())
@@ -66,7 +71,8 @@ public Long countCallvanPosts(
6671
departureFilter(departures, departureKeyword),
6772
arrivalFilter(arrivals, arrivalKeyword),
6873
statusIn(statuses),
69-
titleContains(title))
74+
titleContains(title),
75+
joinedByMemberId(joinedMemberId))
7076
.fetchOne();
7177
}
7278

@@ -132,6 +138,22 @@ private BooleanExpression titleContains(String title) {
132138
return (title != null && !title.isBlank()) ? callvanPost.title.contains(title) : null;
133139
}
134140

141+
private BooleanExpression joinedByMemberId(Integer joinedMemberId) {
142+
if (joinedMemberId == null) {
143+
return null;
144+
}
145+
146+
return JPAExpressions
147+
.selectOne()
148+
.from(callvanParticipant)
149+
.where(
150+
callvanParticipant.post.id.eq(callvanPost.id),
151+
callvanParticipant.member.id.eq(joinedMemberId),
152+
callvanParticipant.isDeleted.isFalse()
153+
)
154+
.exists();
155+
}
156+
135157
private OrderSpecifier<?>[] getOrderSpecifiers(CallvanPostSortCriteria sort) {
136158
if (sort == null) {
137159
return new OrderSpecifier[] {

src/main/java/in/koreatech/koin/domain/callvan/service/CallvanPostQueryService.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,20 +53,22 @@ public CallvanPostSearchResponse getCallvanPosts(
5353
List<CallvanPostStatusFilter> statusFilters,
5454
String title,
5555
CallvanPostSortCriteria sort,
56+
Boolean isJoined,
5657
Integer page,
5758
Integer limit,
5859
Integer userId
5960
) {
6061
Integer authorId = authorFilter.getRequiredAuthorId(userId);
62+
Integer joinedMemberId = getJoinedMemberId(isJoined, userId);
6163
List<CallvanStatus> statuses = CallvanPostStatusFilter.toStatuses(statusFilters);
6264

6365
Long totalCount = callvanPostQueryRepository.countCallvanPosts(
64-
authorId, departures, departureKeyword, arrivals, arrivalKeyword, statuses, title);
66+
authorId, departures, departureKeyword, arrivals, arrivalKeyword, statuses, title, joinedMemberId);
6567

6668
Criteria criteria = Criteria.of(page, limit, totalCount.intValue());
6769

6870
List<CallvanPost> posts = callvanPostQueryRepository.findCallvanPosts(
69-
authorId, departures, departureKeyword, arrivals, arrivalKeyword, statuses, title, sort,
71+
authorId, departures, departureKeyword, arrivals, arrivalKeyword, statuses, title, joinedMemberId, sort,
7072
criteria);
7173

7274
int totalPage = (int)Math.ceil((double)totalCount / criteria.getLimit());
@@ -94,6 +96,13 @@ public CallvanPostSearchResponse getCallvanPosts(
9496
userId);
9597
}
9698

99+
private Integer getJoinedMemberId(Boolean isJoined, Integer userId) {
100+
if (Boolean.TRUE.equals(isJoined) && userId != null) {
101+
return userId;
102+
}
103+
return null;
104+
}
105+
97106
public CallvanPostDetailResponse getCallvanPostDetail(Integer postId, Integer userId) {
98107
if (!callvanParticipantRepository.existsByPostIdAndMemberIdAndIsDeletedFalse(postId, userId)) {
99108
throw CustomException.of(ApiResponseCode.FORBIDDEN_PARTICIPANT);
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package in.koreatech.koin.domain.community.article.dto;
2+
3+
import java.time.LocalDateTime;
4+
5+
public record BusArticleProjection(
6+
Integer id,
7+
String title,
8+
LocalDateTime createdAt
9+
) {
10+
11+
}

src/main/java/in/koreatech/koin/domain/community/article/model/redis/BusNoticeArticle.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
package in.koreatech.koin.domain.community.article.model.redis;
22

3-
import in.koreatech.koin.domain.community.article.model.Article;
43
import org.springframework.data.annotation.Id;
4+
import org.springframework.data.redis.core.RedisHash;
5+
56
import lombok.Builder;
67
import lombok.Getter;
7-
import org.springframework.data.redis.core.RedisHash;
88

99
@Getter
1010
@RedisHash(value = "busNoticeArticle")
@@ -21,10 +21,10 @@ private BusNoticeArticle(Integer id, String title) {
2121
this.title = title;
2222
}
2323

24-
public static BusNoticeArticle from(Article article) {
24+
public static BusNoticeArticle of(int id, String title) {
2525
return BusNoticeArticle.builder()
26-
.id(article.getId())
27-
.title(article.getTitle())
28-
.build();
26+
.id(id)
27+
.title(title)
28+
.build();
2929
}
3030
}

src/main/java/in/koreatech/koin/domain/community/article/repository/ArticleRepository.java

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import org.springframework.data.repository.Repository;
1515
import org.springframework.data.repository.query.Param;
1616

17+
import in.koreatech.koin.domain.community.article.dto.BusArticleProjection;
1718
import in.koreatech.koin.domain.community.article.exception.ArticleNotFoundException;
1819
import in.koreatech.koin.domain.community.article.exception.BoardNotFoundException;
1920
import in.koreatech.koin.domain.community.article.model.Article;
@@ -184,8 +185,19 @@ List<Article> findAllByRegisteredAtIsAfterExcludingBoardId(@Param("registeredAt"
184185
@Query("SELECT a.title FROM Article a WHERE a.id = :id")
185186
String getTitleById(@Param("id") Integer id);
186187

187-
@Query(value = "SELECT * FROM new_articles a "
188-
+ "WHERE a.title REGEXP '통학버스|등교버스|셔틀버스|하교버스' AND a.is_notice = true "
189-
+ "ORDER BY a.created_at DESC LIMIT 5", nativeQuery = true)
190-
List<Article> findBusArticlesTop5OrderByCreatedAtDesc();
188+
@Query("""
189+
SELECT new in.koreatech.koin.domain.community.article.dto.BusArticleProjection(
190+
a.id, a.title, a.createdAt
191+
)
192+
FROM Article a
193+
WHERE (
194+
a.title LIKE '%통학버스%'
195+
OR a.title LIKE '%등교버스%'
196+
OR a.title LIKE '%셔틀버스%'
197+
OR a.title LIKE '%하교버스%'
198+
)
199+
AND a.isNotice = true
200+
ORDER BY a.createdAt DESC
201+
""")
202+
List<BusArticleProjection> findBusArticlesTop5OrderByCreatedAtDesc(Pageable pageable);
191203
}

src/main/java/in/koreatech/koin/domain/community/article/service/ArticleSyncService.java

Lines changed: 31 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,21 @@
11
package in.koreatech.koin.domain.community.article.service;
22

3+
import java.time.Clock;
4+
import java.time.LocalDate;
5+
import java.time.LocalDateTime;
6+
import java.util.HashMap;
7+
import java.util.List;
8+
import java.util.Map;
9+
import java.util.Optional;
10+
import java.util.Set;
11+
12+
import org.springframework.data.domain.PageRequest;
13+
import org.springframework.data.redis.core.RedisTemplate;
14+
import org.springframework.data.redis.core.ZSetOperations.TypedTuple;
15+
import org.springframework.stereotype.Service;
16+
import org.springframework.transaction.annotation.Transactional;
17+
18+
import in.koreatech.koin.domain.community.article.dto.BusArticleProjection;
319
import in.koreatech.koin.domain.community.article.model.Article;
420
import in.koreatech.koin.domain.community.article.model.ArticleSearchKeyword;
521
import in.koreatech.koin.domain.community.article.model.ArticleSearchKeywordIpMap;
@@ -14,20 +30,6 @@
1430
import lombok.RequiredArgsConstructor;
1531
import lombok.extern.slf4j.Slf4j;
1632

17-
import org.springframework.data.redis.core.RedisTemplate;
18-
import org.springframework.data.redis.core.ZSetOperations.TypedTuple;
19-
import org.springframework.stereotype.Service;
20-
import org.springframework.transaction.annotation.Transactional;
21-
22-
import java.time.Clock;
23-
import java.time.LocalDate;
24-
import java.time.LocalDateTime;
25-
import java.util.HashMap;
26-
import java.util.List;
27-
import java.util.Map;
28-
import java.util.Optional;
29-
import java.util.Set;
30-
3133
@Slf4j
3234
@Service
3335
@RequiredArgsConstructor
@@ -75,10 +77,11 @@ public void updateHotArticles() {
7577

7678
@Transactional
7779
public void updateBusNoticeArticle() {
78-
List<Article> articles = articleRepository.findBusArticlesTop5OrderByCreatedAtDesc();
79-
LocalDate latestDate = articles.get(0).getCreatedAt().toLocalDate();
80-
List<Article> latestArticles = articles.stream()
81-
.filter(article -> article.getCreatedAt().toLocalDate().isEqual(latestDate))
80+
List<BusArticleProjection> articles = articleRepository.findBusArticlesTop5OrderByCreatedAtDesc(
81+
PageRequest.of(0, 5));
82+
LocalDate latestDate = articles.get(0).createdAt().toLocalDate();
83+
List<BusArticleProjection> latestArticles = articles.stream()
84+
.filter(article -> article.createdAt().toLocalDate().isEqual(latestDate))
8285
.toList();
8386

8487
if (latestArticles.size() >= 2) {
@@ -88,21 +91,23 @@ public void updateBusNoticeArticle() {
8891
int secondWeight = 0;
8992

9093
// 제목(title)에 "사과"가 들어가면 후순위, "긴급"이 포함되면 우선순위
91-
if (first.getTitle().contains("사과"))
94+
if (first.title().contains("사과"))
9295
firstWeight++;
93-
if (first.getTitle().contains("긴급"))
96+
if (first.title().contains("긴급"))
9497
firstWeight--;
9598

96-
if (second.getTitle().contains("사과"))
99+
if (second.title().contains("사과"))
97100
secondWeight++;
98-
if (second.getTitle().contains("긴급"))
101+
if (second.title().contains("긴급"))
99102
secondWeight--;
100103

101104
return Integer.compare(firstWeight, secondWeight);
102105
})
103106
.toList();
104107
}
105-
busArticleRepository.save(BusNoticeArticle.from(latestArticles.get(0)));
108+
109+
BusArticleProjection latestArticle = latestArticles.get(0);
110+
busArticleRepository.save(BusNoticeArticle.of(latestArticle.id(), latestArticle.title()));
106111
}
107112

108113
@Transactional
@@ -130,9 +135,10 @@ private void syncAllIpSearchCounts() {
130135
String ipAddress = ipKey.replace(IP_SEARCH_COUNT_PREFIX, "");
131136

132137
for (Map.Entry<Object, Object> entry : keywordSearchCounts.entrySet()) {
133-
String searchedKeyword = (String) entry.getKey();
138+
String searchedKeyword = (String)entry.getKey();
134139
int searchCount = Integer.parseInt(entry.getValue().toString());
135-
if (searchCount <= 0) continue;
140+
if (searchCount <= 0)
141+
continue;
136142

137143
articleSearchKeywordRepository.findByKeyword(searchedKeyword).ifPresent(keywordEntity -> {
138144
ipMapRepository.findByArticleSearchKeywordAndIpAddress(keywordEntity, ipAddress)

src/main/java/in/koreatech/koin/domain/community/keyword/repository/UserNotificationStatusRepository.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,11 @@ List<Integer> findUserIdsByNotifiedArticleIdAndUserIdIn(
3232

3333
@Modifying(flushAutomatically = true, clearAutomatically = true)
3434
@Query(value = """
35-
INSERT INTO user_notification_status (user_id, last_notified_article_id)
36-
VALUES (:userId, :notifiedArticleId)
37-
ON DUPLICATE KEY UPDATE last_notified_article_id = :notifiedArticleId
35+
INSERT INTO user_notification_status (user_id, last_notified_article_id, created_at, updated_at)
36+
VALUES (:userId, :notifiedArticleId, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
37+
ON DUPLICATE KEY UPDATE
38+
last_notified_article_id = :notifiedArticleId,
39+
updated_at = CURRENT_TIMESTAMP
3840
""", nativeQuery = true)
3941
void upsertLastNotifiedArticleId(
4042
@Param("userId") Integer userId,

0 commit comments

Comments
 (0)