Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
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
5 changes: 4 additions & 1 deletion .claude/settings.local.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@
"Bash(dir:*)",
"Bash(del /f \"C:\\Users\\haeun\\OneDrive\\Desktop\\BEProject\\konnect-back\\src\\main\\java\\com\\example\\konnect_backend\\domain\\auth\\dto\\request\\SignInRequest.java.bak\")",
"Bash(del /f \"C:\\Users\\haeun\\OneDrive\\Desktop\\BEProject\\konnect-back\\src\\main\\java\\com\\example\\konnect_backend\\domain\\ai\\service\\FileTranslationService.java\")",
"Bash(test:*)"
"Bash(test:*)",
"Bash(./gradlew bootRun:*)",
"Bash(curl:*)",
"Bash(./gradlew test:*)"
],
"deny": [],
"ask": []
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,7 @@ application-*.properties
# Google Vision API key
src/main/resources/google-key.json
google-key.json

src/main/resources/firebase/firebase-service-account.json
firebase-service-account.json
src/main/resources/firebase/*.json
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ dependencies {
// 캐싱
implementation 'com.github.ben-manes.caffeine:caffeine:3.1.8'
implementation 'org.springframework.boot:spring-boot-starter-cache'

// Firebase Admin SDK (FCM 푸시 알림)
implementation 'com.google.firebase:firebase-admin:9.2.0'
}

tasks.named('test') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableJpaAuditing
@EnableScheduling
public class KonnectBackendApplication {

public static void main(String[] args) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,14 @@ public class DifficultExpressionExtractorModule implements PromptModule<String,

## 중요
- original: 원본 한국어 표현 그대로
- explanation: %s로 쉽게 풀어서 설명 (간단명료하게)
- explanation: %s로 쉽게 풀어서 설명 (간단명료하게, 순수 텍스트만)
- 최대 10개까지만 추출
- 정말 어려운 표현만 선별

## 출력 형식 규칙 (필수)
- 마크다운 문법 사용 금지 (###, **, *, -, |, 표 등 사용하지 않기)
- explanation 필드는 순수 텍스트로만 작성

## 분석할 텍스트
%s

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ public class DocumentClassifierModule implements PromptModule<String, Classifica
## 분석할 텍스트
%s

## 출력 형식 규칙 (필수)
- 마크다운 문법 사용 금지 (###, **, *, -, |, 표 등 사용하지 않기)
- 모든 텍스트 필드는 순수 텍스트로만 작성

## 응답 형식 (반드시 아래 JSON 형식으로만 출력, 다른 텍스트 없이)
분류 과정을 단계별로 설명해주세요:
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ public class KoreanSimplifierModule implements PromptModule<String, String> {
- 문서의 전체 내용과 구조는 유지
- 의미가 달라지지 않도록 주의

## 출력 형식 규칙 (필수)
- 마크다운 문법 사용 금지 (###, **, *, -, |, 표 등 사용하지 않기)
- 순수 텍스트로만 작성
- 줄바꿈은 허용하되, 특수 기호나 서식 없이 일반 문장으로 작성

## 원본 텍스트
%s

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ public class SummarizerModule implements PromptModule<String, String> {
- 학부모가 바로 이해할 수 있게 명확하게 작성
- %s로 요약문만 출력하고 다른 설명은 하지 마세요

## 출력 형식 규칙 (필수)
- 마크다운 문법 사용 금지 (###, **, *, -, |, 표 등 사용하지 않기)
- 순수 텍스트로만 작성
- 줄바꿈은 허용하되, 특수 기호나 서식 없이 일반 문장으로 작성

## 번역문
%s

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ public class TranslatorModule implements PromptModule<String, String> {
- 문단 구조 유지
- 번역문만 출력하고 다른 설명은 하지 마세요

## 출력 형식 규칙 (필수)
- 마크다운 문법 사용 금지 (###, **, *, -, |, 표 등 사용하지 않기)
- 순수 텍스트로만 작성
- 줄바꿈은 허용하되, 특수 기호나 서식 없이 일반 문장으로 작성

## 원문
%s

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ public class UnifiedExtractorModule implements PromptModule<String, ExtractionRe
- 모든 텍스트 필드는 반드시 %s로 작성해주세요
- 원본이 한국어여도 %s로 번역하여 출력

## 출력 형식 규칙 (필수)
- 마크다운 문법 사용 금지 (###, **, *, -, |, 표 등 사용하지 않기)
- 모든 텍스트 필드는 순수 텍스트로만 작성

## 오늘 날짜 (연도 추론에 활용)
%s

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package com.example.konnect_backend.domain.notification.controller;

import com.example.konnect_backend.domain.notification.dto.request.FcmTokenRequest;
import com.example.konnect_backend.domain.notification.dto.response.NotificationListResponse;
import com.example.konnect_backend.domain.notification.dto.response.UnreadCountResponse;
import com.example.konnect_backend.domain.notification.service.NotificationService;
import com.example.konnect_backend.global.ApiResponse;
import com.example.konnect_backend.global.security.SecurityUtil;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/notifications")
@RequiredArgsConstructor
@Tag(name = "Notification", description = "알림 관리 API")
@SecurityRequirement(name = "bearerAuth")
public class NotificationController {

private final NotificationService notificationService;

@PostMapping("/fcm-token")
@Operation(summary = "FCM 토큰 등록", description = "푸시 알림을 위한 FCM 토큰을 등록합니다.")
@ApiResponses(value = {
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "토큰 등록 성공"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "401", description = "인증 실패",
content = @Content(schema = @Schema(implementation = ApiResponse.class)))
})
public ApiResponse<String> registerFcmToken(@Valid @RequestBody FcmTokenRequest fcmTokenRequest) {
Long userId = SecurityUtil.getCurrentUserIdOrNull();
notificationService.registerFcmToken(userId, fcmTokenRequest);
return ApiResponse.onSuccess("FCM 토큰이 등록되었습니다.");
}

@DeleteMapping("/fcm-token")
@Operation(summary = "FCM 토큰 삭제", description = "로그아웃 시 FCM 토큰을 삭제합니다.")
@ApiResponses(value = {
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "토큰 삭제 성공"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "401", description = "인증 실패",
content = @Content(schema = @Schema(implementation = ApiResponse.class)))
})
public ApiResponse<String> removeFcmToken(
@Parameter(description = "삭제할 FCM 토큰", required = true)
@RequestParam String token) {
notificationService.removeFcmToken(token);
return ApiResponse.onSuccess("FCM 토큰이 삭제되었습니다.");
}

@GetMapping
@Operation(summary = "알림 목록 조회", description = "사용자의 알림 목록을 페이징하여 조회합니다.")
@ApiResponses(value = {
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "조회 성공"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "401", description = "인증 실패",
content = @Content(schema = @Schema(implementation = ApiResponse.class)))
})
public ApiResponse<NotificationListResponse> getNotifications(
@Parameter(description = "페이지 번호 (0부터 시작)", example = "0")
@RequestParam(defaultValue = "0") int page,
@Parameter(description = "페이지 크기", example = "20")
@RequestParam(defaultValue = "20") int size) {
Long userId = SecurityUtil.getCurrentUserIdOrNull();
return ApiResponse.onSuccess(notificationService.getNotifications(userId, page, size));
}

@GetMapping("/unread-count")
@Operation(summary = "읽지 않은 알림 개수 조회", description = "읽지 않은 알림의 개수를 조회합니다.")
@ApiResponses(value = {
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "조회 성공"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "401", description = "인증 실패",
content = @Content(schema = @Schema(implementation = ApiResponse.class)))
})
public ApiResponse<UnreadCountResponse> getUnreadCount() {
Long userId = SecurityUtil.getCurrentUserIdOrNull();
return ApiResponse.onSuccess(notificationService.getUnreadCount(userId));
}

@PatchMapping("/{notificationId}/read")
@Operation(summary = "알림 읽음 처리", description = "특정 알림을 읽음 처리합니다.")
@ApiResponses(value = {
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "읽음 처리 성공"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "401", description = "인증 실패",
content = @Content(schema = @Schema(implementation = ApiResponse.class))),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "403", description = "권한 없음",
content = @Content(schema = @Schema(implementation = ApiResponse.class))),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "알림을 찾을 수 없음",
content = @Content(schema = @Schema(implementation = ApiResponse.class)))
})
public ApiResponse<String> markAsRead(
@Parameter(description = "알림 ID", required = true, example = "1")
@PathVariable Long notificationId) {
Long userId = SecurityUtil.getCurrentUserIdOrNull();
notificationService.markAsRead(userId, notificationId);
return ApiResponse.onSuccess("알림이 읽음 처리되었습니다.");
}

@PatchMapping("/read-all")
@Operation(summary = "모든 알림 읽음 처리", description = "사용자의 모든 알림을 읽음 처리합니다.")
@ApiResponses(value = {
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "읽음 처리 성공"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "401", description = "인증 실패",
content = @Content(schema = @Schema(implementation = ApiResponse.class)))
})
public ApiResponse<String> markAllAsRead() {
Long userId = SecurityUtil.getCurrentUserIdOrNull();
notificationService.markAllAsRead(userId);
return ApiResponse.onSuccess("모든 알림이 읽음 처리되었습니다.");
}

@DeleteMapping("/{notificationId}")
@Operation(summary = "알림 삭제", description = "특정 알림을 삭제합니다.")
@ApiResponses(value = {
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "삭제 성공"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "401", description = "인증 실패",
content = @Content(schema = @Schema(implementation = ApiResponse.class))),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "403", description = "권한 없음",
content = @Content(schema = @Schema(implementation = ApiResponse.class))),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "알림을 찾을 수 없음",
content = @Content(schema = @Schema(implementation = ApiResponse.class)))
})
public ApiResponse<String> deleteNotification(
@Parameter(description = "알림 ID", required = true, example = "1")
@PathVariable Long notificationId) {
Long userId = SecurityUtil.getCurrentUserIdOrNull();
notificationService.deleteNotification(userId, notificationId);
return ApiResponse.onSuccess("알림이 삭제되었습니다.");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.example.konnect_backend.domain.notification.dto.request;

import jakarta.validation.constraints.NotBlank;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class FcmTokenRequest {

@NotBlank(message = "FCM 토큰은 필수입니다")
private String token;

private String deviceId;

private String deviceType; // iOS, Android 등
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.example.konnect_backend.domain.notification.dto.response;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.util.List;

@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class NotificationListResponse {

private List<NotificationResponse> notifications;
private long unreadCount;
private int page;
private int size;
private long totalElements;
private int totalPages;
private boolean hasNext;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.example.konnect_backend.domain.notification.dto.response;

import com.example.konnect_backend.domain.notification.entity.Notification;
import com.example.konnect_backend.domain.notification.entity.NotificationType;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;

@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class NotificationResponse {

private Long id;
private String title;
private String body;
private NotificationType type;
private Long referenceId;
private Boolean isRead;

@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
private LocalDateTime createdAt;

public static NotificationResponse from(Notification notification) {
return NotificationResponse.builder()
.id(notification.getId())
.title(notification.getTitle())
.body(notification.getBody())
.type(notification.getType())
.referenceId(notification.getReferenceId())
.isRead(notification.getIsRead())
.createdAt(notification.getCreatedAt())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.example.konnect_backend.domain.notification.dto.response;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UnreadCountResponse {
private long unreadCount;
}
Loading