-
Notifications
You must be signed in to change notification settings - Fork 10
6주차 미션 #16
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
6주차 미션 #16
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| package com.example.kuit6_android_api.data.api | ||
|
|
||
| import com.example.kuit6_android_api.data.model.response.BaseResponse | ||
| import com.example.kuit6_android_api.data.model.response.PostResponse | ||
| import com.example.kuit6_android_api.data.model.request.PostCreateRequest | ||
| import okhttp3.MultipartBody | ||
| import retrofit2.http.* | ||
|
|
||
| interface ApiService { | ||
| @GET("/api/posts") | ||
| suspend fun getPosts(): BaseResponse<List<PostResponse>> | ||
|
|
||
| @POST("/api/posts") | ||
| suspend fun createPost( | ||
| @Query("author") author: String = "시헌", | ||
| @Body request: PostCreateRequest | ||
| ): BaseResponse<PostResponse> | ||
|
|
||
| @DELETE("/api/posts/{id}") | ||
| suspend fun deletePost( | ||
| @Path("id") id: Long | ||
| ): BaseResponse<Unit> // data에 빈 객체 반환 | ||
|
|
||
| @GET("/api/posts/{id}") | ||
| suspend fun getPostDetail( | ||
| @Path("id") id: Long | ||
| ): BaseResponse<PostResponse> | ||
|
|
||
| // 업데이트 요청 | ||
| @PUT("/api/posts/{id}") | ||
| suspend fun updatePost( | ||
| @Path("id") id: Long, | ||
| @Body request: PostCreateRequest // 수정된 내용 전달 | ||
| ): BaseResponse<PostResponse> | ||
|
|
||
| @Multipart | ||
| @POST("/api/images/upload") | ||
| suspend fun uploadImage( | ||
| @Part file: MultipartBody.Part | ||
| ): BaseResponse<Map<String, String>> | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,30 @@ | ||||||||||||||||||||||
| package com.example.kuit6_android_api.data.api | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| import com.example.kuit6_android_api.BuildConfig | ||||||||||||||||||||||
| import okhttp3.OkHttpClient | ||||||||||||||||||||||
| import okhttp3.logging.HttpLoggingInterceptor | ||||||||||||||||||||||
| import retrofit2.Retrofit | ||||||||||||||||||||||
| import retrofit2.converter.gson.GsonConverterFactory | ||||||||||||||||||||||
| import java.util.concurrent.TimeUnit | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| object RetrofitClient { | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| private val loggingInterceptor = HttpLoggingInterceptor().apply { | ||||||||||||||||||||||
| level = HttpLoggingInterceptor.Level.BODY | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
Comment on lines
+12
to
+14
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 프로덕션 환경에서 로깅 레벨 주의
다음과 같이 빌드 타입에 따라 로깅 레벨을 조정하세요: private val loggingInterceptor = HttpLoggingInterceptor().apply {
- level = HttpLoggingInterceptor.Level.BODY
+ level = if (BuildConfig.DEBUG) {
+ HttpLoggingInterceptor.Level.BODY
+ } else {
+ HttpLoggingInterceptor.Level.NONE
+ }
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| private val okHttpClient = OkHttpClient.Builder() | ||||||||||||||||||||||
| .addInterceptor(loggingInterceptor) | ||||||||||||||||||||||
| .connectTimeout(30, TimeUnit.SECONDS) | ||||||||||||||||||||||
| .readTimeout(30, TimeUnit.SECONDS) | ||||||||||||||||||||||
| .writeTimeout(30, TimeUnit.SECONDS) | ||||||||||||||||||||||
| .build() | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| private val retrofit: Retrofit = Retrofit.Builder() | ||||||||||||||||||||||
| .baseUrl(BuildConfig.BASE_URL) | ||||||||||||||||||||||
| .client(okHttpClient) | ||||||||||||||||||||||
| .addConverterFactory(GsonConverterFactory.create()) | ||||||||||||||||||||||
| .build() | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| val apiService: ApiService = retrofit.create(ApiService::class.java) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| package com.example.kuit6_android_api.data.model.request | ||
|
|
||
| import kotlinx.serialization.SerialName | ||
|
|
||
| data class PostCreateRequest( | ||
| @SerialName(value = "title") val title:String, | ||
| @SerialName(value = "content") val content:String, | ||
| @SerialName(value = "imageUrl") val imageUrl:String? | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| package com.example.kuit6_android_api.data.model.response | ||
|
|
||
| import kotlinx.serialization.SerialName | ||
|
|
||
| data class AuthorResponse ( | ||
| @SerialName(value = "id") val id : Long, | ||
| @SerialName(value = "username") val username: String, | ||
| @SerialName(value = "profileImageUrl") val profileImageUrl:String? | ||
|
Comment on lines
+3
to
+8
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 직렬화 라이브러리 불일치 문제 이 파일도 동일한 직렬화 라이브러리 불일치 문제가 있습니다. Gson 사용 시: -import kotlinx.serialization.SerialName
+import com.google.gson.annotations.SerializedName
data class AuthorResponse (
- @SerialName(value = "id") val id : Long,
- @SerialName(value = "username") val username: String,
- @SerialName(value = "profileImageUrl") val profileImageUrl:String?
+ @SerializedName("id") val id : Long,
+ @SerializedName("username") val username: String,
+ @SerializedName("profileImageUrl") val profileImageUrl: String?
)🤖 Prompt for AI Agents |
||
| ) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| package com.example.kuit6_android_api.data.model.response | ||
|
|
||
| import kotlinx.serialization.SerialName | ||
|
|
||
| data class BaseResponse<T>( | ||
| @SerialName(value = "success") val success : Boolean, | ||
| @SerialName(value = "message") val message : String?, | ||
| @SerialName(value = "data") val data: T?, | ||
| @SerialName(value = "timestamp") val timestamp: String | ||
|
Comment on lines
+3
to
+9
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 직렬화 라이브러리 불일치 문제
Gson 사용 시: -import kotlinx.serialization.SerialName
+import com.google.gson.annotations.SerializedName
data class BaseResponse<T>(
- @SerialName(value = "success") val success : Boolean,
- @SerialName(value = "message") val message : String?,
- @SerialName(value = "data") val data: T?,
- @SerialName(value = "timestamp") val timestamp: String
+ @SerializedName("success") val success : Boolean,
+ @SerializedName("message") val message : String?,
+ @SerializedName("data") val data: T?,
+ @SerializedName("timestamp") val timestamp: String
)
🤖 Prompt for AI Agents |
||
| ) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| package com.example.kuit6_android_api.data.model.response | ||
|
|
||
| import kotlinx.serialization.SerialName | ||
|
|
||
| data class PostResponse( | ||
| @SerialName(value = "id") val id : Long, | ||
| @SerialName(value = "title") val title:String, | ||
| @SerialName(value = "content") val content : String, | ||
| @SerialName(value = "imageUrl") val imageUrl:String?, | ||
| @SerialName(value = "author") val author: AuthorResponse, | ||
| @SerialName(value = "createdAt") val createdAt:String, | ||
| @SerialName(value = "updatedAt") val updatedAt:String | ||
|
Comment on lines
+3
to
+12
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 직렬화 라이브러리 불일치 문제 이 파일은 해결 방법 1 (권장): Gson 어노테이션 사용 package com.example.kuit6_android_api.data.model.response
-import kotlinx.serialization.SerialName
+import com.google.gson.annotations.SerializedName
data class PostResponse(
- @SerialName(value = "id") val id : Long,
- @SerialName(value = "title") val title:String,
- @SerialName(value = "content") val content : String,
- @SerialName(value = "imageUrl") val imageUrl:String?,
- @SerialName(value = "author") val author: AuthorResponse,
- @SerialName(value = "createdAt") val createdAt:String,
- @SerialName(value = "updatedAt") val updatedAt:String
+ @SerializedName("id") val id : Long,
+ @SerializedName("title") val title: String,
+ @SerializedName("content") val content : String,
+ @SerializedName("imageUrl") val imageUrl: String?,
+ @SerializedName("author") val author: AuthorResponse,
+ @SerializedName("createdAt") val createdAt: String,
+ @SerializedName("updatedAt") val updatedAt: String
)해결 방법 2: kotlinx.serialization 컨버터 사용
// build.gradle.kts에 추가
implementation("com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0")
// RetrofitClient.kt
.addConverterFactory(Json.asConverterFactory("application/json".toMediaType()))동일한 수정이 🤖 Prompt for AI Agents |
||
| ) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -29,33 +29,41 @@ import androidx.compose.material3.MaterialTheme | |
| import androidx.compose.material3.OutlinedTextField | ||
| import androidx.compose.material3.OutlinedTextFieldDefaults | ||
| import androidx.compose.material3.Scaffold | ||
| import androidx.compose.material3.SnackbarHostState | ||
| import androidx.compose.material3.Text | ||
| import androidx.compose.material3.TopAppBar | ||
| import androidx.compose.material3.TopAppBarDefaults | ||
| import androidx.compose.runtime.Composable | ||
| import androidx.compose.runtime.State | ||
| import androidx.compose.runtime.getValue | ||
| import androidx.compose.runtime.mutableStateOf | ||
| import androidx.compose.runtime.remember | ||
| import androidx.compose.runtime.rememberCoroutineScope | ||
| import androidx.compose.runtime.setValue | ||
| import androidx.compose.ui.Alignment | ||
| import androidx.compose.ui.Modifier | ||
| import androidx.compose.ui.platform.LocalContext | ||
| import androidx.compose.ui.text.font.FontWeight | ||
| import androidx.compose.ui.tooling.preview.Preview | ||
| import androidx.compose.ui.unit.dp | ||
| import androidx.lifecycle.viewmodel.compose.viewModel | ||
| import com.example.kuit6_android_api.ui.post.viewmodel.PostViewModel | ||
| import kotlinx.coroutines.launch | ||
|
|
||
| @OptIn(ExperimentalMaterial3Api::class) | ||
| @Composable | ||
| fun PostCreateScreen( | ||
| onNavigateBack: () -> Unit, | ||
| onPostCreated: () -> Unit, | ||
| snackBarState: SnackbarHostState, | ||
| viewModel: PostViewModel = viewModel() | ||
| ) { | ||
| val context = LocalContext.current | ||
| var author by remember { mutableStateOf("") } | ||
| var title by remember { mutableStateOf("") } | ||
| var content by remember { mutableStateOf("") } | ||
| var selectedImageUri by remember { mutableStateOf<Uri?>(null) } | ||
| val scope = rememberCoroutineScope() | ||
|
|
||
| val imagePickerLauncher = rememberLauncherForActivityResult( | ||
| contract = ActivityResultContracts.GetContent() | ||
|
|
@@ -187,9 +195,10 @@ fun PostCreateScreen( | |
|
|
||
| Button( | ||
| onClick = { | ||
| val finalAuthor = author.ifBlank { "anonymous" } | ||
| val finalAuthor = author | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain작성자 필드 유효성 검증을 추가하세요. 작성자 필드가 비어있을 수 있습니다. 기존에는 🏁 Script executed: #!/bin/bash
# Description: ApiService에서 author 파라미터 처리 방식 확인
# createPost 메서드에서 author 파라미터 기본값 및 처리 확인
ast-grep --pattern $'suspend fun createPost(
@Query("author") author: String$$$
)$$$'Length of output: 0 추가 검증을 위해 리포지토리를 탐색하겠습니다. Length of output: 230 🏁 Script executed: #!/bin/bash
# Search for createPost method definition with context
rg -n "suspend fun createPost" -A 5 -B 2 --type=kotlinLength of output: 799 🏁 Script executed: #!/bin/bash
# Check PostCreateScreen.kt for author field handling around line 198
cat "app/src/main/java/com/example/kuit6_android_api/ui/post/screen/PostCreateScreen.kt" | sed -n '190,210p'Length of output: 780 Length of output: 207 🏁 Script executed: #!/bin/bash
# Check the viewModel.createPost method signature
rg -n "fun createPost" "app/src/main/java/com/example/kuit6_android_api" -A 3 -B 2 --type=kotlin | grep -v "ApiService"Length of output: 617 🏁 Script executed: #!/bin/bash
# Get more context around line 198 in PostCreateScreen.kt - check lines 1-100 to find author initialization
sed -n '1,100p' "app/src/main/java/com/example/kuit6_android_api/ui/post/screen/PostCreateScreen.kt" | grep -n "author"Length of output: 52 작성자 필드 유효성 검증이 필요합니다. 작성자 필드( 조치: 다음 중 하나를 선택하세요:
🤖 Prompt for AI Agents |
||
| viewModel.createPost(finalAuthor, title, content, null) { | ||
| onPostCreated() | ||
| scope.launch { snackBarState.showSnackbar("게시글이 작성되었습니다.") } | ||
| } | ||
| }, | ||
| modifier = Modifier | ||
|
|
@@ -226,7 +235,8 @@ fun PostCreateScreenPreview() { | |
| MaterialTheme { | ||
| PostCreateScreen( | ||
| onNavigateBack = {}, | ||
| onPostCreated = {} | ||
| onPostCreated = {}, | ||
| snackBarState = remember { SnackbarHostState() } | ||
| ) | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BASE_URL null 처리 필요
local.properties파일이 없거나BASE_URL속성이 설정되지 않은 경우baseUrl변수가 null이 되어 빌드 실패가 발생합니다.다음 diff를 적용하여 null 처리를 추가하세요:
// defaultConfig 블록 안에 추가 val properties = Properties() val localPropertiesFile = rootProject.file("local.properties") if (localPropertiesFile.exists()) { properties.load(localPropertiesFile.inputStream()) } -val baseUrl = properties.getProperty("BASE_URL") +val baseUrl = properties.getProperty("BASE_URL") ?: "https://default-api-url.com/" buildConfigField("String", "BASE_URL", "\"$baseUrl\"")📝 Committable suggestion
🤖 Prompt for AI Agents