Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
d55651e
refactor(#87): 관리자 알림 구조 개선
tnals0924 Apr 14, 2025
0f2be1f
Merge branch 'main' into refactor/#87
tnals0924 May 18, 2025
cb4d46d
Merge pull request #93 from billilge/refactor/#87
tnals0924 May 18, 2025
eb2e6a9
feat(#92): 알림 모두 읽음 처리 API 구현
tnals0924 May 18, 2025
3354566
Merge pull request #94 from billilge/feat/#92
tnals0924 May 18, 2025
3ae13c3
refactor(#95): 관리자 대시보드 API 신청 시간을 대여시각으로 변경
tnals0924 May 18, 2025
12f5380
Merge pull request #96 from billilge/refactor/#95
tnals0924 May 18, 2025
a7446e3
feat: 액셀파일 생성 util 구현
tnals0924 May 20, 2025
1d45d1b
deploy(#97): 개발버전 서버 배포
tnals0924 May 20, 2025
3c3968f
Merge pull request #98 from billilge/deploy/#97
tnals0924 May 20, 2025
d7953cd
HOTFIX: redirect-url 설정
tnals0924 May 21, 2025
b835e46
Merge branch 'deploy/#97' into develop
tnals0924 May 21, 2025
42cbcc3
feat(#85): 학생회비 납부자 엑셀 다운로드 구현
tnals0924 May 22, 2025
2d54eda
Merge pull request #99 from billilge/feat/#85
tnals0924 May 22, 2025
d064985
Merge branch 'feat/#85' into develop
tnals0924 May 22, 2025
9a551b8
Merge branch 'develop' of https://github.com/billilge/backend into de…
tnals0924 May 22, 2025
6f2b083
HOTFIX: 납부자 엑셀 파일 깨지는 현상 수정
tnals0924 May 22, 2025
1730f1a
refactor: 엑셀 파일 형식 수정 및 형태 변경
tnals0924 May 22, 2025
9710917
chore: column sizing을 위한 수정
tnals0924 May 22, 2025
dc5aefd
fix: column 너비 값을 256 곱한 값으로 수정
tnals0924 May 22, 2025
67ee3cf
feat(#100): 토큰에 학생회비 납부 여부 정보 추가
tnals0924 May 22, 2025
2855f60
Merge pull request #101 from billilge/feat/#100
tnals0924 May 22, 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
68 changes: 68 additions & 0 deletions .github/workflows/ci-cd_dev.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
name: Development Server CI/CD with Gradle and Docker

on:
push:
branches: [ "develop" ]
pull_request:
branches: [ "develop" ]

jobs:
build-docker-image:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'

- name: Create Firebase Config Directory
run: |
mkdir -p src/main/resources/firebase
echo "${{ secrets.FIREBASE_SERVICE_KEY }}" | base64 --decode > src/main/resources/firebase/firebaseServiceKey.json
shell: bash

- name: Setup Gradle
uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 # v4.0.0

- name: Build with Gradle Wrapper
run: ./gradlew clean build -x test

# Docker 관련 작업들은 push 시에만 실행되도록 설정
- name: docker image build
if: github.event_name == 'push'
run: docker build -f Dockerfile-dev -t ${{ secrets.DOCKER_USERNAME }}/billilge-dev:latest .

# DockerHub 로그인
- name: docker login
if: github.event_name == 'push'
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}

# Docker Hub 이미지 푸시
- name: docker Hub push
if: github.event_name == 'push'
run: docker push ${{ secrets.DOCKER_USERNAME }}/billilge-dev:latest

run-docker-image-on-ec2:
needs: build-docker-image
#push 했을 때만 배포가 진행되도록
if: github.event_name == 'push'
runs-on: self-hosted
steps:
- name: docker pull
run: sudo docker pull ${{ secrets.DOCKER_USERNAME }}/billilge-dev:latest

- name: docker stop container
run: sudo docker stop springboot || true

- name: docker run new container
run: sudo docker run --env-file /home/ubuntu/billilge.env --name springboot --rm -d -p 8080:8080 ${{ secrets.DOCKER_USERNAME }}/billilge-dev:latest

- name: delete old docker image
run: sudo docker image prune -f
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: CI/CD with Gradle and Docker
name: Production Server CI/CD with Gradle and Docker

on:
push:
Expand Down
10 changes: 10 additions & 0 deletions Dockerfile-dev
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Dockerfile

# jdk17 Image Start
FROM openjdk:17

# jar 파일 복제
COPY build/libs/*.jar app.jar

# 실행 명령어
ENTRYPOINT ["java", "-Duser.timezone=Asia/Seoul", "-Dspring.profiles.active=dev", "-jar", "app.jar"]
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,19 @@ interface NotificationApi {
fun getNotificationCount(
@AuthenticationPrincipal userAuthInfo: UserAuthInfo
): ResponseEntity<NotificationCountResponse>

@Operation(
summary = "알림 모두 읽음 처리",
description = "사용자가 알림을 모두 읽음 처리합니다."
)
@ApiResponses(
value = [
ApiResponse(responseCode = "200", description = "알림 모두 읽음 처리 완료"),
ApiResponse(responseCode = "403", description = "권한이 없는 사용자"),
ApiResponse(responseCode = "404", description = "알림을 찾을 수 없음")
]
)
fun readAllNotifications(
@AuthenticationPrincipal userAuthInfo: UserAuthInfo
): ResponseEntity<Void>
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,13 @@ class NotificationController (
notificationService.readNotification(memberId, notificationId)
return ResponseEntity.ok().build()
}

@PatchMapping("/all")
override fun readAllNotifications(
@AuthenticationPrincipal userAuthInfo: UserAuthInfo
): ResponseEntity<Void> {
val memberId = userAuthInfo.memberId
notificationService.readAllNotifications(memberId)
return ResponseEntity.ok().build()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ import java.time.LocalDateTime
@Table(name = "notifications")
@EntityListeners(AuditingEntityListener::class)
class Notification(
@JoinColumn(name = "member_id", nullable = false)
@JoinColumn(name = "member_id", nullable = true)
@ManyToOne
val member: Member,
val member: Member? = null,

@Enumerated(EnumType.STRING)
@Column(name = "type", nullable = false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,18 @@ class NotificationService(

return NotificationFindAllResponse(
notifications
.map { NotificationDetail.from(it) })
.map { NotificationDetail.from(it) }
)
}

@Transactional
fun readNotification(memberId: Long?, notificationId: Long) {
val notification = notificationRepository.findById(notificationId)
.orElseThrow { ApiException(NotificationErrorCode.NOTIFICATION_NOT_FOUND) }

if (notification.member.id != memberId) {
if (notification.isAdminStatus()) return;

if (notification.member?.id != memberId) {
throw ApiException(NotificationErrorCode.NOTIFICATION_ACCESS_DENIED)
}

Expand All @@ -58,7 +61,7 @@ class NotificationService(
member: Member,
status: NotificationStatus,
formatValues: List<String>,
needPush: Boolean = false
needPush: Boolean = false,
) {
val notification = Notification(
member = member,
Expand All @@ -69,32 +72,50 @@ class NotificationService(
notificationRepository.save(notification)

if (needPush) {
val studentId = member.studentId
sendPushNotification(member, status, formatValues)
}
}

if (member.fcmToken == null) {
log.warn { "(studentId=${studentId}) FCM Token is null" }
return
}
private fun sendPushNotification(
member: Member,
status: NotificationStatus,
formatValues: List<String>,
) {
val studentId = member.studentId

fcmService.sendPushNotification(
member.fcmToken!!,
status.title,
status.formattedMessage(*formatValues.toTypedArray()),
status.link,
studentId
)
if (member.fcmToken == null) {
log.warn { "(studentId=${studentId}) FCM Token is null" }
return
}

fcmService.sendPushNotification(
member.fcmToken!!,
status.title,
status.formattedMessage(*formatValues.toTypedArray()),
status.link,
studentId
)
}

@Transactional
fun sendNotificationToAdmin(
type: NotificationStatus,
formatValues: List<String>,
needPush: Boolean = false
) {
val admins = memberRepository.findAllByRole(Role.ADMIN)

admins.forEach { admin ->
sendNotification(admin, type, formatValues, needPush)
val notification = Notification(
status = type,
formatValues = formatValues.joinToString(",")
)

notificationRepository.save(notification)

if (needPush) {
admins.forEach { admin ->
sendPushNotification(admin, type, formatValues)
}
}
}

Expand All @@ -103,4 +124,13 @@ class NotificationService(

return NotificationCountResponse(count)
}

@Transactional
fun readAllNotifications(memberId: Long?) {
notificationRepository
.findAllUserNotificationsByMemberId(memberId!!)
.forEach { it.readNotification() }
}

private fun Notification.isAdminStatus(): Boolean = status.name.contains("ADMIN", true)
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package site.billilge.api.backend.domain.payer.controller

import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.media.Content
import io.swagger.v3.oas.annotations.media.Schema
import io.swagger.v3.oas.annotations.responses.ApiResponse
import io.swagger.v3.oas.annotations.responses.ApiResponses
import io.swagger.v3.oas.annotations.tags.Tag
import org.springframework.core.io.InputStreamResource
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestParam
Expand Down Expand Up @@ -62,4 +65,22 @@ interface AdminPayerApi {
]
)
fun deletePayers(@RequestBody request: PayerDeleteRequest): ResponseEntity<Void>

@Operation(
summary = "학생회비 납부자 Excel 파일 다운로드",
description = "학생회비 납부자 데이터를 엑셀 파일로 다운받을 수 있는 관리자용 API"
)
@ApiResponses(
value = [
ApiResponse(
responseCode = "200",
description = "파일 다운로드 성공",
content = [Content(
mediaType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
schema = Schema(type = "string", format = "binary")
)]
)
]
)
fun createPayerExcel(): ResponseEntity<InputStreamResource>
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package site.billilge.api.backend.domain.payer.controller

import org.springframework.core.io.InputStreamResource
import org.springframework.http.ContentDisposition
import org.springframework.http.HttpHeaders
import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*
import site.billilge.api.backend.domain.payer.dto.request.PayerDeleteRequest
Expand All @@ -9,6 +13,8 @@ import site.billilge.api.backend.domain.payer.service.PayerService
import site.billilge.api.backend.global.annotation.OnlyAdmin
import site.billilge.api.backend.global.dto.PageableCondition
import site.billilge.api.backend.global.dto.SearchCondition
import java.time.LocalDate
import java.time.format.DateTimeFormatter

@RestController
@RequestMapping("/admin/members/payers")
Expand All @@ -35,4 +41,22 @@ class AdminPayerController(
payerService.deletePayers(request)
return ResponseEntity.noContent().build()
}

@GetMapping("/excel")
override fun createPayerExcel(): ResponseEntity<InputStreamResource> {
val excel = payerService.createPayerExcel()
val currentDate = LocalDate.now()
val dateFormatter = DateTimeFormatter.ofPattern("yyyyMMdd")
val headers = HttpHeaders().apply {
contentDisposition = ContentDisposition.builder("attachment")
.filename("kmusw_payers_${dateFormatter.format(currentDate)}.xlsx")
.build()
}
val excelMediaType = MediaType.valueOf("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")

return ResponseEntity.ok()
.headers(headers)
.contentType(excelMediaType)
.body(InputStreamResource((excel)))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,6 @@ interface PayerRepository : JpaRepository<Payer, Long?> {

@Query("SELECT p FROM Payer p WHERE p.name LIKE CONCAT('%', :name, '%')")
fun findAllByNameContaining(@Param("name") name: String, pageable: Pageable): Page<Payer>

fun findAllByEnrollmentYear(enrollmentYear: String): List<Payer>
}
Loading
Loading