Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
33ec82c
Merge pull request #2198 from BCSDLab/hotfix/2196-express-bus-timetable
dh2906 Mar 24, 2026
e54717d
fix: 버스 공지 n+1 쿼리 개선 (#2199)
kih1015 Mar 24, 2026
5479530
fix: Activity API 삭제 (#2185)
Soundbar91 Apr 1, 2026
e55fe83
fix: Member, Track, Tech API 삭제 (#2186)
Soundbar91 Apr 1, 2026
f1013db
feat: 콜벤 알림 FCM 메시지 추가 & 이용 제한 내역 조회 (#2212)
DHkimgit Apr 8, 2026
674e3a0
chore: 키워드 알림 주석 처리 (#2213)
Soundbar91 Apr 8, 2026
b180c94
fix: 키워드 페이징 조회를 단건 조회로 수정 (#2216)
Soundbar91 Apr 8, 2026
19e645f
fix: 천안역 → 학교 방향 시내버스 시간표 조회 오류 수정 (#2215)
kih1015 Apr 9, 2026
fc866c3
chore: 키워드 알림 테스트를 위한 주석 처리 롤백 (#2217)
Soundbar91 Apr 13, 2026
3fb62e9
Merge branch 'develop' into hotfix/admin-land-create-request
Soundbar91 Apr 14, 2026
201c7e8
Merge pull request #2222 from BCSDLab/hotfix/admin-land-create-request
Soundbar91 Apr 14, 2026
8a22867
Merge branch 'develop' into hotfix/admin-land-update
Soundbar91 Apr 14, 2026
eaa098f
Merge pull request #2224 from BCSDLab/hotfix/admin-land-update
Soundbar91 Apr 14, 2026
8fe0d9c
feat: 교내 시설물 아이콘 URL 서빙 (#2227)
DHkimgit Apr 28, 2026
6124ad9
feat: 분실물 키워드 알림 구독 Enum 추가 (#2232)
Soundbar91 May 6, 2026
8815344
feat: 키워드 알림 카테고리 Enum 추가 (#2230)
Soundbar91 May 6, 2026
ae497bc
feat: 분실물 키워드 알림 로직 분리 (#2234)
Soundbar91 May 7, 2026
d7d3fbf
fix: README 업데이트 및 팀 기반 리뷰어 매칭 삭제 (#2225)
Soundbar91 May 7, 2026
ff6fb80
fix: 키워드 알림 스레드 점유 문제 개선 (#2236)
Soundbar91 May 8, 2026
3b1b9a5
Merge main hotfixes into develop
dh2906 May 14, 2026
915ffe1
Merge pull request #2247 from BCSDLab/sync/main-hotfix-to-develop
dh2906 May 14, 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
12 changes: 12 additions & 0 deletions .github/auto_assign.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
addAssignees: author
numberOfReviewers: 2
addReviewers: true
reviewers:
- BaeJinho4028
- Soundbar91
- ImTotem
- kih1015
- DHkimgit
- taejinn
- dh2906
- JanooGwan
7 changes: 0 additions & 7 deletions .github/auto_assign_team_business.yml

This file was deleted.

7 changes: 0 additions & 7 deletions .github/auto_assign_team_campus.yml

This file was deleted.

7 changes: 0 additions & 7 deletions .github/auto_assign_team_user.yml

This file was deleted.

2 changes: 1 addition & 1 deletion .github/workflows/flyway-dangerous-sql-validator.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:

for file in $(git diff --name-only "$BASE" "$HEAD" -- src/main/resources/db/migration/*.sql); do
ADDED_SQL=$(git diff "$BASE" "$HEAD" -- "$file" \
| grep -i -wE "^\+.*\b(DROP|TRUNCATE|RENAME|CHANGE)\b" || true)
| grep -i -wE "^\+.*\b(DROP COLUMN|TRUNCATE|RENAME|CHANGE)\b" || true)

if [[ -n "$ADDED_SQL" ]]; then
echo "::error::⚠️ 위험 SQL 감지됨"
Expand Down
38 changes: 5 additions & 33 deletions .github/workflows/pr-label-reviewer-assigner.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: "PR Label & Assign Reviewers"

on:
pull_request:
types: [opened, labeled]
types: [opened, labeled, ready_for_review]

jobs:
label_and_assign:
Expand All @@ -20,38 +20,10 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

# 3. 최신 PR 라벨 목록 조회
- name: Fetch current PR labels
id: get_labels
uses: actions/github-script@v6
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const { data: labels } = await github.rest.issues.listLabelsOnIssue({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.pull_request.number
});
return labels.map(l => l.name);

# 4. 라벨별 리뷰어 할당
- name: Assign Team Business
if: contains(steps.get_labels.outputs.result, 'Team Business')
uses: kentaro-m/auto-assign-action@v2.0.0
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
configuration-path: .github/auto_assign_team_business.yml

- name: Assign Team Campus
if: contains(steps.get_labels.outputs.result, 'Team Campus')
uses: kentaro-m/auto-assign-action@v2.0.0
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
configuration-path: .github/auto_assign_team_campus.yml

- name: Assign Team User
if: contains(steps.get_labels.outputs.result, 'Team User')
# 3. Team Members에서 랜덤 2명 리뷰어 할당 (Draft 제외)
- name: Assign Random Reviewers
if: github.event.pull_request.draft == false
uses: kentaro-m/auto-assign-action@v2.0.0
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
configuration-path: .github/auto_assign_team_user.yml
configuration-path: .github/auto_assign.yml
26 changes: 0 additions & 26 deletions .github/workflows/pr-reviewers-assigner.yml

This file was deleted.

35 changes: 28 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,19 +97,40 @@ src

---

### 🧑‍🧑‍🧒‍🧒 Team Members
<details>
<summary><h3>🧑‍🧑‍🧒‍🧒 Team Members</h3></summary>

<br/>

#### Active Members

| <img src="https://github.com/Soundbar91.png" width="130"> | <img src="https://github.com/dh2906.png" width="130"> | <img src="https://github.com/DHkimgit.png" width="130"> |
| :--: | :--: | :--: |
| [관규](https://github.com/Soundbar91) | [동훈](https://github.com/dh2906) | [두현](https://github.com/DHkimgit) |

| <img src="https://github.com/user-attachments/assets/7f3ce35a-0b5d-4755-b5c3-2baef4a2677f" width="130"> | <img src="https://github.com/user-attachments/assets/530d5b55-26a0-4b85-ac65-d0da0e5f895a" width="130"> | <img src="https://github.com/user-attachments/assets/1210988c-4ed0-4759-949c-b435bb81e803" width="130"> | <img src="https://github.com/user-attachments/assets/25e3bc7a-0c9e-4fca-97f8-6bbfb45079ee" width="130"> |
| <img src="https://github.com/ImTotem.png" width="130"> | <img src="https://github.com/BaeJinho4028.png" width="130"> | <img src="https://github.com/taejinn.png" width="130"> |
| :--: | :--: | :--: |
| [성빈](https://github.com/ImTotem) | [진호](https://github.com/BaeJinho4028) | [태진](https://github.com/taejinn) |

#### Former Members

| <img src="https://github.com/daheeParkk.png" width="130"> | <img src="https://github.com/songsunkook.png" width="130"> | <img src="https://github.com/seongjae6751.png" width="130"> | <img src="https://github.com/krSeonghyeon.png" width="130"> |
| :--: | :--: | :--: | :--: |
| [진호](https://github.com/BaeJinho4028) | [성현](https://github.com/krSeonghyeon) | [관규](https://github.com/Soundbar91) | [성빈](https://github.com/ImTotem) |
| [다희](https://github.com/daheeParkk) | [선권](https://github.com/songsunkook) | [성재](https://github.com/seongjae6751) | [성현](https://github.com/krSeonghyeon) |

| <img src="https://github.com/user-attachments/assets/d0ae5872-2ae6-45f8-aae8-fa053fa475e5" width="130"> | <img src="https://github.com/user-attachments/assets/b3373849-7ed8-46a3-81f2-fdae8e52d82a" width="130"> | <img src="https://github.com/user-attachments/assets/b217bbd3-9a6e-4262-abd3-46acb1fd3cbd" width="130"> | <img src="https://github.com/user-attachments/assets/6068f003-71cc-4119-93bf-97c9a27ed256" width="130"> |
| <img src="https://github.com/kwoo28.png" width="130"> | <img src="https://github.com/kih1015.png" width="130"> | <img src="https://github.com/duehee.png" width="130"> | <img src="https://github.com/dradnats1012.png" width="130"> |
| :--: | :--: | :--: | :--: |
| [정빈](https://github.com/duehee) | [현식](https://github.com/Choon0414) | [인화](https://github.com/kih1015) | [두현](https://github.com/DHkimgit) |
| [원경](https://github.com/kwoo28) | [인화](https://github.com/kih1015) | [정빈](https://github.com/duehee) | [준기](https://github.com/dradnats1012) |

| <img src="https://github.com/asa9874.png" width="130"> | <img src="https://github.com/taejinn.png" width="130"> | <img src="https://github.com/dh2906.png" width="130"> | <img src="https://github.com/dldb-chamchi.png" width="130"> |
| <img src="https://github.com/johnny19991006.png" width="130"> | <img src="https://github.com/Choi-JJunho.png" width="130"> | <img src="https://github.com/Invidam.png" width="130"> | <img src="https://github.com/20HyeonsuLee.png" width="130"> |
| :--: | :--: | :--: | :--: |
| [박종범](https://github.com/asa9874) | [박태진](https://github.com/taejinn) | [이동훈](https://github.com/dh2906) | [임아리](https://github.com/dldb-chamchi) |
| [준영](https://github.com/johnny19991006) | [준호](https://github.com/Choi-JJunho) | [한수](https://github.com/Invidam) | [현수](https://github.com/20HyeonsuLee) |

| <img src="https://github.com/Choon0414.png" width="130"> |
| :--: |
| [현식](https://github.com/Choon0414) |

</details>


---
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;

import in.koreatech.koin.admin.history.aop.AdminActivityLogging;
import in.koreatech.koin.admin.keyword.dto.AdminFilteredKeywordsResponse;
import in.koreatech.koin.admin.keyword.dto.AdminKeywordFilterRequest;
import in.koreatech.koin.domain.community.keyword.enums.KeywordCategory;
import in.koreatech.koin.global.auth.Auth;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
Expand All @@ -36,6 +38,7 @@ public interface AdminKeywordApi {
@AdminActivityLogging(domain = KEYWORDS)
ResponseEntity<Void> toggleKeywordFilter(
@Valid @RequestBody AdminKeywordFilterRequest request,
@RequestParam(value = "type", required = false, defaultValue = "KOREATECH") KeywordCategory keywordCategory,
@Auth(permit = {ADMIN}) Integer adminId
);

Expand All @@ -50,6 +53,7 @@ ResponseEntity<Void> toggleKeywordFilter(
@Operation(summary = "필터링 된 키워드 조회")
@GetMapping("/admin/articles/keyword/filtered")
ResponseEntity<AdminFilteredKeywordsResponse> getFilteredKeywords(
@RequestParam(value = "type", required = false, defaultValue = "KOREATECH") KeywordCategory keywordCategory,
@Auth(permit = {ADMIN}) Integer adminId
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import in.koreatech.koin.admin.history.aop.AdminActivityLogging;
import in.koreatech.koin.admin.keyword.dto.AdminFilteredKeywordsResponse;
import in.koreatech.koin.admin.keyword.dto.AdminKeywordFilterRequest;
import in.koreatech.koin.admin.keyword.service.AdminKeywordService;
import in.koreatech.koin.domain.community.keyword.enums.KeywordCategory;
import in.koreatech.koin.global.auth.Auth;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
Expand All @@ -29,17 +31,19 @@ public class AdminKeywordController implements AdminKeywordApi {
@AdminActivityLogging(domain = KEYWORDS)
public ResponseEntity<Void> toggleKeywordFilter(
@Valid @RequestBody AdminKeywordFilterRequest request,
@RequestParam(value = "type", required = false, defaultValue = "KOREATECH") KeywordCategory keywordCategory,
@Auth(permit = {ADMIN}) Integer adminId
) {
adminKeywordService.filterKeyword(request.keyword(), request.isFiltered());
adminKeywordService.filterKeyword(request.keyword(), request.isFiltered(), keywordCategory);
return ResponseEntity.ok().build();
}

@GetMapping("/filtered")
public ResponseEntity<AdminFilteredKeywordsResponse> getFilteredKeywords(
@RequestParam(value = "type", required = false, defaultValue = "KOREATECH") KeywordCategory keywordCategory,
@Auth(permit = {ADMIN}) Integer adminId
) {
AdminFilteredKeywordsResponse response = adminKeywordService.getFilteredKeywords();
AdminFilteredKeywordsResponse response = adminKeywordService.getFilteredKeywords(keywordCategory);
return ResponseEntity.ok(response);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,19 @@
import org.springframework.data.repository.Repository;

import in.koreatech.koin.domain.community.keyword.exception.ArticleKeywordNotFoundException;
import in.koreatech.koin.domain.community.keyword.enums.KeywordCategory;
import in.koreatech.koin.domain.community.keyword.model.ArticleKeyword;

public interface AdminArticleKeywordRepository extends Repository<ArticleKeyword, Integer> {

Optional<ArticleKeyword> findByKeyword(String keyword);
Optional<ArticleKeyword> findByKeywordAndCategory(String keyword, KeywordCategory category);

default ArticleKeyword getByKeyword(String keyword) {
return findByKeyword(keyword)
.orElseThrow(() -> ArticleKeywordNotFoundException.withDetail("keyword : " + keyword));
default ArticleKeyword getByKeywordAndCategory(String keyword, KeywordCategory category) {
return findByKeywordAndCategory(keyword, category)
.orElseThrow(() -> ArticleKeywordNotFoundException.withDetail(
"keyword : " + keyword + ", category : " + category
));
}

List<ArticleKeyword> findByIsFiltered(boolean isFiltered);
List<ArticleKeyword> findByIsFilteredAndCategory(boolean isFiltered, KeywordCategory category);
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import in.koreatech.koin.admin.keyword.repository.AdminArticleKeywordRepository;
import in.koreatech.koin.admin.keyword.dto.AdminFilteredKeywordsResponse;
import in.koreatech.koin.domain.community.keyword.enums.KeywordCategory;
import in.koreatech.koin.domain.community.keyword.model.ArticleKeyword;
import in.koreatech.koin.global.exception.custom.KoinIllegalArgumentException;
import lombok.RequiredArgsConstructor;
Expand All @@ -20,19 +21,22 @@ public class AdminKeywordService {
private final AdminArticleKeywordRepository adminArticleKeywordRepository;

@Transactional
public void filterKeyword(String keyword, Boolean isFiltered) {
ArticleKeyword articleKeyword = adminArticleKeywordRepository.getByKeyword(keyword);
public void filterKeyword(String keyword, Boolean isFiltered, KeywordCategory category) {
ArticleKeyword articleKeyword = adminArticleKeywordRepository.getByKeywordAndCategory(keyword, category);

if (Objects.equals(articleKeyword.getIsFiltered(), isFiltered)) {
String action = isFiltered ? "필터링 된" : "필터링이 취소된";
throw new KoinIllegalArgumentException("이미 " + action + " 키워드입니다: " + keyword);
throw new KoinIllegalArgumentException("이미 " + action + " 키워드입니다: " + keyword + ", category: " + category);
}

articleKeyword.applyFiltered(isFiltered);
}

public AdminFilteredKeywordsResponse getFilteredKeywords() {
List<ArticleKeyword> filteredKeywords = adminArticleKeywordRepository.findByIsFiltered(true);
public AdminFilteredKeywordsResponse getFilteredKeywords(KeywordCategory category) {
List<ArticleKeyword> filteredKeywords = adminArticleKeywordRepository.findByIsFilteredAndCategory(
true,
category
);
return AdminFilteredKeywordsResponse.from(filteredKeywords);
}
}
Loading
Loading