Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ out/
### VS Code ###
.vscode/

/src/main/resources/*.env
*.env
*.yml

/src/main/generated/
Expand Down
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
implementation 'org.springframework.boot:spring-boot-starter-webflux'
implementation 'com.fasterxml.jackson.core:jackson-databind'
implementation 'org.springframework.boot:spring-boot-starter-validation'

compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.mysql:mysql-connector-j'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public CustomResponse<PostResDTO.PostPreviewDTO> getPost(@PathVariable Long post
public CustomResponse<PostResDTO.PostPreviewListDTO> getPostList(
@PathVariable BoardType boardType,
@RequestParam(required = false) Long cursor,
@RequestParam(defaultValue = "10") @Max(30) int size) {
@RequestParam(defaultValue = "10") @Max(10) int size) {
PostResDTO.PostPreviewListDTO resDTO = postQueryService.getPostList(boardType, cursor, size);

return CustomResponse.onSuccess(GeneralSuccessCode.OK, resDTO);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import naughty.tuzamate.domain.user.entity.User;

import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

@NoArgsConstructor(access = AccessLevel.PRIVATE)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import naughty.tuzamate.global.error.GeneralErrorCode;
import naughty.tuzamate.global.error.exception.CustomException;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
Expand Down Expand Up @@ -36,20 +37,31 @@ public PostResDTO.PostPreviewDTO getPost(Long postId) {
@Override
@Transactional(readOnly = true)
public PostResDTO.PostPreviewListDTO getPostList(BoardType boardType, Long cursor, int size) {
PageRequest pr = PageRequest.of(0, size);
Slice<Post> slice = postRepository.findByBoardTypeAndCursor(boardType, cursor, pr);
cursor = normalizeCursor(cursor);
size = normalizeSize(size);

List<PostResDTO.PostPreviewDTO> previews = slice.getContent()
.stream()
Pageable pageable = PageRequest.of(0, size);
Slice<Post> slice = postRepository.findByBoardTypeAndCursor(boardType, cursor, pageable);

List<PostResDTO.PostPreviewDTO> previews = slice.stream()
.map(PostConverter::toPostPreviewDTO)
.toList();

Long nextCursor = slice.hasNext() ? previews.get(previews.size() - 1).id() : null;
Long nextCursor = (slice.hasNext() && !previews.isEmpty()) ? previews.get(previews.size() - 1).id() : null;

return PostResDTO.PostPreviewListDTO.builder()
.postPreviewDTOList(previews)
.nextCursor(nextCursor)
.hasNext(slice.hasNext())
.build();
}

private Long normalizeCursor(Long cursor) {
return (cursor == null || cursor == 0) ? Long.MAX_VALUE : cursor;
}

// 요청 사이즈가 1보다 작으면 기본값 10, 10보다 크면 최대값 10으로 제한
private int normalizeSize(int size) {
return (size < 1 || size > 10) ? 10 : size;
}
}
23 changes: 18 additions & 5 deletions src/main/java/naughty/tuzamate/global/config/FireBaseConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,30 +9,43 @@
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;

import java.io.FileInputStream;

import java.io.IOException;
import java.io.InputStream;

@Slf4j
@Configuration
public class FireBaseConfig {

// 🚨 수정 포인트 1: String 대신 Resource 타입으로 주입받습니다.
@Value("${firebase.service-account.path}")
private String SERVICE_ACCOUNT_PATH;
private Resource serviceAccountResource;

Comment on lines +23 to 26
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Ensure property resolves to a valid Resource (prevent NPE at startup)

If ${firebase.service-account.path} is missing or invalid, serviceAccountResource may be null and cause a NullPointerException before the IOException catch. Add a null check to fail fast with a clear message.

Optionally add this at the top of firebaseApp():

+        Objects.requireNonNull(
+                serviceAccountResource,
+                "Property 'firebase.service-account.path' must resolve to a valid Resource (e.g., classpath:/..., file:/...)"
+        );

And add the import:

import java.util.Objects;

To verify configuration is present, run:


🏁 Script executed:

#!/bin/bash
# Verify the firebase service account property is defined in configuration files
rg -n -C2 --glob '!**/build/**' --glob '!**/out/**' 'firebase\.service-account\.path'

Length of output: 557


🏁 Script executed:

#!/bin/bash
set -e
sed -n '1,240p' src/main/java/naughty/tuzamate/global/config/FireBaseConfig.java || true

Length of output: 2044


Fail-fast if firebase.service-account.path doesn't resolve (prevent NPE at startup)

serviceAccountResource is injected and used immediately via getInputStream(); if the property is missing/invalid the field can be null and cause an NPE. Add a null (and optionally existence/readability) check before use.

  • File: src/main/java/naughty/tuzamate/global/config/FireBaseConfig.java — in firebaseApp(), before calling serviceAccountResource.getInputStream()

Suggested change (minimal):

+import java.util.Objects;
 ...
     public FirebaseApp firebaseApp() {
+        Objects.requireNonNull(
+                serviceAccountResource,
+                "Property 'firebase.service-account.path' must resolve to a valid Resource (e.g., classpath:/..., file:/...)"
+        );
         try {
             // 주입받은 Resource에서 바로 InputStream을 얻음
             InputStream serviceAccount = serviceAccountResource.getInputStream();

Optionally also check serviceAccountResource.exists() / isReadable() and throw a clear IllegalStateException if false.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// String 대신 Resource 타입으로 주입받음
@Value("${firebase.service-account.path}")
private String SERVICE_ACCOUNT_PATH;
private Resource serviceAccountResource;
// (add near other imports)
import java.util.Objects;
// String 대신 Resource 타입으로 주입받음
@Value("${firebase.service-account.path}")
private Resource serviceAccountResource;
@Bean
public FirebaseApp firebaseApp() {
Objects.requireNonNull(
serviceAccountResource,
"Property 'firebase.service-account.path' must resolve to a valid Resource (e.g., classpath:/..., file:/...)"
);
try {
// 주입받은 Resource에서 바로 InputStream을 얻음
InputStream serviceAccount = serviceAccountResource.getInputStream();
FirebaseOptions options = FirebaseOptions.builder()
.setCredentials(GoogleCredentials.fromStream(serviceAccount))
.build();
if (FirebaseApp.getApps().isEmpty()) {
log.info("Successfully initialized firebase app");
return FirebaseApp.initializeApp(options);
} else {
return FirebaseApp.getInstance();
}
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
🤖 Prompt for AI Agents
In src/main/java/naughty/tuzamate/global/config/FireBaseConfig.java around lines
23 to 26, the injected serviceAccountResource may be null or unreadable which
will cause a NullPointerException when calling
serviceAccountResource.getInputStream(); before invoking getInputStream() add a
fail-fast guard that checks serviceAccountResource != null and optionally
serviceAccountResource.exists() and serviceAccountResource.isReadable(), and if
any check fails throw a clear IllegalStateException (including the property key
and resolved value/path) so startup fails with a helpful message rather than an
NPE.

@Bean
public FirebaseApp firebaseApp() {
try (FileInputStream serviceAccount = new FileInputStream(SERVICE_ACCOUNT_PATH)) {
try {
// 🚨 수정 포인트 2: 주입받은 Resource에서 바로 InputStream을 얻습니다.
InputStream serviceAccount = serviceAccountResource.getInputStream();

FirebaseOptions options = FirebaseOptions.builder()
.setCredentials(GoogleCredentials.fromStream(serviceAccount))
.build();
Comment on lines +29 to 35
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Close the service account InputStream via try-with-resources

The InputStream should be closed to avoid resource leaks. Wrap it in a try-with-resources.

-        try {
-            // 🚨 수정 포인트 2: 주입받은 Resource에서 바로 InputStream을 얻습니다.
-            InputStream serviceAccount = serviceAccountResource.getInputStream();
-
-            FirebaseOptions options = FirebaseOptions.builder()
-                    .setCredentials(GoogleCredentials.fromStream(serviceAccount))
-                    .build();
+        try {
+            // 🚨 수정 포인트 2: 주입받은 Resource에서 바로 InputStream을 얻습니다.
+            try (InputStream serviceAccount = serviceAccountResource.getInputStream()) {
+                FirebaseOptions options = FirebaseOptions.builder()
+                        .setCredentials(GoogleCredentials.fromStream(serviceAccount))
+                        .build();

And close the additional brace at the end of the init block:

-            if (FirebaseApp.getApps().isEmpty()) {
+                if (FirebaseApp.getApps().isEmpty()) {
                     log.info("Successfully initialized firebase app");
-                return FirebaseApp.initializeApp(options);
-            } else {
-                return FirebaseApp.getInstance();
-            }
+                    return FirebaseApp.initializeApp(options);
+                } else {
+                    log.info("Reusing existing firebase app instance");
+                    return FirebaseApp.getInstance();
+                }
+            }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try {
// 🚨 수정 포인트 2: 주입받은 Resource에서 바로 InputStream을 얻습니다.
InputStream serviceAccount = serviceAccountResource.getInputStream();
FirebaseOptions options = FirebaseOptions.builder()
.setCredentials(GoogleCredentials.fromStream(serviceAccount))
.build();
try {
// 🚨 수정 포인트 2: 주입받은 Resource에서 바로 InputStream을 얻습니다.
try (InputStream serviceAccount = serviceAccountResource.getInputStream()) {
FirebaseOptions options = FirebaseOptions.builder()
.setCredentials(GoogleCredentials.fromStream(serviceAccount))
.build();
if (FirebaseApp.getApps().isEmpty()) {
log.info("Successfully initialized firebase app");
return FirebaseApp.initializeApp(options);
} else {
log.info("Reusing existing firebase app instance");
return FirebaseApp.getInstance();
}
}
}
🤖 Prompt for AI Agents
In src/main/java/naughty/tuzamate/global/config/FireBaseConfig.java around lines
29 to 35, the InputStream obtained from serviceAccountResource is not closed and
the init block is missing a closing brace; wrap obtaining and using
serviceAccountResource.getInputStream() in a try-with-resources to automatically
close the InputStream (e.g., try (InputStream serviceAccount =
serviceAccountResource.getInputStream()) { ... }) and ensure you add the missing
closing brace at the end of the init block so the method/block compiles
correctly.


log.info("Successfully initialized firebase app");
return FirebaseApp.initializeApp(options);
// 앱이 이미 초기화되었는지 확인 (중복 초기화 방지)
if (FirebaseApp.getApps().isEmpty()) {
log.info("Successfully initialized firebase app");
return FirebaseApp.initializeApp(options);
} else {
return FirebaseApp.getInstance();
}

} catch (IOException exception) {
log.error("Fail to initialize firebase app: {}", exception.getMessage(), exception);
return null;
// 초기화 실패 시 null 대신 예외를 던져서 애플리케이션이 문제를 인지하게 하는 것이 더 좋습니다.
throw new RuntimeException("Failed to initialize Firebase app.", exception);
}
}

Expand Down
Loading