diff --git a/data/src/main/java/com/foke/together/data/datasource/remote/RemoteDataSource.java b/data/src/main/java/com/foke/together/data/datasource/remote/RemoteDataSource.java deleted file mode 100644 index 92422b3..0000000 --- a/data/src/main/java/com/foke/together/data/datasource/remote/RemoteDataSource.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.foke.together.data.datasource.remote; - -public class RemoteDataSource { -} diff --git a/data/src/main/java/com/foke/together/data/datasource/remote/WebClientApi.kt b/data/src/main/java/com/foke/together/data/datasource/remote/WebClientApi.kt new file mode 100644 index 0000000..dfabc8f --- /dev/null +++ b/data/src/main/java/com/foke/together/data/datasource/remote/WebClientApi.kt @@ -0,0 +1,50 @@ +package com.foke.together.data.datasource.remote + +import com.foke.together.data.datasource.remote.dto.AccountRegisterRequest +import com.foke.together.data.datasource.remote.dto.AccountRegisterResponse +import com.foke.together.data.datasource.remote.dto.AccountSigninResponse +import com.foke.together.data.datasource.remote.dto.AccountWhoAmIResponse +import com.foke.together.data.datasource.remote.dto.S3PresignedUrlResponse +import okhttp3.RequestBody +import okhttp3.ResponseBody +import retrofit2.http.Body +import retrofit2.http.GET +import retrofit2.http.HeaderMap +import retrofit2.http.POST +import retrofit2.http.PUT +import retrofit2.http.Query +import retrofit2.http.Url + +interface WebClientApi { + // account + @POST("api/account/register") + suspend fun accountRegister( + @Body body: AccountRegisterRequest + ): Result + + @GET("api/account/register") + suspend fun accountSignin( + @Query("email") email: String, + @Query("password") password: String + ): Result + + @GET("api/account/who-am-i") + suspend fun accountWhoAmI( + @HeaderMap headers: HashMap, + ): Result + + // s3 + @GET("api/s3/presigned-url") + suspend fun s3PresignedUrl( + @HeaderMap headers: HashMap, + @Query("key") key: String, + @Query("ContentLength") contentLength: String, // max: 20971520 + ): Result + + @PUT + suspend fun sendFileToCloud( + @Url url: String, + @HeaderMap headers: HashMap, + @Body body: RequestBody + ): Result +} \ No newline at end of file diff --git a/data/src/main/java/com/foke/together/data/datasource/remote/WebClientModule.kt b/data/src/main/java/com/foke/together/data/datasource/remote/WebClientModule.kt new file mode 100644 index 0000000..6d16a31 --- /dev/null +++ b/data/src/main/java/com/foke/together/data/datasource/remote/WebClientModule.kt @@ -0,0 +1,50 @@ +package com.foke.together.data.datasource.remote + +import com.foke.together.util.AppPolicy +import com.foke.together.util.retrofit.NetworkCallAdapterFactory +import com.google.gson.GsonBuilder +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import okhttp3.OkHttpClient +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory +import java.util.concurrent.TimeUnit +import javax.inject.Named +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object WebClientModule { + + @Singleton + @Provides + @Named("okHttpWebClient") + fun provideOkHttpWebClient() = OkHttpClient.Builder() + .connectTimeout(AppPolicy.WEB_CONNECT_TIMEOUT, TimeUnit.SECONDS) + .readTimeout(AppPolicy.WEB_READ_TIMEOUT, TimeUnit.SECONDS) + .writeTimeout(AppPolicy.WEB_WRITE_TIMEOUT, TimeUnit.SECONDS) + .build() + + @Singleton + @Provides + @Named("webClientRetrofit") + fun provideWebClientRetrofit( + @Named("okHttpWebClient") okHttpClient: OkHttpClient + ) = Retrofit.Builder() + .baseUrl(AppPolicy.WEB_SERVER_URL) + .addConverterFactory(GsonConverterFactory.create( + GsonBuilder().setLenient().create() + )) + .addCallAdapterFactory(NetworkCallAdapterFactory()) + .client(okHttpClient) + .build() + + @Singleton + @Provides + fun provideWebClientApi( + @Named("webClientRetrofit") retrofit: Retrofit + ): WebClientApi = + retrofit.create(WebClientApi::class.java) +} \ No newline at end of file diff --git a/data/src/main/java/com/foke/together/data/datasource/remote/WebDataSource.kt b/data/src/main/java/com/foke/together/data/datasource/remote/WebDataSource.kt new file mode 100644 index 0000000..d113a12 --- /dev/null +++ b/data/src/main/java/com/foke/together/data/datasource/remote/WebDataSource.kt @@ -0,0 +1,62 @@ +package com.foke.together.data.datasource.remote + +import com.foke.together.data.datasource.remote.dto.AccountRegisterRequest +import com.foke.together.data.datasource.remote.dto.S3PresignedUrlResponse +import com.foke.together.util.AppPolicy +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.RequestBody.Companion.asRequestBody +import okhttp3.ResponseBody +import java.io.File +import javax.inject.Inject + +class WebDataSource @Inject constructor( + private val webClientApi: WebClientApi, +) { + private val headers = HashMap() + + suspend fun accountWhoAmI() = + webClientApi.accountWhoAmI(headers) + + suspend fun accountRegister(email: String, name: String, password: String) = + webClientApi.accountRegister( + AccountRegisterRequest(email, name, password) + ) + + suspend fun accountSignIn(email: String, password: String): Result { + webClientApi.accountSignin(email, password) + .onSuccess { + setToken(it.token) + return Result.success(Unit) + } + .onFailure { + setToken("") + return Result.failure(it) + } + return Result.failure(Exception("unknown error")) + } + + suspend fun s3PreSignedUrl(key: String, file: File): Result { + val contentLength = file.length() + if (contentLength > AppPolicy.WEB_FILE_MAX_CONTENT_LENGTH) { + return Result.failure(Exception("file size is over limit")) + } + return webClientApi.s3PresignedUrl( + headers = headers, + key = key, + contentLength = file.length().toString() + ) + } + + suspend fun sendFileToCloud(preSignedUrl: String, file: File): Result { + val requestBody = file.asRequestBody("application/octet-stream".toMediaType()) + return webClientApi.sendFileToCloud( + url = preSignedUrl, + hashMapOf("Content-Type" to requestBody.contentType().toString()), + body = requestBody + ) + } + + private fun setToken(token: String) { + headers["token"] = token + } +} \ No newline at end of file diff --git a/data/src/main/java/com/foke/together/data/datasource/remote/dto/AccountRegisterRequest.kt b/data/src/main/java/com/foke/together/data/datasource/remote/dto/AccountRegisterRequest.kt new file mode 100644 index 0000000..3e287ed --- /dev/null +++ b/data/src/main/java/com/foke/together/data/datasource/remote/dto/AccountRegisterRequest.kt @@ -0,0 +1,7 @@ +package com.foke.together.data.datasource.remote.dto + +data class AccountRegisterRequest( + val email: String, // max 32 + val name: String, // max 32 + val password: String // max 32 +) \ No newline at end of file diff --git a/data/src/main/java/com/foke/together/data/datasource/remote/dto/AccountRegisterResponse.kt b/data/src/main/java/com/foke/together/data/datasource/remote/dto/AccountRegisterResponse.kt new file mode 100644 index 0000000..7edb528 --- /dev/null +++ b/data/src/main/java/com/foke/together/data/datasource/remote/dto/AccountRegisterResponse.kt @@ -0,0 +1,7 @@ +package com.foke.together.data.datasource.remote.dto + +import com.google.gson.annotations.SerializedName + +data class AccountRegisterResponse ( + @SerializedName("token") val token: String +) \ No newline at end of file diff --git a/data/src/main/java/com/foke/together/data/datasource/remote/dto/AccountSigninResponse.kt b/data/src/main/java/com/foke/together/data/datasource/remote/dto/AccountSigninResponse.kt new file mode 100644 index 0000000..d51b773 --- /dev/null +++ b/data/src/main/java/com/foke/together/data/datasource/remote/dto/AccountSigninResponse.kt @@ -0,0 +1,7 @@ +package com.foke.together.data.datasource.remote.dto + +import com.google.gson.annotations.SerializedName + +data class AccountSigninResponse ( + @SerializedName("token") val token: String +) \ No newline at end of file diff --git a/data/src/main/java/com/foke/together/data/datasource/remote/dto/AccountWhoAmIResponse.kt b/data/src/main/java/com/foke/together/data/datasource/remote/dto/AccountWhoAmIResponse.kt new file mode 100644 index 0000000..a6702d4 --- /dev/null +++ b/data/src/main/java/com/foke/together/data/datasource/remote/dto/AccountWhoAmIResponse.kt @@ -0,0 +1,9 @@ +package com.foke.together.data.datasource.remote.dto + +import com.google.gson.annotations.SerializedName + +data class AccountWhoAmIResponse ( + @SerializedName("name") val name: String, + @SerializedName("email") val email: String, + @SerializedName("id") val id: String +) \ No newline at end of file diff --git a/data/src/main/java/com/foke/together/data/datasource/remote/dto/S3PresignedUrlResponse.kt b/data/src/main/java/com/foke/together/data/datasource/remote/dto/S3PresignedUrlResponse.kt new file mode 100644 index 0000000..4f23953 --- /dev/null +++ b/data/src/main/java/com/foke/together/data/datasource/remote/dto/S3PresignedUrlResponse.kt @@ -0,0 +1,7 @@ +package com.foke.together.data.datasource.remote.dto + +import com.google.gson.annotations.SerializedName + +data class S3PresignedUrlResponse ( + @SerializedName("presignedUrl") val presignedUrl: String, +) \ No newline at end of file diff --git a/data/src/main/java/com/foke/together/data/datasource/remote/interceptor/MockInterceptor.kt b/data/src/main/java/com/foke/together/data/datasource/remote/interceptor/MockInterceptor.kt new file mode 100644 index 0000000..bd0929d --- /dev/null +++ b/data/src/main/java/com/foke/together/data/datasource/remote/interceptor/MockInterceptor.kt @@ -0,0 +1,15 @@ +package com.foke.together.external.network.interceptor + +import okhttp3.Interceptor +import okhttp3.Protocol +import okhttp3.Response + +class MockInterceptor: Interceptor { + override fun intercept(chain: Interceptor.Chain): Response { + // TODO: make mock for test + return Response.Builder() + .request(chain.request()) + .protocol(Protocol.HTTP_2) + .build() + } +} \ No newline at end of file diff --git a/data/src/main/java/com/foke/together/data/repository/RemoteRepository.kt b/data/src/main/java/com/foke/together/data/repository/RemoteRepository.kt new file mode 100644 index 0000000..be1021d --- /dev/null +++ b/data/src/main/java/com/foke/together/data/repository/RemoteRepository.kt @@ -0,0 +1,66 @@ +package com.foke.together.data.repository + +import com.foke.together.data.datasource.remote.WebDataSource +import com.foke.together.domain.interactor.entity.AccountData +import com.foke.together.domain.output.RemoteRepositoryInterface +import java.io.File +import javax.inject.Inject + +class RemoteRepository @Inject constructor( + private val webDataSource: WebDataSource +) : RemoteRepositoryInterface { + override suspend fun registerAccount(data: AccountData): Result { + return data.name?.let { name -> + webDataSource.accountRegister(data.email, name, data.password) + .onSuccess { + return Result.success(Unit) + } + .onFailure { + return Result.failure(it) + } + Result.failure(Exception("unknown error")) + } ?: Result.failure(Exception("name is null")) + } + + override suspend fun signIn(data: AccountData): Result = + webDataSource.accountSignIn(data.email, data.password) + + override suspend fun getAccountStatus(): Result { + webDataSource.accountWhoAmI() + .onSuccess { + return Result.success( + AccountData(it.email, "", it.name) + ) + } + .onFailure { + return Result.failure(it) + } + return Result.failure(Exception("unknown error")) + } + + override suspend fun getUploadUrl(key: String, file: File): Result { + webDataSource.s3PreSignedUrl(key, file) + .onSuccess { + return Result.success(it.presignedUrl) + } + .onFailure { + return Result.failure(it) + } + return Result.failure(Exception("unknown error")) + } + + override suspend fun uploadFile(preSignedUrl: String, file: File): Result { + webDataSource.sendFileToCloud(preSignedUrl, file) + .onSuccess { + return Result.success(Unit) + } + .onFailure { + return Result.failure(it) + } + return Result.failure(Exception("unknown error")) + } + + companion object { + val TAG = RemoteRepository::class.java.simpleName + } +} \ No newline at end of file diff --git a/data/src/main/java/com/foke/together/data/repository/di/RepositoryModule.kt b/data/src/main/java/com/foke/together/data/repository/di/RepositoryModule.kt index 0d567a7..f26d07f 100644 --- a/data/src/main/java/com/foke/together/data/repository/di/RepositoryModule.kt +++ b/data/src/main/java/com/foke/together/data/repository/di/RepositoryModule.kt @@ -1,7 +1,9 @@ package com.foke.together.data.repository.di import com.foke.together.data.repository.AppPreferencesRepository +import com.foke.together.data.repository.RemoteRepository import com.foke.together.domain.output.AppPreferenceInterface +import com.foke.together.domain.output.RemoteRepositoryInterface import dagger.Binds import dagger.Module import dagger.hilt.InstallIn @@ -12,4 +14,7 @@ import dagger.hilt.android.components.ViewModelComponent abstract class RepositoryModule { @Binds abstract fun bindAppPreferenceRepository(appPreferenceRepository: AppPreferencesRepository): AppPreferenceInterface + + @Binds + abstract fun bindRemoteRepository(remoteRepository: RemoteRepository): RemoteRepositoryInterface } \ No newline at end of file diff --git a/domain/src/main/java/com/foke/together/domain/interactor/entity/AccountData.kt b/domain/src/main/java/com/foke/together/domain/interactor/entity/AccountData.kt new file mode 100644 index 0000000..75e24fa --- /dev/null +++ b/domain/src/main/java/com/foke/together/domain/interactor/entity/AccountData.kt @@ -0,0 +1,7 @@ +package com.foke.together.domain.interactor.entity + +data class AccountData ( + val email: String, + val password: String, + val name: String? +) \ No newline at end of file diff --git a/domain/src/main/java/com/foke/together/domain/interactor/entity/PresignedFileData.kt b/domain/src/main/java/com/foke/together/domain/interactor/entity/PresignedFileData.kt new file mode 100644 index 0000000..71346db --- /dev/null +++ b/domain/src/main/java/com/foke/together/domain/interactor/entity/PresignedFileData.kt @@ -0,0 +1,7 @@ +package com.foke.together.domain.interactor.entity + +data class PreSignedFileData ( + val key: String, + val contentLength: Long, + val preSignedUrl: String, +) \ No newline at end of file diff --git a/domain/src/main/java/com/foke/together/domain/output/RemoteRepositoryInterface.kt b/domain/src/main/java/com/foke/together/domain/output/RemoteRepositoryInterface.kt new file mode 100644 index 0000000..b63a8d7 --- /dev/null +++ b/domain/src/main/java/com/foke/together/domain/output/RemoteRepositoryInterface.kt @@ -0,0 +1,12 @@ +package com.foke.together.domain.output + +import com.foke.together.domain.interactor.entity.AccountData +import java.io.File + +interface RemoteRepositoryInterface { + suspend fun registerAccount(data: AccountData): Result + suspend fun signIn(data: AccountData): Result + suspend fun getAccountStatus(): Result + suspend fun getUploadUrl(key: String, file: File): Result + suspend fun uploadFile(preSignedUrl: String, file: File): Result +} \ No newline at end of file diff --git a/external/src/main/java/com/foke/together/external/network/ExternalCameraModule.kt b/external/src/main/java/com/foke/together/external/network/ExternalCameraModule.kt index 5adbad7..2496eca 100644 --- a/external/src/main/java/com/foke/together/external/network/ExternalCameraModule.kt +++ b/external/src/main/java/com/foke/together/external/network/ExternalCameraModule.kt @@ -30,7 +30,8 @@ object ExternalCameraModule { @Singleton @Provides - fun provideOkHttpClient( + @Named("okHttpExternalCameraClient") + fun provideOkHttpExternalCameraClient( baseUrlInterceptor: BaseUrlInterceptor ) = OkHttpClient.Builder() .connectTimeout(AppPolicy.EXTERNAL_CAMERA_CONNECT_TIMEOUT, TimeUnit.SECONDS) @@ -42,8 +43,9 @@ object ExternalCameraModule { @Singleton @Provides + @Named("externalCameraServerRetrofit") fun provideExternalCameraServerRetrofit( - okHttpClient: OkHttpClient, + @Named("okHttpExternalCameraClient") okHttpClient: OkHttpClient, @Named("cameraIPUrl") cameraIPUrl: String ) = Retrofit.Builder() .baseUrl(cameraIPUrl) @@ -56,6 +58,8 @@ object ExternalCameraModule { @Singleton @Provides - fun provideApiService(retrofit: Retrofit): ExternalCameraApi = + fun provideExternalCameraApi( + @Named("externalCameraServerRetrofit") retrofit: Retrofit + ): ExternalCameraApi = retrofit.create(ExternalCameraApi::class.java) } \ No newline at end of file diff --git a/util/src/main/java/com/foke/together/util/AppPolicy.kt b/util/src/main/java/com/foke/together/util/AppPolicy.kt index 794dd80..2a53c1f 100644 --- a/util/src/main/java/com/foke/together/util/AppPolicy.kt +++ b/util/src/main/java/com/foke/together/util/AppPolicy.kt @@ -6,6 +6,12 @@ object AppPolicy { const val DEFAULT_EXTERNAL_CAMERA_IP = "0.0.0.0" // network + const val WEB_SERVER_URL = "http://4cuts.store/" + const val WEB_CONNECT_TIMEOUT = 10L + const val WEB_READ_TIMEOUT = 10L + const val WEB_WRITE_TIMEOUT = 10L + const val WEB_FILE_MAX_CONTENT_LENGTH = 20971520 + const val EXTERNAL_CAMERA_DEFAULT_SERVER_URL = "http://0.0.0.0" const val EXTERNAL_CAMERA_CONNECT_TIMEOUT = 10L const val EXTERNAL_CAMERA_READ_TIMEOUT = 10L