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
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package com.into.websoso.core.common.image

import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.FileOutputStream
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class ImageCompressor
@Inject
constructor(
@ApplicationContext private val context: Context,
) {
suspend fun compressUris(
uris: List<Uri>,
size: Double = DEFAULT_MAX_IMAGE_SIZE,
): List<Uri> =
withContext(Dispatchers.IO) {
uris.mapNotNull { uri ->
runCatching {
val inputStream = context.contentResolver.openInputStream(uri)
val bitmap = inputStream?.use { BitmapFactory.decodeStream(it) } ?: return@runCatching null

var quality = INITIAL_QUALITY
val outputStream = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.JPEG, quality, outputStream)

while (outputStream.size() > (size * MB) && quality > QUALITY_DECREMENT_STEP) {
quality -= QUALITY_DECREMENT_STEP
outputStream.reset()
bitmap.compress(Bitmap.CompressFormat.JPEG, quality, outputStream)
}

val compressedFile = File.createTempFile("compressed_", ".jpg", context.cacheDir)
FileOutputStream(compressedFile).use {
it.write(outputStream.toByteArray())
}

outputStream.close()
Uri.fromFile(compressedFile)
}.onFailure {
it.printStackTrace()
}.getOrNull()
}
}

companion object {
private const val INITIAL_QUALITY: Int = 100
private const val QUALITY_DECREMENT_STEP: Int = 5
private const val DEFAULT_MAX_IMAGE_SIZE: Double = 0.25
private const val MB: Double = (1024 * 1024).toDouble()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.into.websoso.core.network.common

import android.content.Context
import android.net.Uri
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.File
import java.io.FileOutputStream
import java.net.HttpURLConnection
import java.net.URL
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class ImageDownloader
@Inject
constructor(
@ApplicationContext private val context: Context,
) {
suspend fun formatImageToUri(url: String): Result<Uri> =
withContext(Dispatchers.IO) {
val connection = (URL(url).openConnection() as? HttpURLConnection)
?: return@withContext Result.failure(IllegalStateException("Invalid HTTP connection"))

runCatching {
connection.connect()
Comment on lines +23 to +27
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

연결 타임아웃 설정이 누락되어 네트워크 지연 시 무한 대기 가능

HttpURLConnection에 타임아웃이 설정되지 않아 네트워크 응답이 느리거나 서버가 응답하지 않을 때 스레드가 무한정 차단될 수 있습니다. 또한 HTTP 응답 코드를 확인하지 않아 오류 응답(4xx, 5xx)의 내용도 이미지로 저장될 수 있습니다.

🔧 타임아웃 및 응답 코드 검증 추가
 val connection = (URL(url).openConnection() as? HttpURLConnection)
     ?: return@withContext Result.failure(IllegalStateException("Invalid HTTP connection"))

 runCatching {
+    connection.connectTimeout = 10_000  // 10초
+    connection.readTimeout = 10_000     // 10초
     connection.connect()
+    
+    val responseCode = connection.responseCode
+    if (responseCode !in 200..299) {
+        return@withContext Result.failure(
+            IllegalStateException("HTTP error code: $responseCode")
+        )
+    }
+    
     connection.inputStream.use { inputStream ->
🤖 Prompt for AI Agents
In
@core/network/src/main/java/com/into/websoso/core/network/common/ImageDownloader.kt
around lines 23 - 27, The code opens an HttpURLConnection via
URL(url).openConnection() and then calls connection.connect() without timeouts
or response-code checks; update the logic around the connection variable to set
connection.connectTimeout and connection.readTimeout (e.g., 5_000 and 10_000 ms
or configurable), call connection.connect(), then immediately verify
connection.responseCode is in the 200..299 range and return
Result.failure(IOException(...)) for non-success codes, and ensure the
connection is properly disconnected in a finally block (or use
try-with-resources equivalent) around the runCatching block so slow/failed
endpoints don't hang and error responses aren't treated as valid images.

connection.inputStream.use { inputStream ->
val file = File.createTempFile("image_", ".jpg", context.cacheDir)
FileOutputStream(file).use { output ->
inputStream.copyTo(output)
}
Uri.fromFile(file)
}
}.also {
connection.disconnect()
}
}
Comment on lines +21 to +38
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

다운로드 파일 크기 제한 및 Content-Type 검증 권장

임의의 URL에서 제한 없이 이미지를 다운로드하면 다음 위험이 있습니다:

  • 대용량 파일 다운로드로 인한 메모리/저장소 부족
  • 이미지가 아닌 파일 타입 처리

파일 크기 제한과 Content-Type 검증을 추가하는 것을 권장합니다.

💡 파일 크기 및 타입 검증 추가
 connection.connect()
 
 val responseCode = connection.responseCode
 if (responseCode !in 200..299) {
     return@withContext Result.failure(
         IllegalStateException("HTTP error code: $responseCode")
     )
 }
 
+val contentLength = connection.contentLength
+val maxSize = 10 * 1024 * 1024  // 10MB
+if (contentLength > maxSize) {
+    return@withContext Result.failure(
+        IllegalStateException("File too large: $contentLength bytes")
+    )
+}
+
+val contentType = connection.contentType
+if (contentType?.startsWith("image/") != true) {
+    return@withContext Result.failure(
+        IllegalStateException("Invalid content type: $contentType")
+    )
+}
+
 connection.inputStream.use { inputStream ->
🤖 Prompt for AI Agents
In
@core/network/src/main/java/com/into/websoso/core/network/common/ImageDownloader.kt
around lines 21 - 38, The formatImageToUri function currently downloads any URL
without validating content type or size; update it to first check the
HttpURLConnection's Content-Type header starts with "image/" and return a
failure if not, enforce a max allowed size (e.g., via
connection.contentLengthLong when available and/or by counting bytes while
copying the inputStream) and abort/return failure if the size exceeds the limit,
and ensure you still close/disconnect the connection; keep using the existing
connection, inputStream, File.createTempFile and Uri.fromFile logic but add
these validations and return appropriate Result.failure when checks fail.

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import retrofit2.http.Part
import retrofit2.http.Path
import retrofit2.http.Query

internal interface FeedApi {
interface FeedApi {
@GET("feeds")
suspend fun getFeeds(
@Query("category") category: String?,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.into.websoso.core.network.datasource.feed.mapper

import android.content.Context
import android.net.Uri
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.serialization.json.Json
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.MultipartBody
import okhttp3.RequestBody.Companion.toRequestBody
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class MultiPartMapper
@Inject
constructor(
@ApplicationContext private val context: Context,
) {
inline fun <reified T> formatToMultipart(
target: T,
partName: String = "data",
fileName: String = "data.json",
): MultipartBody.Part {
val json = Json.encodeToString(target)
val requestBody = json.toRequestBody("application/json".toMediaType())
return MultipartBody.Part.createFormData(partName, fileName, requestBody)
}
Comment on lines +19 to +27
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

제네릭 타입 제약 조건 추가 필요

Json.encodeToString(target)를 사용하려면 타입 T@Serializable이어야 하지만, 현재 타입 파라미터에 제약 조건이 없습니다. 이로 인해 직렬화 불가능한 타입이 전달될 경우 런타임 오류가 발생합니다.

🔧 타입 제약 추가 제안

옵션 1: 인라인 함수이므로 컴파일러가 직렬화 가능 여부를 확인하도록 문서화하거나, 옵션 2: 직렬화 실패 시 예외 처리 추가:

 inline fun <reified T> formatToMultipart(
     target: T,
     partName: String = "data",
     fileName: String = "data.json",
 ): MultipartBody.Part {
-    val json = Json.encodeToString(target)
+    val json = try {
+        Json.encodeToString(target)
+    } catch (e: Exception) {
+        throw IllegalArgumentException("직렬화 불가능한 타입: ${T::class.simpleName}", e)
+    }
     val requestBody = json.toRequestBody("application/json".toMediaType())
     return MultipartBody.Part.createFormData(partName, fileName, requestBody)
 }

또는 KDoc에 @param T must be annotated with @Serializable 명시

🤖 Prompt for AI Agents
In
@core/network/src/main/java/com/into/websoso/core/network/datasource/feed/mapper/MultiPartMapper.kt
around lines 19 - 27, The function formatToMultipart uses
Json.encodeToString(target) but has no compile-time guarantee that T is
@Serializable, so either change the signature to require an explicit serializer
(e.g., add a parameter serializer: KSerializer<T> and call
Json.encodeToString(serializer, target)) to ensure correct serialization at
compile time, or keep the signature but wrap the Json.encodeToString call in a
try/catch that throws or logs a clear error mentioning formatToMultipart and
that T must be @Serializable; also update KDoc for formatToMultipart to state
the requirement if you choose the runtime-check option.


fun formatToMultipart(
uri: Uri,
partName: String = "images",
contentType: String = "image/*",
): MultipartBody.Part {
val inputStream = context.contentResolver.openInputStream(uri)
?: throw IllegalArgumentException("유효하지 않은 URI: $uri")
return inputStream.use { stream ->
val bytes = stream.readBytes()
val fileName = uri.lastPathSegment ?: "file.jpg"
val requestBody = bytes.toRequestBody(contentType.toMediaType())
MultipartBody.Part.createFormData(partName, fileName, requestBody)
}
}
Comment on lines +29 to +43
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# Search for formatToMultipart usage
rg -n "formatToMultipart" --type=kt -B 2 -A 2

Repository: Team-WSS/WSS-Android

Length of output: 90


🏁 Script executed:

# Search for ImageCompressor usage
rg -n "ImageCompressor" --type=kt -B 2 -A 2

Repository: Team-WSS/WSS-Android

Length of output: 90


🏁 Script executed:

# Look for FeedRepository and feed upload patterns
fd "FeedRepository" --type=kt --exec cat -n {}

Repository: Team-WSS/WSS-Android

Length of output: 233


🏁 Script executed:

# Check for any file size validation or limits
rg -n "size|Size|limit|Limit|MAX|max" --type=kt core/network/src/main/java/com/into/websoso/core/network/datasource/feed/ -B 1 -A 1

Repository: Team-WSS/WSS-Android

Length of output: 90


🏁 Script executed:

# Search for formatToMultipart usage in .kt files
rg -n "formatToMultipart" -g '*.kt' -B 2 -A 2

Repository: Team-WSS/WSS-Android

Length of output: 7362


🏁 Script executed:

# Search for ImageCompressor usage in .kt files
rg -n "ImageCompressor" -g '*.kt' -B 2 -A 2

Repository: Team-WSS/WSS-Android

Length of output: 3412


🏁 Script executed:

# Find the MultiPartMapper file location to understand context better
find . -name "MultiPartMapper.kt" -type f

Repository: Team-WSS/WSS-Android

Length of output: 232


🏁 Script executed:

# Search for FeedRepository in .kt files
rg -n "class FeedRepository|object FeedRepository" -g '*.kt' -A 10

Repository: Team-WSS/WSS-Android

Length of output: 2719


🏁 Script executed:

# View the postFeed method in FeedRepository to see complete flow
sed -n '50,85p' data/feed/src/main/java/com/into/websoso/data/feed/repository/FeedRepository.kt

Repository: Team-WSS/WSS-Android

Length of output: 1433


🏁 Script executed:

# View the putFeed method in FeedRepository
sed -n '88,120p' data/feed/src/main/java/com/into/websoso/data/feed/repository/FeedRepository.kt

Repository: Team-WSS/WSS-Android

Length of output: 1458


🏁 Script executed:

# Check how images parameter is processed before being used
rg -n "fun postFeed|fun putFeed" -g '*.kt' data/feed/src/main/java/com/into/websoso/data/feed/repository/FeedRepository.kt -A 30

Repository: Team-WSS/WSS-Android

Length of output: 46


🏁 Script executed:

# Check if compress is called on images anywhere
rg -n "imageCompressor.compress|compress\(" -g '*.kt' data/feed/src/main/java/com/into/websoso/data/feed/repository/ -B 2 -A 2

Repository: Team-WSS/WSS-Android

Length of output: 733


🏁 Script executed:

# Search for usages of compressImages
rg -n "compressImages" -g '*.kt' -B 2 -A 2

Repository: Team-WSS/WSS-Android

Length of output: 1995


🏁 Script executed:

# Check for any file size validation or limits in feed API or mapper
rg -n "MAX|max|size|Size|limit" -g '*.kt' core/network/src/main/java/com/into/websoso/core/network/datasource/feed/ -B 1 -A 1

Repository: Team-WSS/WSS-Android

Length of output: 429


🏁 Script executed:

# Check ImageCompressor implementation for context on compression
cat -n core/common/src/main/java/com/into/websoso/core/common/image/ImageCompressor.kt

Repository: Team-WSS/WSS-Android

Length of output: 2876


🏁 Script executed:

# Check CreateFeedViewModel to see full image handling flow
rg -n "saveFeed\|editFeed" -g '*.kt' app/src/main/java/com/into/websoso/ui/createFeed/CreateFeedViewModel.kt -B 5 -A 5

Repository: Team-WSS/WSS-Android

Length of output: 46


🏁 Script executed:

# Verify that attachedImages (compressed) are what's passed to saveFeed
rg -n "_attachedImages\|attachedImages" -g '*.kt' app/src/main/java/com/into/websoso/ui/createFeed/CreateFeedViewModel.kt -B 2 -A 2

Repository: Team-WSS/WSS-Android

Length of output: 46


🏁 Script executed:

# Find CreateFeedViewModel file
find . -name "CreateFeedViewModel.kt" -type f

Repository: Team-WSS/WSS-Android

Length of output: 137


🏁 Script executed:

# Search for where saveFeed is called in the codebase
rg -n "\.saveFeed\(" -g '*.kt' -B 3 -A 3

Repository: Team-WSS/WSS-Android

Length of output: 920


🏁 Script executed:

# Check the complete flow - search for attachedImages usage more broadly
rg -n "attachedImages" -g '*.kt' -B 2 -A 2 | head -50

Repository: Team-WSS/WSS-Android

Length of output: 5422


파일명 기본값 개선 고려

Line 39의 기본 파일명 "file.jpg"는 contentType이 다양한 파일 타입을 지원할 수 있을 때 부적절할 수 있습니다. contentType에 따라 적절한 확장자를 선택하거나, 원본 파일 정보가 손실되지 않도록 개선하는 것을 검토하세요.

참고: readBytes()로 인한 메모리 문제는 발생하지 않습니다. CreateFeedViewModel에서 이미지가 ImageCompressor를 통해 약 250KB로 압축된 후 전달되므로, 메모리 사용량은 허용 범위 내입니다.

🤖 Prompt for AI Agents
In
@core/network/src/main/java/com/into/websoso/core/network/datasource/feed/mapper/MultiPartMapper.kt
around lines 29 - 43, The default fallback fileName "file.jpg" in
formatToMultipart should be replaced with a more accurate name/extension: try to
resolve the original filename from the Uri via ContentResolver query
(OpenableColumns.DISPLAY_NAME) inside formatToMultipart, and if that's null,
derive an extension from the contentType using
MimeTypeMap.getSingleton().getExtensionFromMimeType(contentType) and use a
fallback like "file.<ext>" (or "file.bin" if extension resolution fails); update
references to fileName and ensure multipart creation via
MultipartBody.Part.createFormData(partName, fileName, requestBody) uses the new
name.

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
internal data class CommentRequestDto(
data class CommentRequestDto(
@SerialName("commentContent") val commentContent: String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.into.websoso.core.network.datasource.feed.model.request

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class FeedRequestDto(
@SerialName("relevantCategories")
val relevantCategories: List<String>,
@SerialName("feedContent")
val feedContent: String,
@SerialName("novelId")
val novelId: Long?,
@SerialName("isSpoiler")
val isSpoiler: Boolean,
@SerialName("isPublic")
val isPublic: Boolean,
)
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
internal data class CommentResponseDto(
data class CommentResponseDto(
@SerialName("userId")
val userId: Long,
@SerialName("nickname")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
internal data class CommentsResponseDto(
data class CommentsResponseDto(
@SerialName("comments")
val comments: List<CommentResponseDto>,
@SerialName("commentsCount")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
internal data class FeedDetailResponseDto(
data class FeedDetailResponseDto(
@SerialName("userId")
val userId: Long,
@SerialName("feedId")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
internal data class FeedResponseDto(
data class FeedResponseDto(
@SerialName("feedId")
val feedId: Long,
@SerialName("userId")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
internal data class FeedsResponseDto(
data class FeedsResponseDto(
@SerialName("category")
val category: String,
@SerialName("isLoadable")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
internal data class PopularFeedsResponseDto(
data class PopularFeedsResponseDto(
@SerialName("popularFeeds")
val popularFeeds: List<PopularFeedResponseDto>,
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
internal data class UserInterestFeedsResponseDto(
data class UserInterestFeedsResponseDto(
@SerialName("recommendFeeds")
val userInterestFeeds: List<UserInterestFeedResponseDto>,
@SerialName("message")
Expand Down
2 changes: 2 additions & 0 deletions data/feed/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,7 @@ android {
dependencies {
implementation(projects.core.common)
implementation(projects.data.account)
implementation(projects.core.network)
implementation(projects.data.library)
Comment on lines +14 to +15
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

git ls-files | grep -E "(core/network|data/library|data/feed)" | head -20

Repository: Team-WSS/WSS-Android

Length of output: 1800


🏁 Script executed:

find . -name "build.gradle.kts" -type f | grep -E "(core|data)" | head -20

Repository: Team-WSS/WSS-Android

Length of output: 420


🏁 Script executed:

cat -n data/feed/build.gradle.kts

Repository: Team-WSS/WSS-Android

Length of output: 534


🏁 Script executed:

cat -n core/network/build.gradle.kts

Repository: Team-WSS/WSS-Android

Length of output: 1406


🏁 Script executed:

cat -n data/library/build.gradle.kts

Repository: Team-WSS/WSS-Android

Length of output: 439


모듈 의존성 및 순환 의존성 검증 완료

피드 데이터 레이어 분리에 필요한 core.networkdata.library 모듈 의존성이 적절하게 추가되었습니다(라인 14-15). 확인 결과 core.networkdata.library 모두 data.feed를 의존하지 않으므로 순환 의존성이 없습니다.

다만, 아키텍처 레이어 위반 문제를 발견했습니다:

  • core.networkdata.account, data.library 등 데이터 레이어 모듈에 의존 중
  • 클린 아키텍처 원칙에서 코어 레이어는 데이터 레이어에 의존하면 안 됨 (데이터 레이어 → 코어 레이어 방향이어야 함)
  • core/network/build.gradle.kts의 31-32번 라인에서 데이터 레이어 의존성 제거 필요
🤖 Prompt for AI Agents
In @data/feed/build.gradle.kts around lines 14 - 15, The core.network module
currently depends on data-layer modules (data.account, data.library); remove
those data-layer dependencies from the dependencies block in
core/network/build.gradle.kts (delete the implementation(projects.data.account)
and implementation(projects.data.library) entries referenced around lines 31-32)
and instead expose required contracts from the core layer or create a dedicated
core-network API module (e.g., core.network.api) for abstractions that
core.network needs; move concrete implementations into the data layer so
data.feed/data.library depend on core/network interfaces rather than
core.network depending on data modules.

implementation(libs.paging.runtime)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package com.into.websoso.data.feed.mapper

import com.into.websoso.core.network.datasource.feed.model.response.CommentResponseDto
import com.into.websoso.core.network.datasource.feed.model.response.CommentsResponseDto
import com.into.websoso.core.network.datasource.feed.model.response.FeedDetailResponseDto
import com.into.websoso.core.network.datasource.feed.model.response.FeedResponseDto
import com.into.websoso.core.network.datasource.feed.model.response.FeedsResponseDto
import com.into.websoso.core.network.datasource.feed.model.response.PopularFeedsResponseDto
import com.into.websoso.core.network.datasource.feed.model.response.UserInterestFeedsResponseDto
import com.into.websoso.data.feed.model.CommentEntity
import com.into.websoso.data.feed.model.CommentsEntity
import com.into.websoso.data.feed.model.FeedEntity
import com.into.websoso.data.feed.model.FeedsEntity
import com.into.websoso.data.feed.model.PopularFeedsEntity
import com.into.websoso.data.feed.model.UserInterestFeedsEntity

fun FeedsResponseDto.toData(): FeedsEntity =
FeedsEntity(
category = category,
isLoadable = isLoadable,
feeds = feeds.map { it.toData() },
)

fun FeedResponseDto.toData(): FeedEntity =
FeedEntity(
user = FeedEntity.UserEntity(
id = userId,
nickname = nickname,
avatarImage = avatarImage,
),
createdDate = createdDate,
id = feedId,
content = feedContent,
relevantCategories = relevantCategories,
likeCount = likeCount,
isLiked = isLiked,
commentCount = commentCount,
isModified = isModified,
isSpoiler = isSpoiler,
isMyFeed = isMyFeed,
isPublic = isPublic,
images = thumbnailUrl?.let { listOf(it) } ?: emptyList(),
imageCount = imageCount,
novel = FeedEntity.NovelEntity(
id = novelId,
title = title,
rating = novelRating,
ratingCount = novelRatingCount,
),
)
Comment on lines +24 to +50
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

find . -name "FeedMapper.kt" -type f

Repository: Team-WSS/WSS-Android

Length of output: 326


🏁 Script executed:

cat -n ./data/feed/src/main/java/com/into/websoso/data/feed/mapper/FeedMapper.kt

Repository: Team-WSS/WSS-Android

Length of output: 5558


🏁 Script executed:

fd "FeedResponseDto|FeedDetailResponseDto" --type f --extension kt | head -20

Repository: Team-WSS/WSS-Android

Length of output: 519


🏁 Script executed:

cat -n core/network/src/main/java/com/into/websoso/core/network/datasource/feed/model/response/FeedResponseDto.kt

Repository: Team-WSS/WSS-Android

Length of output: 1743


🏁 Script executed:

cat -n core/network/src/main/java/com/into/websoso/core/network/datasource/feed/model/response/FeedDetailResponseDto.kt

Repository: Team-WSS/WSS-Android

Length of output: 1673


imageCountimages 리스트 크기 불일치 문제

FeedResponseDto.toData()에서 imagesthumbnailUrl만 포함하여 최대 1개 항목이 되지만, imageCount는 DTO에서 직접 받아오고 있습니다. 예를 들어 서버의 imageCount가 3이고 thumbnailUrl만 전달된 경우, 실제 images 리스트 크기는 1이 됩니다.

반면 FeedDetailResponseDto.toData()에서는 imageCount = images.size로 일관되게 처리하고 있습니다. 두 매핑 함수의 일관성을 맞추기 위해, FeedResponseDto.toData()에서도 실제 이미지 개수를 반영하도록 수정이 필요합니다:

         images = thumbnailUrl?.let { listOf(it) } ?: emptyList(),
-        imageCount = imageCount,
+        imageCount = thumbnailUrl?.let { 1 } ?: 0,
🤖 Prompt for AI Agents
In @data/feed/src/main/java/com/into/websoso/data/feed/mapper/FeedMapper.kt
around lines 24 - 50, In FeedResponseDto.toData() the images list is built from
thumbnailUrl but imageCount is taken from the DTO, causing mismatches; change
the mapping to first build a local imagesList = thumbnailUrl?.let { listOf(it) }
?: emptyList(), set images = imagesList and set imageCount = imagesList.size
(instead of using the DTO imageCount) so the mapped FeedEntity (in
FeedResponseDto.toData()) is consistent with FeedDetailResponseDto.toData().


fun CommentsResponseDto.toData(): CommentsEntity =
CommentsEntity(
comments = comments.map { it.toData() },
commentsCount = commentsCount,
)

fun CommentResponseDto.toData(): CommentEntity =
CommentEntity(
user = FeedEntity.UserEntity(
id = userId,
nickname = nickname,
avatarImage = avatarImage,
),
commentContent = commentContent,
commentId = commentId,
createdDate = createdDate,
isModified = isModified,
isMyComment = isMyComment,
isBlocked = isBlocked,
isHidden = isHidden,
isSpoiler = isSpoiler,
)

fun FeedDetailResponseDto.toData(): FeedEntity =
FeedEntity(
user = FeedEntity.UserEntity(
id = userId,
nickname = nickname,
avatarImage = avatarImage,
),
createdDate = createdDate,
id = feedId,
content = feedContent,
relevantCategories = relevantCategories,
likeCount = likeCount,
isLiked = isLiked,
commentCount = commentCount,
isModified = isModified,
isSpoiler = isSpoiler,
isMyFeed = isMyFeed,
isPublic = isPublic,
images = images,
imageCount = images.size,
novel = FeedEntity.NovelEntity(
id = novelId,
title = title,
rating = novelRating,
ratingCount = novelRatingCount,
),
)

fun PopularFeedsResponseDto.toData(): PopularFeedsEntity =
PopularFeedsEntity(
popularFeeds = popularFeeds.map { feed ->
PopularFeedsEntity.PopularFeedEntity(
feedId = feed.feedId,
feesContent = feed.feedContent,
likeCount = feed.likeCount,
commentCount = feed.commentCount,
isSpoiler = feed.isSpoiler,
)
},
)

fun UserInterestFeedsResponseDto.toData(): UserInterestFeedsEntity =
UserInterestFeedsEntity(
userInterestFeeds = userInterestFeeds.map { feed ->
UserInterestFeedsEntity.UserInterestFeedEntity(
avatarImage = feed.avatarImage,
feedContent = feed.feedContent,
nickname = feed.nickname,
novelId = feed.novelId,
novelImage = feed.novelImage,
novelRating = feed.novelRating,
novelRatingCount = feed.novelRatingCount,
novelTitle = feed.novelTitle,
)
},
message = message,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.into.websoso.data.feed.model

data class CommentEntity(
val user: FeedEntity.UserEntity,
val commentContent: String,
val commentId: Long,
val createdDate: String,
val isModified: Boolean,
val isMyComment: Boolean,
val isSpoiler: Boolean,
val isBlocked: Boolean,
val isHidden: Boolean,
)
Loading
Loading