Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
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 {

// 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 {
// 주입받은 Resource에서 바로 InputStream을 얻음
InputStream serviceAccount = serviceAccountResource.getInputStream();

FirebaseOptions options = FirebaseOptions.builder()
.setCredentials(GoogleCredentials.fromStream(serviceAccount))
.build();

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