Skip to content
Open
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
4 changes: 2 additions & 2 deletions .idea/deploymentTargetSelector.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ dependencies {

// Coroutines
implementation(libs.kotlinx.coroutines.android)
implementation(libs.androidx.datastore.preferences)
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

DataStore alpha 버전 사용을 확인하세요.

androidx.datastore.preferences 라이브러리의 alpha 버전(1.3.0-alpha01)을 사용하고 있습니다. 프로덕션 환경에서는 안정적인(stable) 버전 사용을 권장합니다.

다음 스크립트를 실행하여 최신 안정 버전을 확인하세요:


🏁 Script executed:

#!/bin/bash
# DataStore Preferences 최신 버전 확인
curl -s https://maven.google.com/web/index.html?q=androidx.datastore#androidx.datastore:datastore-preferences | grep -oP 'datastore-preferences/[\d.]+' | head -5

Length of output: 0


검증을 위해 추가 정보를 수집하겠습니다.

이제 실제 build.gradle.kts 파일과 버전 카탈로그를 확인하겠습니다.

Now awaiting script results to verify the actual version being used.


🏁 Script executed:

#!/bin/bash
# build.gradle.kts의 datastore 관련 라인 확인
echo "=== build.gradle.kts 라인 75-90 ===" 
sed -n '75,90p' app/build.gradle.kts

echo -e "\n=== libs.versions.toml 또는 gradle/libs.versions.toml 찾기 ==="
find . -name "libs.versions.toml" -o -name "gradle.properties" | head -5

Length of output: 213



---

🏁 Script executed:

```shell
#!/bin/bash
# build.gradle.kts 파일 위치 찾기
echo "=== build.gradle.kts 파일 위치 ==="
find . -name "build.gradle.kts" -type f | head -10

echo -e "\n=== libs.versions.toml 내용 ==="
cat ./git/gradle/libs.versions.toml

Length of output: 167



---

🏁 Script executed:

```shell
#!/bin/bash
# app/build.gradle.kts의 라인 81 주변 확인
echo "=== app/build.gradle.kts 라인 75-90 ==="
sed -n '75,90p' ./app/build.gradle.kts

echo -e "\n=== gradle/libs.versions.toml 내용 ==="
cat ./gradle/libs.versions.toml 2>/dev/null || echo "파일을 찾을 수 없습니다."

Length of output: 4394


이제 최신 안정 버전을 확인하겠습니다.


---

🌐 Web query:

```web
androidx.datastore preferences latest stable version 2024 2025

💡 Result:

  • Latest stable androidx.datastore:datastore-preferences (as of Nov 20, 2025): 1.1.7 (released May 20, 2025). [1]
  • Key 2024 stable releases: 1.1.0 (Apr 17, 2024) and 1.1.1 (May 1, 2024). [1][2]

Sources:
[1] Android Developers — DataStore release notes.
[2] Maven / mvnrepository listing for datastore-preferences.


androidx.datastore.preferences alpha 버전을 안정 버전으로 업그레이드하세요.

프로젝트에서 최신 안정 버전인 1.1.7(2025년 5월 20일 릴리스)로 업그레이드할 것을 권장합니다. gradle/libs.versions.toml의 datastorePreferences 버전을 다음과 같이 변경하세요:

datastorePreferences = "1.1.7"

변경 후 프로덕션 환경에 배포하기 전에 충분한 테스트를 수행해주세요.

🤖 Prompt for AI Agents
In app/build.gradle.kts around line 81, the project is using the alpha version
of androidx.datastore.preferences; update the version in
gradle/libs.versions.toml by setting datastorePreferences = "1.1.7" (the stable
2025-05-20 release) and then run a full build and regression tests to verify no
regressions before deploying to production.

//implementation(libs.adapter.guava)

testImplementation(libs.junit)
Expand Down
1 change: 1 addition & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
android:maxSdkVersion="32" />

<application
android:name=".App"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
Expand Down
13 changes: 13 additions & 0 deletions app/src/main/java/com/example/kuit6_android_api/App.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.example.kuit6_android_api

import android.app.Application
import com.example.kuit6_android_api.data.di.AppContainer

class App : Application() {
lateinit var container: AppContainer

override fun onCreate() {
super.onCreate()
container = AppContainer(this)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,7 @@ class MainActivity : ComponentActivity() {

NavGraph(
navController = navController,
startDestination = PostListRoute,
snackBarState =snackBarState
startDestination = PostListRoute
)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
package com.example.kuit6_android_api.data.api

import android.R.attr.value
import com.example.kuit6_android_api.data.model.request.LoginRequest
import com.example.kuit6_android_api.data.model.request.PostCreateRequest
import com.example.kuit6_android_api.data.model.response.BaseResponse
import com.example.kuit6_android_api.data.model.response.LoginResponse
import com.example.kuit6_android_api.data.model.response.PostResponse
import okhttp3.MultipartBody
import retrofit2.http.Body
import retrofit2.http.DELETE
import retrofit2.http.GET
import retrofit2.http.Multipart
import retrofit2.http.POST
import retrofit2.http.PUT
import retrofit2.http.Part
import retrofit2.http.Path
import retrofit2.http.Query

Expand Down Expand Up @@ -37,4 +42,23 @@ interface ApiService {
suspend fun deletePost(
@Path(value = "id") id: Long,
) : BaseResponse<Unit>


@POST("/api/upload")
suspend fun uploadImage(
@Part file: MultipartBody.Part
): BaseResponse<Map<String, String>>
Comment on lines +47 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 | 🔴 Critical

@multipart 어노테이션이 누락되었습니다.

uploadImage 함수는 @Part를 사용하여 MultipartBody.Part를 받고 있지만, 함수에 @Multipart 어노테이션이 없습니다. Retrofit에서 multipart 요청을 처리하려면 이 어노테이션이 필요합니다.

+@Multipart
 @POST("/api/upload")
 suspend fun uploadImage(
     @Part file: MultipartBody.Part
 ): BaseResponse<Map<String, String>>
🤖 Prompt for AI Agents
In app/src/main/java/com/example/kuit6_android_api/data/api/ApiService.kt around
lines 47 to 50, the suspend function uploadImage is missing the @Multipart
annotation even though it accepts a @Part MultipartBody.Part; add the @Multipart
annotation to the function declaration so Retrofit treats the request as
multipart/form-data and keep the existing @POST and @Part parameters unchanged.


@POST("/api/auth/signup")
suspend fun signup(
@Body request: LoginRequest
): BaseResponse<LoginResponse>

@POST("/api/auth/login")
suspend fun login(
@Body request: LoginRequest
): BaseResponse<LoginResponse>

@GET("/api/auth/validate")
suspend fun validateToken(): BaseResponse<Boolean>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.example.kuit6_android_api.data.api

import android.content.Context
import com.example.kuit6_android_api.data.repository.TokenRepository
import kotlinx.coroutines.runBlocking
import okhttp3.Interceptor
import okhttp3.Response

class AuthInterceptor(
private val context: Context,
private val tokenRepository: TokenRepository
): Interceptor {

override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()

// DataStore에서 토큰 불러오기 (runBlocking 사용)
val token = runBlocking {
tokenRepository.getToken(context)
}

// 토큰이 있으면 Authorization 헤더에 추가
val newRequest = if (!token.isNullOrEmpty()) {
originalRequest.newBuilder()
.addHeader("Authorization", "Bearer $token")
.build()
} else {
originalRequest
}

return chain.proceed(newRequest)
}
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,26 @@
package com.example.kuit6_android_api.data.api

import android.content.Context
import com.example.kuit6_android_api.BuildConfig
import com.example.kuit6_android_api.data.repository.TokenRepository
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.util.concurrent.TimeUnit

object RetrofitClient {
class RetrofitClient(
private val context: Context,
private val tokenRepository: TokenRepository
) {

private val loggingInterceptor = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}

private val authInterceptor = AuthInterceptor(context, tokenRepository)
private val okHttpClient = OkHttpClient.Builder()
.addInterceptor(authInterceptor)
.addInterceptor(loggingInterceptor)
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.example.kuit6_android_api.data.di

import android.content.Context
import com.example.kuit6_android_api.data.api.ApiService
import com.example.kuit6_android_api.data.api.RetrofitClient
import com.example.kuit6_android_api.data.repository.LoginRepository
import com.example.kuit6_android_api.data.repository.LoginRepositoryImpl
import com.example.kuit6_android_api.data.repository.PostRepository
import com.example.kuit6_android_api.data.repository.PostRepositoryImpl
import com.example.kuit6_android_api.data.repository.TokenRepository
import com.example.kuit6_android_api.data.repository.TokenRepositoryImpl

class AppContainer(private val context: Context) {

val tokenRepository: TokenRepository by lazy{
TokenRepositoryImpl()
}

private val retrofitClient: RetrofitClient by lazy {
RetrofitClient(context, tokenRepository)
}

private val apiService: ApiService by lazy {
retrofitClient.apiService
}

val postRepository: PostRepository by lazy{
PostRepositoryImpl(apiService)
}

val loginRepository: LoginRepository by lazy{
LoginRepositoryImpl(apiService)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.example.kuit6_android_api.data.model.request

import kotlinx.serialization.Serializable

@Serializable
data class LoginRequest(
val username:String,
val password: String
)


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.Serializable

@Serializable
data class LoginResponse(
val token: String,
val userId: Long,
val username: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.example.kuit6_android_api.data.repository

import com.example.kuit6_android_api.data.model.response.LoginResponse

interface LoginRepository {
suspend fun signup(id:String, password: String): Result<LoginResponse>
suspend fun login(id:String, password: String): Result<LoginResponse>
suspend fun validateToken(): Result<Boolean>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.example.kuit6_android_api.data.repository

import android.util.Log
import com.example.kuit6_android_api.data.api.ApiService
import com.example.kuit6_android_api.data.model.request.LoginRequest
import com.example.kuit6_android_api.data.model.response.LoginResponse

class LoginRepositoryImpl(
private val apiService: ApiService
): LoginRepository {
override suspend fun signup(id: String, password: String): Result<LoginResponse> {
return runCatching {
val response = apiService.signup(LoginRequest(username = id, password = password))

if (response.success && response.data != null) {
response.data
} else {
throw Exception(response.message ?: "회원가입 실패")
}
}.onFailure { error ->
Log.e("LoginRepository", error.message.toString())
}
}

override suspend fun login(id: String, password: String): Result<LoginResponse> {
return runCatching {
val response = apiService.login(LoginRequest(username = id, password = password))

if (response.success && response.data != null) {
response.data
} else {
throw Exception(response.message ?: "로그인 실패")
}
}.onFailure { error ->
Log.e("LoginRepository", error.message.toString())
}
}

override suspend fun validateToken(): Result<Boolean> {
return runCatching {
val response = apiService.validateToken()

if (response.success && response.data != null) {
response.data
} else {
throw Exception(response.message ?: "토큰 검증 실패")
}
}.onFailure { error ->
Log.e("LoginRepository", error.message.toString())
}
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.example.kuit6_android_api.data.repository

import com.example.kuit6_android_api.data.model.request.PostCreateRequest
import com.example.kuit6_android_api.data.model.response.PostResponse
import okhttp3.MultipartBody

interface PostRepository{
suspend fun getPosts(): Result<List<PostResponse>>
suspend fun createPost(
author: String,
request: PostCreateRequest
): Result<PostResponse>
suspend fun getPostDetail(
id: Long
): Result<PostResponse>
suspend fun editPost(
id: Long,
request: PostCreateRequest
): Result<PostResponse>
suspend fun deletePost(
id: Long
): Result<Unit>
suspend fun uploadImage(
file: MultipartBody.Part
): Result<Map<String, String>>

}
Loading