From ba24c7ce4b291cc7246ba59b53e27f2df9d330f6 Mon Sep 17 00:00:00 2001 From: 2taezeat Date: Sun, 19 Nov 2023 01:47:50 +0900 Subject: [PATCH 001/165] =?UTF-8?q?feat=20:=20TokenUseCase=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EB=B0=8F=20saveToken=20=EC=84=B8=EB=B6=84=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- android/app/src/main/AndroidManifest.xml | 2 +- .../java/com/ohdodok/catchytape/MainActivity.kt | 8 +------- .../core/data/repository/AuthRepositoryImpl.kt | 17 ++++++++++++++--- .../core/domain/repository/AuthRepository.kt | 6 +++++- .../core/domain/usecase/LoginUseCase.kt | 2 +- .../core/domain/usecase/SignUpUseCase.kt | 2 +- .../core/domain/usecase/TokenUseCase.kt | 10 ++++++++++ 7 files changed, 33 insertions(+), 14 deletions(-) create mode 100644 android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/TokenUseCase.kt diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 7e1812a..710dae0 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -12,7 +12,7 @@ android:usesCleartextTraffic="true" android:theme="@style/Theme.CatchyTape"> diff --git a/android/app/src/main/java/com/ohdodok/catchytape/MainActivity.kt b/android/app/src/main/java/com/ohdodok/catchytape/MainActivity.kt index 8e7eb22..59ca690 100644 --- a/android/app/src/main/java/com/ohdodok/catchytape/MainActivity.kt +++ b/android/app/src/main/java/com/ohdodok/catchytape/MainActivity.kt @@ -1,9 +1,7 @@ package com.ohdodok.catchytape -import android.content.Intent -import androidx.appcompat.app.AppCompatActivity import android.os.Bundle -import com.ohdodok.catchytape.feature.login.LoginActivity +import androidx.appcompat.app.AppCompatActivity import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint @@ -11,9 +9,5 @@ class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) - - // TODO : 자동로그인 할 때 수정 필요 - val intent = Intent(this, LoginActivity::class.java) - startActivity(intent) } } \ No newline at end of file diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/AuthRepositoryImpl.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/AuthRepositoryImpl.kt index 06fae39..11809ad 100644 --- a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/AuthRepositoryImpl.kt +++ b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/AuthRepositoryImpl.kt @@ -9,7 +9,9 @@ import com.ohdodok.catchytape.core.data.model.LoginRequest import com.ohdodok.catchytape.core.data.model.SignUpRequest import com.ohdodok.catchytape.core.domain.repository.AuthRepository import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.map import javax.inject.Inject class AuthRepositoryImpl @Inject constructor( @@ -17,7 +19,8 @@ class AuthRepositoryImpl @Inject constructor( private val preferenceDataStore: DataStore ) : AuthRepository { - private val tokenKey = stringPreferencesKey("token") + private val idTokenKey = stringPreferencesKey("idToken") + private val accessTokenKey = stringPreferencesKey("accessToken") override fun loginWithGoogle(googleToken: String): Flow = flow { val response = userApi.login(LoginRequest(idToken = googleToken)) @@ -35,6 +38,7 @@ class AuthRepositoryImpl @Inject constructor( val response = userApi.signUp(SignUpRequest(idToken = googleToken, nickname = nickname)) if (response.isSuccessful) { response.body()?.let { loginResponse -> + saveIdToken(googleToken) emit(loginResponse.accessToken) } } else { @@ -44,8 +48,15 @@ class AuthRepositoryImpl @Inject constructor( } - override suspend fun saveToken(token: String) { - preferenceDataStore.edit { preferences -> preferences[tokenKey] = token } + override suspend fun saveAccessToken(token: String) { + preferenceDataStore.edit { preferences -> preferences[accessTokenKey] = token } } + override suspend fun saveIdToken(token: String) { + preferenceDataStore.edit { preferences -> preferences[idTokenKey] = token } + } + + override suspend fun getIdToken(): String = + preferenceDataStore.data.map { preferences -> preferences[idTokenKey] ?: "" }.first() + } \ No newline at end of file diff --git a/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/repository/AuthRepository.kt b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/repository/AuthRepository.kt index 7fe17e7..bfd1986 100644 --- a/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/repository/AuthRepository.kt +++ b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/repository/AuthRepository.kt @@ -8,6 +8,10 @@ interface AuthRepository { fun signUpWithGoogle(googleToken: String, nickname: String): Flow - suspend fun saveToken(token: String) + suspend fun saveAccessToken(token: String) + + suspend fun saveIdToken(token: String) + + suspend fun getIdToken(): String } \ No newline at end of file diff --git a/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/LoginUseCase.kt b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/LoginUseCase.kt index 972ad47..bd7af22 100644 --- a/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/LoginUseCase.kt +++ b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/LoginUseCase.kt @@ -10,5 +10,5 @@ class LoginUseCase @Inject constructor( ) { operator fun invoke(googleToken: String): Flow = - authRepository.loginWithGoogle(googleToken).map { authRepository.saveToken(it) } + authRepository.loginWithGoogle(googleToken).map { authRepository.saveAccessToken(it) } } \ No newline at end of file diff --git a/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/SignUpUseCase.kt b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/SignUpUseCase.kt index ea1ff17..9684369 100644 --- a/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/SignUpUseCase.kt +++ b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/SignUpUseCase.kt @@ -10,6 +10,6 @@ class SignUpUseCase @Inject constructor( ) { operator fun invoke(googleToken: String, nickname: String): Flow = - authRepository.signUpWithGoogle(googleToken, nickname).map { authRepository.saveToken(it) } + authRepository.signUpWithGoogle(googleToken, nickname).map { authRepository.saveAccessToken(it) } } \ No newline at end of file diff --git a/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/TokenUseCase.kt b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/TokenUseCase.kt new file mode 100644 index 0000000..bf91994 --- /dev/null +++ b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/TokenUseCase.kt @@ -0,0 +1,10 @@ +package com.ohdodok.catchytape.core.domain.usecase + +import com.ohdodok.catchytape.core.domain.repository.AuthRepository +import javax.inject.Inject + +class TokenUseCase @Inject constructor( + private val authRepository: AuthRepository +) { + suspend operator fun invoke() = authRepository.getIdToken() +} \ No newline at end of file From 11ef6381a9f74067852d625f1cc0581b90ecb1ce Mon Sep 17 00:00:00 2001 From: 2taezeat Date: Sun, 19 Nov 2023 03:09:05 +0900 Subject: [PATCH 002/165] =?UTF-8?q?feat=20:=20=20loginWithGoogle=EC=84=B1?= =?UTF-8?q?=EA=B3=B5=EC=8B=9C=20saveIdToken(googleToken)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../catchytape/core/data/repository/AuthRepositoryImpl.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/AuthRepositoryImpl.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/AuthRepositoryImpl.kt index 11809ad..7d37914 100644 --- a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/AuthRepositoryImpl.kt +++ b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/AuthRepositoryImpl.kt @@ -26,6 +26,7 @@ class AuthRepositoryImpl @Inject constructor( val response = userApi.login(LoginRequest(idToken = googleToken)) if (response.isSuccessful) { response.body()?.let { loginResponse -> + saveIdToken(googleToken) emit(loginResponse.accessToken) } } else if (response.code() == 401) { From a1aef44acc0373217dbabcda8d6b417fc91d2e37 Mon Sep 17 00:00:00 2001 From: 2taezeat Date: Sun, 19 Nov 2023 03:09:49 +0900 Subject: [PATCH 003/165] =?UTF-8?q?feat=20:=20BaseFragment=20collectFlow?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- android/app/src/main/AndroidManifest.xml | 8 ++++ .../catchytape/core/ui/BaseFragment.kt | 9 ++++ .../catchytape/feature/login/LoginFragment.kt | 41 +++++++++++-------- 3 files changed, 41 insertions(+), 17 deletions(-) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 710dae0..dadac8d 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -20,6 +20,14 @@ + + + + + + \ No newline at end of file diff --git a/android/core/ui/src/main/java/com/ohdodok/catchytape/core/ui/BaseFragment.kt b/android/core/ui/src/main/java/com/ohdodok/catchytape/core/ui/BaseFragment.kt index 32b89e3..da66f2c 100644 --- a/android/core/ui/src/main/java/com/ohdodok/catchytape/core/ui/BaseFragment.kt +++ b/android/core/ui/src/main/java/com/ohdodok/catchytape/core/ui/BaseFragment.kt @@ -15,6 +15,7 @@ import androidx.lifecycle.repeatOnLifecycle import androidx.navigation.fragment.findNavController import com.google.android.material.appbar.MaterialToolbar import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.launch abstract class BaseFragment( @@ -50,4 +51,12 @@ abstract class BaseFragment( lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED, block) } } + + fun collectFlow(flow: Flow, action: suspend (T) -> Unit) { + viewLifecycleOwner.lifecycleScope.launch { + viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { + flow.collect(action) + } + } + } } \ No newline at end of file diff --git a/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/LoginFragment.kt b/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/LoginFragment.kt index bf52ba9..13f10d4 100644 --- a/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/LoginFragment.kt +++ b/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/LoginFragment.kt @@ -1,12 +1,13 @@ package com.ohdodok.catchytape.feature.login import android.app.Activity +import android.content.ComponentName import android.content.Intent import android.os.Bundle import android.view.View import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts -import androidx.fragment.app.viewModels +import androidx.fragment.app.activityViewModels import androidx.navigation.fragment.findNavController import com.google.android.gms.auth.api.signin.GoogleSignIn import com.google.android.gms.auth.api.signin.GoogleSignInOptions @@ -14,10 +15,13 @@ import com.ohdodok.catchytape.core.ui.BaseFragment import com.ohdodok.catchytape.feature.login.databinding.FragmentLoginBinding import dagger.hilt.android.AndroidEntryPoint + @AndroidEntryPoint class LoginFragment : BaseFragment(R.layout.fragment_login) { - private val viewModel: LoginViewModel by viewModels() + private val viewModel: LoginViewModel by activityViewModels() + + private val loginActivity by lazy { activity as LoginActivity } private val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN) .requestIdToken(BuildConfig.GOOGLE_CLIENT_ID) @@ -47,22 +51,25 @@ class LoginFragment : BaseFragment(R.layout.fragment_login } private fun observeEvents() { - repeatOnStarted { - viewModel.events.collect { event -> - when (event) { - is LoginEvent.NavigateToHome -> { - activity?.finish() - } + collectFlow(viewModel.events) { handleUiEvent(it) } + } - is LoginEvent.NavigateToNickName -> { - findNavController().navigate( - LoginFragmentDirections.actionLoginFragmentToNicknameFragment( - googleToken = event.googleToken - ) - ) - } - } - } + + private fun handleUiEvent(event: LoginEvent) = when (event) { + is LoginEvent.NavigateToHome -> { + val intent = Intent() + intent.component = ComponentName("com.ohdodok.catchytape", "com.ohdodok.catchytape.MainActivity") + startActivity(intent) + loginActivity.finish() + } + + is LoginEvent.NavigateToNickName -> { + findNavController().navigate( + LoginFragmentDirections.actionLoginFragmentToNicknameFragment( + googleToken = event.googleToken + ) + ) } } + } From 356ae11398ce0007b533604318b195348116983c Mon Sep 17 00:00:00 2001 From: 2taezeat Date: Sun, 19 Nov 2023 03:10:07 +0900 Subject: [PATCH 004/165] feat : autoLoginWithIdToken --- .../feature/login/LoginViewModel.kt | 29 +++++++++++++++++-- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/LoginViewModel.kt b/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/LoginViewModel.kt index 53e4340..2145e2c 100644 --- a/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/LoginViewModel.kt +++ b/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/LoginViewModel.kt @@ -3,30 +3,53 @@ package com.ohdodok.catchytape.feature.login import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.ohdodok.catchytape.core.domain.usecase.LoginUseCase +import com.ohdodok.catchytape.core.domain.usecase.TokenUseCase import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel class LoginViewModel @Inject constructor( - private val loginUseCase: LoginUseCase + private val loginUseCase: LoginUseCase, + private val tokenUseCase: TokenUseCase + ) : ViewModel() { private val _events = MutableSharedFlow() val events = _events.asSharedFlow() - fun login(token: String) { + private val _isAutoLoginFinish = MutableSharedFlow() + val isAutoLoginFinish = _isAutoLoginFinish.asSharedFlow() + + fun login(token: String, isAutoLogin: Boolean = false) { loginUseCase(token) .catch { - _events.emit(LoginEvent.NavigateToNickName(token)) + if (isAutoLogin.not()) { + _events.emit(LoginEvent.NavigateToNickName(token)) + } }.onEach { _events.emit(LoginEvent.NavigateToHome) }.launchIn(viewModelScope) } + + + fun autoLoginWithIdToken() { + viewModelScope.launch { + val idToken = tokenUseCase() + if (idToken.isNotEmpty()) { + login(idToken, true) + } + delay(1000) + _isAutoLoginFinish.emit(true) + } + } + } sealed interface LoginEvent { From f7abaeb0d6ba91c5022c69212506ba90ba10518d Mon Sep 17 00:00:00 2001 From: 2taezeat Date: Sun, 19 Nov 2023 03:15:07 +0900 Subject: [PATCH 005/165] feat : checkAutoLoginIsFinish --- .../ohdodok/catchytape/feature/login/LoginActivity.kt | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/LoginActivity.kt b/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/LoginActivity.kt index 59d0628..fa2600a 100644 --- a/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/LoginActivity.kt +++ b/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/LoginActivity.kt @@ -1,12 +1,21 @@ package com.ohdodok.catchytape.feature.login -import androidx.appcompat.app.AppCompatActivity import android.os.Bundle +import android.view.View +import android.view.ViewTreeObserver +import androidx.activity.viewModels +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.launch @AndroidEntryPoint class LoginActivity : AppCompatActivity() { + private val viewModel: LoginViewModel by viewModels() + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_login) From 17e097e9633dd4cb396f0aa67321835e26740e45 Mon Sep 17 00:00:00 2001 From: 2taezeat Date: Sun, 19 Nov 2023 03:33:58 +0900 Subject: [PATCH 006/165] =?UTF-8?q?feat=20:=20isSplashFinishReady=20?= =?UTF-8?q?=EB=B3=80=EC=88=98=EB=A1=9C=20=EC=B6=94=EC=B6=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../catchytape/feature/login/LoginActivity.kt | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/LoginActivity.kt b/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/LoginActivity.kt index fa2600a..c419c4a 100644 --- a/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/LoginActivity.kt +++ b/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/LoginActivity.kt @@ -16,8 +16,33 @@ import kotlinx.coroutines.launch class LoginActivity : AppCompatActivity() { private val viewModel: LoginViewModel by viewModels() + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_login) + var isSplashFinishReady = false + viewModel.autoLoginWithIdToken() + + val content: View = findViewById(android.R.id.content) + content.viewTreeObserver.addOnPreDrawListener( + object : ViewTreeObserver.OnPreDrawListener { + override fun onPreDraw(): Boolean { + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + viewModel.isAutoLoginFinish.collect { isSplashFinishReady = it } + } + } + + if (isSplashFinishReady) { + content.viewTreeObserver.removeOnPreDrawListener(this) + return true + } else { + return false + } + } + } + ) } -} \ No newline at end of file +} + + From c3bb576df35691f4ca0377ddfeb3923890a6f4fc Mon Sep 17 00:00:00 2001 From: 2taezeat Date: Sun, 19 Nov 2023 03:35:15 +0900 Subject: [PATCH 007/165] =?UTF-8?q?chore=20:=20timber=EC=97=90=EC=84=9C=20?= =?UTF-8?q?""(=EA=B3=B5=EB=B0=B1)=20=EC=9D=B8=20=EA=B2=BD=EC=9A=B0=20log?= =?UTF-8?q?=EA=B0=80=20=EC=B6=9C=EB=A0=A5=20=EB=90=98=EC=A7=80=20=EC=83=81?= =?UTF-8?q?=ED=99=A9,=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/ohdodok/catchytape/CtApplication.kt | 8 ++++++++ android/app/src/main/res/values/strings.xml | 1 + 2 files changed, 9 insertions(+) diff --git a/android/app/src/main/java/com/ohdodok/catchytape/CtApplication.kt b/android/app/src/main/java/com/ohdodok/catchytape/CtApplication.kt index d43fb73..522fef1 100644 --- a/android/app/src/main/java/com/ohdodok/catchytape/CtApplication.kt +++ b/android/app/src/main/java/com/ohdodok/catchytape/CtApplication.kt @@ -10,6 +10,14 @@ class CtApplication : Application() { super.onCreate() if (BuildConfig.DEBUG) { Timber.plant(object : Timber.DebugTree() { + override fun d(message: String?, vararg args: Any?) { + if (message != null && message.isEmpty()) { + super.d(getString(R.string.timber_blank_string), *args) + } else { + super.d(message, *args) + } + } + override fun createStackElementTag(element: StackTraceElement): String { return String.format( getString(R.string.timber_log_format), diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index b9ef990..5bcb116 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -1,4 +1,5 @@ Catchy Tape C: %s, L: %s + "BLANK("")" \ No newline at end of file From 7d484e65de74205d79a7e8f9130742aaf0498faa Mon Sep 17 00:00:00 2001 From: 2taezeat Date: Sun, 19 Nov 2023 14:51:56 +0900 Subject: [PATCH 008/165] =?UTF-8?q?feat=20:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20flow=20=EB=B0=8F=20collect=20=ED=96=89=EC=9C=84=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0,=20=EB=8B=A8=EC=88=9C=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../catchytape/feature/login/LoginActivity.kt | 14 +------------- .../catchytape/feature/login/LoginViewModel.kt | 7 ++++--- 2 files changed, 5 insertions(+), 16 deletions(-) diff --git a/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/LoginActivity.kt b/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/LoginActivity.kt index c419c4a..c5b5c7b 100644 --- a/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/LoginActivity.kt +++ b/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/LoginActivity.kt @@ -5,35 +5,23 @@ import android.view.View import android.view.ViewTreeObserver import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle import dagger.hilt.android.AndroidEntryPoint -import kotlinx.coroutines.launch @AndroidEntryPoint class LoginActivity : AppCompatActivity() { private val viewModel: LoginViewModel by viewModels() - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_login) - var isSplashFinishReady = false viewModel.autoLoginWithIdToken() val content: View = findViewById(android.R.id.content) content.viewTreeObserver.addOnPreDrawListener( object : ViewTreeObserver.OnPreDrawListener { override fun onPreDraw(): Boolean { - lifecycleScope.launch { - repeatOnLifecycle(Lifecycle.State.STARTED) { - viewModel.isAutoLoginFinish.collect { isSplashFinishReady = it } - } - } - - if (isSplashFinishReady) { + if (viewModel.isAutoLoginFinish) { content.viewTreeObserver.removeOnPreDrawListener(this) return true } else { diff --git a/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/LoginViewModel.kt b/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/LoginViewModel.kt index 2145e2c..98ea96f 100644 --- a/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/LoginViewModel.kt +++ b/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/LoginViewModel.kt @@ -24,8 +24,9 @@ class LoginViewModel @Inject constructor( private val _events = MutableSharedFlow() val events = _events.asSharedFlow() - private val _isAutoLoginFinish = MutableSharedFlow() - val isAutoLoginFinish = _isAutoLoginFinish.asSharedFlow() + private var _isAutoLoginFinish = false + val isAutoLoginFinish get() = _isAutoLoginFinish + fun login(token: String, isAutoLogin: Boolean = false) { loginUseCase(token) @@ -46,7 +47,7 @@ class LoginViewModel @Inject constructor( login(idToken, true) } delay(1000) - _isAutoLoginFinish.emit(true) + _isAutoLoginFinish = true } } From 84a4e87cf96dfb3e16907cd4740defe643661d92 Mon Sep 17 00:00:00 2001 From: Seokyung Lim Date: Sun, 19 Nov 2023 20:49:09 +0900 Subject: [PATCH 009/165] =?UTF-8?q?feat=20:=20=EC=84=9C=EB=B2=84=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=9E=90=EB=8F=99=ED=99=94=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 환경 변수는 사용되면 추후 추가 예정 (DB, NCP 인증키) --- .github/workflows/server-dev-ci.yml | 44 +++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 .github/workflows/server-dev-ci.yml diff --git a/.github/workflows/server-dev-ci.yml b/.github/workflows/server-dev-ci.yml new file mode 100644 index 0000000..ea241a1 --- /dev/null +++ b/.github/workflows/server-dev-ci.yml @@ -0,0 +1,44 @@ +name: Backend Dev CI + +on: + pull_request: + branches: [develop] + paths: + - "server/**" + +defaults: + run: + working-directory: ./server + +jobs: + BACKEND-CI: + runs-on: ubuntu-20.04 + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Use NodeJS + uses: actions/setup-node@v2 + with: + node-version: 20.8.1 + + - name: Cache node modules + id: cache + uses: actions/cache@v2 + with: + path: "**/node_modules" + key: npm-packages-${{ hashFiles('**/package-lock.json') }} + + - name: Install Dependency + if: steps.cache.outputs.cache-hit != 'true' + run: npm install + + - name: Execute Test + run: npm run test + + - name: Record Result in Pull Request + uses: EnricoMi/publish-unit-test-result-action@v1 + if: always() + with: + files: "**/build/test-results/test/TEST-*.xml" From 9a62a9f65378ceb209ab2d243b014519afbc3243 Mon Sep 17 00:00:00 2001 From: algosketch Date: Sun, 19 Nov 2023 23:12:19 +0900 Subject: [PATCH 010/165] =?UTF-8?q?chore=20:=20kotest=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=20=EB=B0=8F=20CI=EB=A5=BC=20=EC=9C=84=ED=95=9C=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/android-pull-request-ci.yml | 2 +- android/build.gradle.kts | 9 +++++++++ android/core/domain/build.gradle.kts | 10 +++++++++- .../catchytape/core/domain/signup/TestConfig.kt | 16 ++++++++++++++++ android/gradle/libs.versions.toml | 4 ++++ 5 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 android/core/domain/src/test/java/com/ohdodok/catchytape/core/domain/signup/TestConfig.kt diff --git a/.github/workflows/android-pull-request-ci.yml b/.github/workflows/android-pull-request-ci.yml index 11f050a..82f5461 100644 --- a/.github/workflows/android-pull-request-ci.yml +++ b/.github/workflows/android-pull-request-ci.yml @@ -35,7 +35,7 @@ jobs: DEBUG_KEYSTORE: ${{ secrets.DEBUG_KEYSTORE }} KEYSTORE_PROPERTIES: ${{ secrets.KEYSTORE_PROPERTIES }} run: | - echo "$DEBUG_KEYSTORE" | base64 -d > keystore.properties + echo "$DEBUG_KEYSTORE" | base64 -d > debug.keystore echo "$KEYSTORE_PROPERTIES" > keystore.properties ./gradlew testDebugUnitTest --stacktrace diff --git a/android/build.gradle.kts b/android/build.gradle.kts index 874891b..5cfdda4 100644 --- a/android/build.gradle.kts +++ b/android/build.gradle.kts @@ -8,4 +8,13 @@ plugins { alias(libs.plugins.hilt) apply false alias(libs.plugins.kotlinx.serialization) apply false } + +tasks.register("test") { + useJUnitPlatform() + reports { + junitXml.required.set(false) + } + systemProperty("gradle.build.dir", project.buildDir) +} + true // Needed to make the Suppress annotation work for the plugins block \ No newline at end of file diff --git a/android/core/domain/build.gradle.kts b/android/core/domain/build.gradle.kts index dc48cd3..a67a4e6 100644 --- a/android/core/domain/build.gradle.kts +++ b/android/core/domain/build.gradle.kts @@ -3,6 +3,14 @@ plugins { alias(libs.plugins.org.jetbrains.kotlin.jvm) } -dependencies { +tasks.withType().configureEach { + useJUnitPlatform() +} +dependencies { + testImplementation(libs.junit) + testImplementation(libs.kotest.runner) + testImplementation (libs.kotest.property) + testImplementation (libs.kotest.extentions.junitxml) + implementation("javax.inject:javax.inject:1") } diff --git a/android/core/domain/src/test/java/com/ohdodok/catchytape/core/domain/signup/TestConfig.kt b/android/core/domain/src/test/java/com/ohdodok/catchytape/core/domain/signup/TestConfig.kt new file mode 100644 index 0000000..2a00dbe --- /dev/null +++ b/android/core/domain/src/test/java/com/ohdodok/catchytape/core/domain/signup/TestConfig.kt @@ -0,0 +1,16 @@ +package com.ohdodok.catchytape.core.domain.signup + +import io.kotest.core.config.AbstractProjectConfig +import io.kotest.core.extensions.Extension +import io.kotest.extensions.junitxml.JunitXmlReporter + +class TestConfig : AbstractProjectConfig() { + + override fun extensions(): List = listOf( + JunitXmlReporter( + includeContainers = false, // don't write out status for all tests + useTestPathAsName = true, // use the full test path (ie, includes parent test names) + outputDir = "test-results/excludeContainers" + ) + ) +} \ No newline at end of file diff --git a/android/gradle/libs.versions.toml b/android/gradle/libs.versions.toml index cfbb623..6525c6e 100644 --- a/android/gradle/libs.versions.toml +++ b/android/gradle/libs.versions.toml @@ -12,6 +12,7 @@ hilt = "2.48" gms = "20.7.0" junit = "4.13.2" +kotest = "5.8.0" retrofit = "2.9.0" okhttp = "4.11.0" @@ -35,6 +36,9 @@ hilt-compiler = { group = "com.google.dagger", name = "hilt-compiler", version.r google-play-services = { group = "com.google.android.gms", name = "play-services-auth", version.ref = "gms" } +kotest-runner = { group = "io.kotest", name = "kotest-runner-junit5", version.ref = "kotest"} +kotest-property = { group = "io.kotest", name = "kotest-property", version.ref = "kotest"} +kotest-extentions-junitxml = { group = "io.kotest", name = "kotest-extensions-junitxml", version.ref = "kotest"} junit = { group = "junit", name = "junit", version.ref = "junit" } retrofit = { module = "com.squareup.retrofit2:retrofit", name = "retrofit", version.ref = "retrofit" } From 599315d4a5fc2014471b7cec5a4ad7de17c9659d Mon Sep 17 00:00:00 2001 From: algosketch Date: Sun, 19 Nov 2023 23:23:10 +0900 Subject: [PATCH 011/165] =?UTF-8?q?test=20:=20=EB=8B=89=EB=84=A4=EC=9E=84?= =?UTF-8?q?=20=EC=9C=A0=ED=9A=A8=EC=84=B1=20=EA=B2=80=EC=82=AC=20=EB=8B=A8?= =?UTF-8?q?=EC=9C=84=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../signup/NicknameValidationUseCase.kt | 17 +++++++ .../signup/NicknameValidationUseCaseTest.kt | 48 +++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/signup/NicknameValidationUseCase.kt create mode 100644 android/core/domain/src/test/java/com/ohdodok/catchytape/core/domain/signup/NicknameValidationUseCaseTest.kt diff --git a/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/signup/NicknameValidationUseCase.kt b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/signup/NicknameValidationUseCase.kt new file mode 100644 index 0000000..1b9c1c0 --- /dev/null +++ b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/signup/NicknameValidationUseCase.kt @@ -0,0 +1,17 @@ +package com.ohdodok.catchytape.core.domain.signup + +import javax.inject.Inject + +enum class NicknameValidationResult { + VALID, + EMPTY, + INVALID_LENGTH, + INVALID_CHARACTER, +} + +class NicknameValidationUseCase @Inject constructor() { + operator fun invoke(nickname: String): NicknameValidationResult { + return NicknameValidationResult.EMPTY +// TODO("닉네임 유효성 검사 구현") + } +} \ No newline at end of file diff --git a/android/core/domain/src/test/java/com/ohdodok/catchytape/core/domain/signup/NicknameValidationUseCaseTest.kt b/android/core/domain/src/test/java/com/ohdodok/catchytape/core/domain/signup/NicknameValidationUseCaseTest.kt new file mode 100644 index 0000000..5d834ca --- /dev/null +++ b/android/core/domain/src/test/java/com/ohdodok/catchytape/core/domain/signup/NicknameValidationUseCaseTest.kt @@ -0,0 +1,48 @@ +package com.ohdodok.catchytape.core.domain.signup + +import io.kotest.core.spec.style.BehaviorSpec +import io.kotest.matchers.shouldBe + +class NicknameValidationUseCaseTest : BehaviorSpec() { + private val nicknameValidationUseCase = NicknameValidationUseCase() + + init { + given("유효한 닉네임이 주어지고") { + `when`("유효성을 검사하면") { + then("Valid를 반환한다") { + listOf("아이유", "iu", "20", "가a1_.", "특수문자_.").forEach { + nicknameValidationUseCase(nickname = it) shouldBe NicknameValidationResult.VALID + } + } + } + } + + given("비어 있는 닉네임이 주어지고") { + `when`("유효성을 검사하면") { + then("Empty를 반환한다") { + nicknameValidationUseCase(nickname = "") shouldBe NicknameValidationResult.EMPTY + } + } + } + + given("짧거나 긴 닉네임이 주어지고") { + `when`("유효성을 검사하면") { + then("Invalid length를 반환한다") { + listOf("한", "닉네임을이렇게길게지으면어떡해", "a").forEach { + nicknameValidationUseCase(nickname = it) shouldBe NicknameValidationResult.INVALID_LENGTH + } + } + } + } + + given("사용할 수 없는 문자가 포함된 닉네임이 주어지고") { + `when`("유효성을 검사하면") { + then("Invalid length를 반환한다") { + listOf("안 돼", "특수문자^", "특수문자*").forEach { + nicknameValidationUseCase(nickname = it) shouldBe NicknameValidationResult.INVALID_CHARACTER + } + } + } + } + } +} \ No newline at end of file From ab8b62a5ab3c4ad0becabfbafcc91ca58037eb6e Mon Sep 17 00:00:00 2001 From: algosketch Date: Sun, 19 Nov 2023 23:31:37 +0900 Subject: [PATCH 012/165] =?UTF-8?q?feat=20:=20=EB=8B=89=EB=84=A4=EC=9E=84?= =?UTF-8?q?=20=EC=9C=A0=ED=9A=A8=EC=84=B1=20=EA=B2=80=EC=82=AC=20UseCase?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/domain/signup/NicknameValidationUseCase.kt | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/signup/NicknameValidationUseCase.kt b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/signup/NicknameValidationUseCase.kt index 1b9c1c0..1072498 100644 --- a/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/signup/NicknameValidationUseCase.kt +++ b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/signup/NicknameValidationUseCase.kt @@ -11,7 +11,13 @@ enum class NicknameValidationResult { class NicknameValidationUseCase @Inject constructor() { operator fun invoke(nickname: String): NicknameValidationResult { - return NicknameValidationResult.EMPTY -// TODO("닉네임 유효성 검사 구현") + val regex = "(^[ㄱ-ㅎ가-힣\\w_.]{2,10}$)".toRegex() + + return when { + regex.matches(nickname) -> NicknameValidationResult.VALID + nickname.isBlank() -> NicknameValidationResult.EMPTY + nickname.length !in 2..10 -> NicknameValidationResult.INVALID_LENGTH + else -> NicknameValidationResult.INVALID_CHARACTER + } } } \ No newline at end of file From 95353ed63d3e07c315a4f97360686cd65c3c2785 Mon Sep 17 00:00:00 2001 From: Yura Park <62279741+youlalala@users.noreply.github.com> Date: Mon, 20 Nov 2023 11:48:59 +0900 Subject: [PATCH 013/165] =?UTF-8?q?docs=20:=20README=20=EC=97=85=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 33f39df..ff5a82e 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ | DI | Hilt | | Network | Retrofit, Kotlin Serialization | [역/직렬화 라이브러리 비교](https://github.com/boostcampwm2023/and04-catchy-tape/wiki/%EC%97%AD-%EC%A7%81%EB%A0%AC%ED%99%94-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-%EB%B9%84%EA%B5%90) | Jetpack | Navigation | -| CI | Github Actions | [PR에 대한 단위 테스트 자동화](https://algosketch.tistory.com/178) +| CI | Github Actions | [PR에 대한 단위 테스트 자동화](https://algosketch.tistory.com/178) / [APK Release 자동화](https://tral-lalala.tistory.com/127)
그 외 기록 From 69eb865a457238925ebaac7ae9d82f3edbb14778 Mon Sep 17 00:00:00 2001 From: Yura Park <62279741+youlalala@users.noreply.github.com> Date: Mon, 20 Nov 2023 11:50:19 +0900 Subject: [PATCH 014/165] =?UTF-8?q?docs=20:=20README=20=EC=97=85=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ff5a82e..93b64d6 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ | DI | Hilt | | Network | Retrofit, Kotlin Serialization | [역/직렬화 라이브러리 비교](https://github.com/boostcampwm2023/and04-catchy-tape/wiki/%EC%97%AD-%EC%A7%81%EB%A0%AC%ED%99%94-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-%EB%B9%84%EA%B5%90) | Jetpack | Navigation | -| CI | Github Actions | [PR에 대한 단위 테스트 자동화](https://algosketch.tistory.com/178) / [APK Release 자동화](https://tral-lalala.tistory.com/127) +| CI/CD | Github Actions | [PR에 대한 단위 테스트 자동화](https://algosketch.tistory.com/178) / [APK Release 자동화](https://tral-lalala.tistory.com/127)
그 외 기록 From 06957d1006e74cd2dea419e0283bf0dbbdbfe02c Mon Sep 17 00:00:00 2001 From: 2taezeat Date: Mon, 20 Nov 2023 16:14:51 +0900 Subject: [PATCH 015/165] =?UTF-8?q?refactor=20:=20usecase=20=EB=B0=8F=20?= =?UTF-8?q?=EB=B6=88=ED=95=84=EC=9A=94=ED=95=9C=20backing=20property=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../usecase/{TokenUseCase.kt => GetIdTokenUseCase.kt} | 2 +- .../ohdodok/catchytape/feature/login/LoginViewModel.kt | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) rename android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/{TokenUseCase.kt => GetIdTokenUseCase.kt} (85%) diff --git a/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/TokenUseCase.kt b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/GetIdTokenUseCase.kt similarity index 85% rename from android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/TokenUseCase.kt rename to android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/GetIdTokenUseCase.kt index bf91994..83db5a2 100644 --- a/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/TokenUseCase.kt +++ b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/GetIdTokenUseCase.kt @@ -3,7 +3,7 @@ package com.ohdodok.catchytape.core.domain.usecase import com.ohdodok.catchytape.core.domain.repository.AuthRepository import javax.inject.Inject -class TokenUseCase @Inject constructor( +class GetIdTokenUseCase @Inject constructor( private val authRepository: AuthRepository ) { suspend operator fun invoke() = authRepository.getIdToken() diff --git a/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/LoginViewModel.kt b/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/LoginViewModel.kt index 98ea96f..c263dc4 100644 --- a/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/LoginViewModel.kt +++ b/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/LoginViewModel.kt @@ -2,8 +2,8 @@ package com.ohdodok.catchytape.feature.login import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.ohdodok.catchytape.core.domain.usecase.GetIdTokenUseCase import com.ohdodok.catchytape.core.domain.usecase.LoginUseCase -import com.ohdodok.catchytape.core.domain.usecase.TokenUseCase import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableSharedFlow @@ -17,15 +17,15 @@ import javax.inject.Inject @HiltViewModel class LoginViewModel @Inject constructor( private val loginUseCase: LoginUseCase, - private val tokenUseCase: TokenUseCase + private val tokenUseCase: GetIdTokenUseCase ) : ViewModel() { private val _events = MutableSharedFlow() val events = _events.asSharedFlow() - private var _isAutoLoginFinish = false - val isAutoLoginFinish get() = _isAutoLoginFinish + var isAutoLoginFinish: Boolean = false + private set fun login(token: String, isAutoLogin: Boolean = false) { @@ -47,7 +47,7 @@ class LoginViewModel @Inject constructor( login(idToken, true) } delay(1000) - _isAutoLoginFinish = true + isAutoLoginFinish = true } } From 0b996230a48fc337534d5535008398f323a2df93 Mon Sep 17 00:00:00 2001 From: 2taezeat Date: Mon, 20 Nov 2023 16:54:22 +0900 Subject: [PATCH 016/165] =?UTF-8?q?refactor=20:=20=ED=95=A8=EC=88=98?= =?UTF-8?q?=EB=AA=85=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?=EB=B0=8F=20BindingAdapter=20=EC=9C=84=EC=B9=98=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../catchytape/core/ui/BaseFragment.kt | 8 ----- .../catchytape/feature/login/LoginActivity.kt | 4 +-- .../catchytape/feature/login/LoginFragment.kt | 36 ++++++++++--------- .../feature/login/LoginViewModel.kt | 6 ++-- .../feature/upload/BindingAdapter.kt} | 2 +- 5 files changed, 25 insertions(+), 31 deletions(-) rename android/{core/ui/src/main/java/com/ohdodok/catchytape/core/ui/bindingadapter/UploadBindingAdapter.kt => feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/BindingAdapter.kt} (83%) diff --git a/android/core/ui/src/main/java/com/ohdodok/catchytape/core/ui/BaseFragment.kt b/android/core/ui/src/main/java/com/ohdodok/catchytape/core/ui/BaseFragment.kt index da66f2c..9f3f91c 100644 --- a/android/core/ui/src/main/java/com/ohdodok/catchytape/core/ui/BaseFragment.kt +++ b/android/core/ui/src/main/java/com/ohdodok/catchytape/core/ui/BaseFragment.kt @@ -15,7 +15,6 @@ import androidx.lifecycle.repeatOnLifecycle import androidx.navigation.fragment.findNavController import com.google.android.material.appbar.MaterialToolbar import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.launch abstract class BaseFragment( @@ -52,11 +51,4 @@ abstract class BaseFragment( } } - fun collectFlow(flow: Flow, action: suspend (T) -> Unit) { - viewLifecycleOwner.lifecycleScope.launch { - viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { - flow.collect(action) - } - } - } } \ No newline at end of file diff --git a/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/LoginActivity.kt b/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/LoginActivity.kt index c5b5c7b..4685815 100644 --- a/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/LoginActivity.kt +++ b/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/LoginActivity.kt @@ -15,13 +15,13 @@ class LoginActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_login) - viewModel.autoLoginWithIdToken() + viewModel.automaticallyLogin() val content: View = findViewById(android.R.id.content) content.viewTreeObserver.addOnPreDrawListener( object : ViewTreeObserver.OnPreDrawListener { override fun onPreDraw(): Boolean { - if (viewModel.isAutoLoginFinish) { + if (viewModel.isAutoLoginFinished) { content.viewTreeObserver.removeOnPreDrawListener(this) return true } else { diff --git a/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/LoginFragment.kt b/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/LoginFragment.kt index 13f10d4..72c7f4e 100644 --- a/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/LoginFragment.kt +++ b/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/LoginFragment.kt @@ -51,25 +51,27 @@ class LoginFragment : BaseFragment(R.layout.fragment_login } private fun observeEvents() { - collectFlow(viewModel.events) { handleUiEvent(it) } - } - - - private fun handleUiEvent(event: LoginEvent) = when (event) { - is LoginEvent.NavigateToHome -> { - val intent = Intent() - intent.component = ComponentName("com.ohdodok.catchytape", "com.ohdodok.catchytape.MainActivity") - startActivity(intent) - loginActivity.finish() - } + repeatOnStarted { + viewModel.events.collect { event -> + when (event) { + is LoginEvent.NavigateToHome -> { + val intent = Intent() + intent.component = ComponentName("com.ohdodok.catchytape", "com.ohdodok.catchytape.MainActivity") + startActivity(intent) + loginActivity.finish() + } - is LoginEvent.NavigateToNickName -> { - findNavController().navigate( - LoginFragmentDirections.actionLoginFragmentToNicknameFragment( - googleToken = event.googleToken - ) - ) + is LoginEvent.NavigateToNickName -> { + findNavController().navigate( + LoginFragmentDirections.actionLoginFragmentToNicknameFragment( + googleToken = event.googleToken + ) + ) + } + } + } } } + } diff --git a/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/LoginViewModel.kt b/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/LoginViewModel.kt index c263dc4..fb311a2 100644 --- a/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/LoginViewModel.kt +++ b/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/LoginViewModel.kt @@ -24,7 +24,7 @@ class LoginViewModel @Inject constructor( private val _events = MutableSharedFlow() val events = _events.asSharedFlow() - var isAutoLoginFinish: Boolean = false + var isAutoLoginFinished: Boolean = false private set @@ -40,14 +40,14 @@ class LoginViewModel @Inject constructor( } - fun autoLoginWithIdToken() { + fun automaticallyLogin() { viewModelScope.launch { val idToken = tokenUseCase() if (idToken.isNotEmpty()) { login(idToken, true) } delay(1000) - isAutoLoginFinish = true + isAutoLoginFinished = true } } diff --git a/android/core/ui/src/main/java/com/ohdodok/catchytape/core/ui/bindingadapter/UploadBindingAdapter.kt b/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/BindingAdapter.kt similarity index 83% rename from android/core/ui/src/main/java/com/ohdodok/catchytape/core/ui/bindingadapter/UploadBindingAdapter.kt rename to android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/BindingAdapter.kt index 4ade183..85f6a5e 100644 --- a/android/core/ui/src/main/java/com/ohdodok/catchytape/core/ui/bindingadapter/UploadBindingAdapter.kt +++ b/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/BindingAdapter.kt @@ -1,4 +1,4 @@ -package com.ohdodok.catchytape.core.ui.bindingadapter +package com.ohdodok.catchytape.feature.upload import android.widget.AutoCompleteTextView import androidx.databinding.BindingAdapter From 1c79bde20b80e1a26acf49735438feb2262d2bc7 Mon Sep 17 00:00:00 2001 From: HamBP Date: Mon, 20 Nov 2023 18:09:01 +0900 Subject: [PATCH 017/165] =?UTF-8?q?feat=20:=20=EB=8B=89=EB=84=A4=EC=9E=84?= =?UTF-8?q?=20=EC=9C=A0=ED=9A=A8=EC=84=B1=20=EA=B2=80=EC=82=AC=20=EA=B2=B0?= =?UTF-8?q?=EA=B3=BC=EB=A5=BC=20=EC=A0=80=EC=9E=A5=ED=95=98=EB=8A=94=20?= =?UTF-8?q?=EB=B3=80=EC=88=98=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/login/NicknameViewModel.kt | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/NicknameViewModel.kt b/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/NicknameViewModel.kt index 1aa1734..be85e0d 100644 --- a/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/NicknameViewModel.kt +++ b/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/NicknameViewModel.kt @@ -2,18 +2,25 @@ package com.ohdodok.catchytape.feature.login import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.ohdodok.catchytape.core.domain.signup.NicknameValidationResult +import com.ohdodok.catchytape.core.domain.signup.NicknameValidationUseCase import com.ohdodok.catchytape.core.domain.usecase.SignUpUseCase import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.stateIn import javax.inject.Inject @HiltViewModel class NicknameViewModel @Inject constructor( - private val signUpUseCase: SignUpUseCase + private val signUpUseCase: SignUpUseCase, + private val nicknameValidationUseCase: NicknameValidationUseCase, ) : ViewModel() { private val _events = MutableSharedFlow() @@ -21,8 +28,18 @@ class NicknameViewModel @Inject constructor( val nickname = MutableStateFlow("") + val nicknameValidationState: StateFlow = + nickname.map { nicknameValidationUseCase(it) } + .stateIn( + viewModelScope, + SharingStarted.WhileSubscribed(), + NicknameValidationResult.EMPTY + ) + fun signUp(googleToken: String) { - // TODO : 중복 검사 및 유효성 검사 + + if (nicknameValidationState.value != NicknameValidationResult.VALID) return + signUpUseCase(googleToken = googleToken, nickname = nickname.value) .onEach { _events.emit(NicknameEvent.NavigateToHome) From 6b32da87f23033b5ba63c71974cb077034e82310 Mon Sep 17 00:00:00 2001 From: HamBP Date: Mon, 20 Nov 2023 19:09:43 +0900 Subject: [PATCH 018/165] =?UTF-8?q?feat=20:=20=EC=A4=91=EB=B3=B5=20?= =?UTF-8?q?=EA=B2=80=EC=82=AC=20=EA=B2=B0=EA=B3=BC=EB=A5=BC=20=EC=B6=94?= =?UTF-8?q?=EC=A0=81=ED=95=98=EB=8A=94=20Flow=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ohdodok/catchytape/core/data/api/UserApi.kt | 7 +++++++ .../core/data/model/NicknameResponse.kt | 8 ++++++++ .../core/data/repository/AuthRepositoryImpl.kt | 10 ++++++++++ .../core/domain/repository/AuthRepository.kt | 2 ++ .../domain/signup/IsDuplicatedNicknameUseCase.kt | 13 +++++++++++++ .../feature/login/NicknameViewModel.kt | 16 ++++++++++++++++ 6 files changed, 56 insertions(+) create mode 100644 android/core/data/src/main/java/com/ohdodok/catchytape/core/data/model/NicknameResponse.kt create mode 100644 android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/signup/IsDuplicatedNicknameUseCase.kt diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/api/UserApi.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/api/UserApi.kt index b80452d..7038dd0 100644 --- a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/api/UserApi.kt +++ b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/api/UserApi.kt @@ -2,10 +2,13 @@ package com.ohdodok.catchytape.core.data.api import com.ohdodok.catchytape.core.data.model.LoginRequest import com.ohdodok.catchytape.core.data.model.LoginResponse +import com.ohdodok.catchytape.core.data.model.NicknameResponse import com.ohdodok.catchytape.core.data.model.SignUpRequest import retrofit2.Response import retrofit2.http.Body +import retrofit2.http.GET import retrofit2.http.POST +import retrofit2.http.Path interface UserApi { @@ -19,4 +22,8 @@ interface UserApi { @Body signUpRequest: SignUpRequest ): Response + @GET("users/duplicate/{nickname}") + suspend fun verifyDuplicatedNickname( + @Path("nickname") nickname: String, + ): Response } \ No newline at end of file diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/model/NicknameResponse.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/model/NicknameResponse.kt new file mode 100644 index 0000000..1bbcff4 --- /dev/null +++ b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/model/NicknameResponse.kt @@ -0,0 +1,8 @@ +package com.ohdodok.catchytape.core.data.model + +import kotlinx.serialization.Serializable + +@Serializable +data class NicknameResponse( + val nickname: String +) diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/AuthRepositoryImpl.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/AuthRepositoryImpl.kt index 7d37914..7206729 100644 --- a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/AuthRepositoryImpl.kt +++ b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/AuthRepositoryImpl.kt @@ -12,6 +12,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.map +import java.lang.RuntimeException import javax.inject.Inject class AuthRepositoryImpl @Inject constructor( @@ -60,4 +61,13 @@ class AuthRepositoryImpl @Inject constructor( override suspend fun getIdToken(): String = preferenceDataStore.data.map { preferences -> preferences[idTokenKey] ?: "" }.first() + override fun isDuplicatedNickname(nickname: String): Flow = flow { + val response = userApi.verifyDuplicatedNickname(nickname = nickname) + + when (response.code()) { + in 200..299 -> emit(false) + 409 -> emit(true) + else -> throw RuntimeException("네트워크 에러") // fixme : 예외 처리 로직이 정해지면 수정 + } + } } \ No newline at end of file diff --git a/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/repository/AuthRepository.kt b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/repository/AuthRepository.kt index bfd1986..4189825 100644 --- a/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/repository/AuthRepository.kt +++ b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/repository/AuthRepository.kt @@ -14,4 +14,6 @@ interface AuthRepository { suspend fun getIdToken(): String + fun isDuplicatedNickname(nickname: String): Flow + } \ No newline at end of file diff --git a/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/signup/IsDuplicatedNicknameUseCase.kt b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/signup/IsDuplicatedNicknameUseCase.kt new file mode 100644 index 0000000..a422fb1 --- /dev/null +++ b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/signup/IsDuplicatedNicknameUseCase.kt @@ -0,0 +1,13 @@ +package com.ohdodok.catchytape.core.domain.signup + +import com.ohdodok.catchytape.core.domain.repository.AuthRepository +import kotlinx.coroutines.flow.Flow +import javax.inject.Inject + +class IsDuplicatedNicknameUseCase @Inject constructor( + private val authRepository: AuthRepository, +) { + + operator fun invoke(nickname: String): Flow = + authRepository.isDuplicatedNickname(nickname) +} \ No newline at end of file diff --git a/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/NicknameViewModel.kt b/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/NicknameViewModel.kt index be85e0d..d34636f 100644 --- a/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/NicknameViewModel.kt +++ b/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/NicknameViewModel.kt @@ -2,25 +2,32 @@ package com.ohdodok.catchytape.feature.login import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.ohdodok.catchytape.core.domain.signup.IsDuplicatedNicknameUseCase import com.ohdodok.catchytape.core.domain.signup.NicknameValidationResult import com.ohdodok.catchytape.core.domain.signup.NicknameValidationUseCase import com.ohdodok.catchytape.core.domain.usecase.SignUpUseCase import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.single import kotlinx.coroutines.flow.stateIn import javax.inject.Inject +import kotlin.time.Duration.Companion.milliseconds @HiltViewModel class NicknameViewModel @Inject constructor( private val signUpUseCase: SignUpUseCase, private val nicknameValidationUseCase: NicknameValidationUseCase, + private val isDuplicatedNicknameUseCase: IsDuplicatedNicknameUseCase, ) : ViewModel() { private val _events = MutableSharedFlow() @@ -36,6 +43,15 @@ class NicknameViewModel @Inject constructor( NicknameValidationResult.EMPTY ) + @OptIn(FlowPreview::class) + val isDuplicatedNickname = + nicknameValidationState.filter { it == NicknameValidationResult.VALID } + .debounce(300.milliseconds) + .map { _ -> + isDuplicatedNicknameUseCase(nickname.value).single() + } + .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), false) + fun signUp(googleToken: String) { if (nicknameValidationState.value != NicknameValidationResult.VALID) return From ee44aae7b007b0cd2c3c91a1d702a374c729784d Mon Sep 17 00:00:00 2001 From: youlalala Date: Mon, 20 Nov 2023 21:55:01 +0900 Subject: [PATCH 019/165] =?UTF-8?q?feat=20:=20Firebase=20App=20Distributio?= =?UTF-8?q?n=20=EC=9E=90=EB=8F=99=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/android-cd.yml | 10 +++++++++- android/app/build.gradle.kts | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/android-cd.yml b/.github/workflows/android-cd.yml index ef139b4..fb2b40f 100644 --- a/.github/workflows/android-cd.yml +++ b/.github/workflows/android-cd.yml @@ -49,4 +49,12 @@ jobs: with: generate_release_notes: true files: | - ./android/app/build/outputs/apk/release/*.apk + ./android/app/build/outputs/apk/release/app-release.apk + + - name: Upload artifact to Firebase App Distribution + uses: wzieba/Firebase-Distribution-Github-Action@v1 + with: + appId: ${{secrets.FIREBASE_APP_ID}} + serviceCredentialsFileContent: ${{ secrets.CREDENTIAL_FILE_CONTENT }} + groups: testers + file: ./android/app/build/outputs/apk/release/app-release.apk diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 87a95cd..f44574d 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -21,7 +21,7 @@ android { minSdk = 26 targetSdk = 33 versionCode = 1 - versionName = "1.0.0" + versionName = "0.0.1" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } From 9d0c5afbcc71b8b1fd366517fb1ed194043e316f Mon Sep 17 00:00:00 2001 From: youlalala Date: Mon, 20 Nov 2023 22:21:46 +0900 Subject: [PATCH 020/165] =?UTF-8?q?refactor=20:=20=EA=B2=BD=EB=A1=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/android-cd.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/android-cd.yml b/.github/workflows/android-cd.yml index fb2b40f..8a5f0d4 100644 --- a/.github/workflows/android-cd.yml +++ b/.github/workflows/android-cd.yml @@ -41,7 +41,7 @@ jobs: uses: actions/upload-artifact@v3 with: name: release-artifacts - path: ./android/app/build/outputs/apk/release/ + path: android/app/build/outputs/apk/release/ if-no-files-found: error - name: Create Github Release @@ -49,7 +49,7 @@ jobs: with: generate_release_notes: true files: | - ./android/app/build/outputs/apk/release/app-release.apk + android/app/build/outputs/apk/release/app-release.apk - name: Upload artifact to Firebase App Distribution uses: wzieba/Firebase-Distribution-Github-Action@v1 @@ -57,4 +57,4 @@ jobs: appId: ${{secrets.FIREBASE_APP_ID}} serviceCredentialsFileContent: ${{ secrets.CREDENTIAL_FILE_CONTENT }} groups: testers - file: ./android/app/build/outputs/apk/release/app-release.apk + file: android/app/build/outputs/apk/release/app-release.apk From 5bda11c77b1fddd8f1330fd0afbf9248e8754904 Mon Sep 17 00:00:00 2001 From: Seokyung Lim Date: Mon, 20 Nov 2023 22:25:43 +0900 Subject: [PATCH 021/165] =?UTF-8?q?chore=20:=20=EC=A0=88=EB=8C=80=20?= =?UTF-8?q?=EA=B2=BD=EB=A1=9C=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit src 안의 경로를 base 경로로 설정 --- server/package.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/server/package.json b/server/package.json index 8d0f7dc..876a3e3 100644 --- a/server/package.json +++ b/server/package.json @@ -81,6 +81,9 @@ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", - "testEnvironment": "node" + "testEnvironment": "node", + "moduleNameMapper": { + "^src/(.*)": "/$1" + } } } From 3b555eb7bb342a66884b4c868a1f1e78c2dc1c2b Mon Sep 17 00:00:00 2001 From: Seokyung Lim Date: Mon, 20 Nov 2023 23:16:15 +0900 Subject: [PATCH 022/165] =?UTF-8?q?chore=20:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EC=BD=94=EB=93=9C=20=EC=A0=95=EC=83=81=EC=A0=81=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=8F=8C=EC=95=84=EA=B0=80=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: khw3754 --- server/src/auth/auth.controller.spec.ts | 19 +++++++++++++++++++ server/src/auth/auth.service.spec.ts | 17 ++++++++++++++++- server/src/user/user.controller.spec.ts | 15 +++++++++++++++ server/src/user/user.service.spec.ts | 13 ++++++++++++- 4 files changed, 62 insertions(+), 2 deletions(-) diff --git a/server/src/auth/auth.controller.spec.ts b/server/src/auth/auth.controller.spec.ts index 27a31e6..bbeb643 100644 --- a/server/src/auth/auth.controller.spec.ts +++ b/server/src/auth/auth.controller.spec.ts @@ -1,15 +1,34 @@ import { Test, TestingModule } from '@nestjs/testing'; import { AuthController } from './auth.controller'; +import { AuthService } from './auth.service'; +import { getRepositoryToken } from '@nestjs/typeorm'; +import { User } from 'src/entity/user.entity'; +import { Repository } from 'typeorm'; +import { JwtModule, JwtService } from '@nestjs/jwt'; describe('AuthController', () => { let controller: AuthController; + let service: AuthService; + let jwtModule: JwtModule; + let repository; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ + imports: [JwtModule], controllers: [AuthController], + providers: [ + AuthService, + { + provide: getRepositoryToken(User), + useClass: Repository, + }, + ], }).compile(); controller = module.get(AuthController); + service = module.get(AuthService); + jwtModule = module.get(JwtModule); + repository = module.get(getRepositoryToken(User)); }); it('should be defined', () => { diff --git a/server/src/auth/auth.service.spec.ts b/server/src/auth/auth.service.spec.ts index 800ab66..32496c8 100644 --- a/server/src/auth/auth.service.spec.ts +++ b/server/src/auth/auth.service.spec.ts @@ -1,15 +1,30 @@ import { Test, TestingModule } from '@nestjs/testing'; import { AuthService } from './auth.service'; +import { Repository } from 'typeorm'; +import { getRepositoryToken } from '@nestjs/typeorm'; +import { User } from 'src/entity/user.entity'; +import { JwtModule, JwtService } from '@nestjs/jwt'; describe('AuthService', () => { let service: AuthService; + let jwtModule: JwtModule; + let repository; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ - providers: [AuthService], + imports: [JwtModule], + providers: [ + AuthService, + { + provide: getRepositoryToken(User), + useClass: Repository, + }, + ], }).compile(); service = module.get(AuthService); + jwtModule = module.get(JwtModule); + repository = module.get(getRepositoryToken(User)); }); it('should be defined', () => { diff --git a/server/src/user/user.controller.spec.ts b/server/src/user/user.controller.spec.ts index 7057a1a..4856bff 100644 --- a/server/src/user/user.controller.spec.ts +++ b/server/src/user/user.controller.spec.ts @@ -1,15 +1,30 @@ import { Test, TestingModule } from '@nestjs/testing'; import { UserController } from './user.controller'; +import { UserService } from './user.service'; +import { getRepositoryToken } from '@nestjs/typeorm'; +import { User } from 'src/entity/user.entity'; +import { Repository } from 'typeorm'; describe('UserController', () => { let controller: UserController; + let userService: UserService; + let repository; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [UserController], + providers: [ + UserService, + { + provide: getRepositoryToken(User), + useClass: Repository, + }, + ], }).compile(); controller = module.get(UserController); + userService = module.get(UserService); + repository = module.get(getRepositoryToken(User)); }); it('should be defined', () => { diff --git a/server/src/user/user.service.spec.ts b/server/src/user/user.service.spec.ts index 873de8a..5653523 100644 --- a/server/src/user/user.service.spec.ts +++ b/server/src/user/user.service.spec.ts @@ -1,15 +1,26 @@ import { Test, TestingModule } from '@nestjs/testing'; import { UserService } from './user.service'; +import { getRepositoryToken } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { User } from '../entity/user.entity'; describe('UserService', () => { let service: UserService; + let repository; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ - providers: [UserService], + providers: [ + UserService, + { + provide: getRepositoryToken(User), + useClass: Repository, + }, + ], }).compile(); service = module.get(UserService); + repository = module.get(getRepositoryToken(User)) }); it('should be defined', () => { From 0ce29c91572963f1534729b04b9f6dd612679759 Mon Sep 17 00:00:00 2001 From: algosketch Date: Mon, 20 Nov 2023 23:19:19 +0900 Subject: [PATCH 023/165] =?UTF-8?q?fix=20:=20UseCase=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EC=9C=A0=ED=9A=A8=EC=84=B1=EA=B3=BC=20=EC=A4=91=EB=B3=B5=20?= =?UTF-8?q?=EB=AA=A8=EB=91=90=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- android/core/domain/build.gradle.kts | 12 +++++++---- .../signup/IsDuplicatedNicknameUseCase.kt | 13 ------------ .../signup/NicknameValidationUseCase.kt | 21 +++++++++++++++---- .../signup/NicknameValidationUseCaseTest.kt | 20 +++++++++++++----- .../feature/login/NicknameViewModel.kt | 13 +----------- android/gradle/libs.versions.toml | 2 ++ 6 files changed, 43 insertions(+), 38 deletions(-) delete mode 100644 android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/signup/IsDuplicatedNicknameUseCase.kt diff --git a/android/core/domain/build.gradle.kts b/android/core/domain/build.gradle.kts index d6ebede..2bb3d4c 100644 --- a/android/core/domain/build.gradle.kts +++ b/android/core/domain/build.gradle.kts @@ -8,10 +8,14 @@ tasks.withType().configureEach { } dependencies { - testImplementation(libs.junit) - testImplementation(libs.kotest.runner) - testImplementation (libs.kotest.property) - testImplementation (libs.kotest.extentions.junitxml) api(libs.coroutines) + implementation(libs.inject) + + // fixme : kotest 사용이 확정되면 junit 지우기 + testImplementation(libs.junit) + testImplementation(libs.kotest.runner) + testImplementation(libs.kotest.property) + testImplementation(libs.kotest.extentions.junitxml) + testImplementation(libs.mockk) } diff --git a/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/signup/IsDuplicatedNicknameUseCase.kt b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/signup/IsDuplicatedNicknameUseCase.kt deleted file mode 100644 index a422fb1..0000000 --- a/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/signup/IsDuplicatedNicknameUseCase.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.ohdodok.catchytape.core.domain.signup - -import com.ohdodok.catchytape.core.domain.repository.AuthRepository -import kotlinx.coroutines.flow.Flow -import javax.inject.Inject - -class IsDuplicatedNicknameUseCase @Inject constructor( - private val authRepository: AuthRepository, -) { - - operator fun invoke(nickname: String): Flow = - authRepository.isDuplicatedNickname(nickname) -} \ No newline at end of file diff --git a/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/signup/NicknameValidationUseCase.kt b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/signup/NicknameValidationUseCase.kt index 1072498..b6e00e2 100644 --- a/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/signup/NicknameValidationUseCase.kt +++ b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/signup/NicknameValidationUseCase.kt @@ -1,5 +1,9 @@ package com.ohdodok.catchytape.core.domain.signup +import com.ohdodok.catchytape.core.domain.repository.AuthRepository +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.single import javax.inject.Inject enum class NicknameValidationResult { @@ -7,17 +11,26 @@ enum class NicknameValidationResult { EMPTY, INVALID_LENGTH, INVALID_CHARACTER, + DUPLICATED, } -class NicknameValidationUseCase @Inject constructor() { - operator fun invoke(nickname: String): NicknameValidationResult { +class NicknameValidationUseCase @Inject constructor( + private val authRepository: AuthRepository, +) { + operator fun invoke(nickname: String): Flow = flow { val regex = "(^[ㄱ-ㅎ가-힣\\w_.]{2,10}$)".toRegex() - return when { - regex.matches(nickname) -> NicknameValidationResult.VALID + val result = when { + regex.matches(nickname) -> { + val response = authRepository.isDuplicatedNickname(nickname).single() + if(response) NicknameValidationResult.DUPLICATED + else NicknameValidationResult.VALID + } nickname.isBlank() -> NicknameValidationResult.EMPTY nickname.length !in 2..10 -> NicknameValidationResult.INVALID_LENGTH else -> NicknameValidationResult.INVALID_CHARACTER } + + emit(result) } } \ No newline at end of file diff --git a/android/core/domain/src/test/java/com/ohdodok/catchytape/core/domain/signup/NicknameValidationUseCaseTest.kt b/android/core/domain/src/test/java/com/ohdodok/catchytape/core/domain/signup/NicknameValidationUseCaseTest.kt index 5d834ca..c7afde1 100644 --- a/android/core/domain/src/test/java/com/ohdodok/catchytape/core/domain/signup/NicknameValidationUseCaseTest.kt +++ b/android/core/domain/src/test/java/com/ohdodok/catchytape/core/domain/signup/NicknameValidationUseCaseTest.kt @@ -1,17 +1,27 @@ package com.ohdodok.catchytape.core.domain.signup +import com.ohdodok.catchytape.core.domain.repository.AuthRepository import io.kotest.core.spec.style.BehaviorSpec import io.kotest.matchers.shouldBe +import io.mockk.every +import io.mockk.mockk +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.single class NicknameValidationUseCaseTest : BehaviorSpec() { - private val nicknameValidationUseCase = NicknameValidationUseCase() + private val authRepository: AuthRepository = mockk() + private val nicknameValidationUseCase = NicknameValidationUseCase(authRepository) init { + every { + authRepository.isDuplicatedNickname(any()) + } returns flow { emit(false) } + given("유효한 닉네임이 주어지고") { `when`("유효성을 검사하면") { then("Valid를 반환한다") { listOf("아이유", "iu", "20", "가a1_.", "특수문자_.").forEach { - nicknameValidationUseCase(nickname = it) shouldBe NicknameValidationResult.VALID + nicknameValidationUseCase(nickname = it).single() shouldBe NicknameValidationResult.VALID } } } @@ -20,7 +30,7 @@ class NicknameValidationUseCaseTest : BehaviorSpec() { given("비어 있는 닉네임이 주어지고") { `when`("유효성을 검사하면") { then("Empty를 반환한다") { - nicknameValidationUseCase(nickname = "") shouldBe NicknameValidationResult.EMPTY + nicknameValidationUseCase(nickname = "").single() shouldBe NicknameValidationResult.EMPTY } } } @@ -29,7 +39,7 @@ class NicknameValidationUseCaseTest : BehaviorSpec() { `when`("유효성을 검사하면") { then("Invalid length를 반환한다") { listOf("한", "닉네임을이렇게길게지으면어떡해", "a").forEach { - nicknameValidationUseCase(nickname = it) shouldBe NicknameValidationResult.INVALID_LENGTH + nicknameValidationUseCase(nickname = it).single() shouldBe NicknameValidationResult.INVALID_LENGTH } } } @@ -39,7 +49,7 @@ class NicknameValidationUseCaseTest : BehaviorSpec() { `when`("유효성을 검사하면") { then("Invalid length를 반환한다") { listOf("안 돼", "특수문자^", "특수문자*").forEach { - nicknameValidationUseCase(nickname = it) shouldBe NicknameValidationResult.INVALID_CHARACTER + nicknameValidationUseCase(nickname = it).single() shouldBe NicknameValidationResult.INVALID_CHARACTER } } } diff --git a/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/NicknameViewModel.kt b/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/NicknameViewModel.kt index d34636f..bde42b6 100644 --- a/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/NicknameViewModel.kt +++ b/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/NicknameViewModel.kt @@ -2,7 +2,6 @@ package com.ohdodok.catchytape.feature.login import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.ohdodok.catchytape.core.domain.signup.IsDuplicatedNicknameUseCase import com.ohdodok.catchytape.core.domain.signup.NicknameValidationResult import com.ohdodok.catchytape.core.domain.signup.NicknameValidationUseCase import com.ohdodok.catchytape.core.domain.usecase.SignUpUseCase @@ -27,7 +26,6 @@ import kotlin.time.Duration.Companion.milliseconds class NicknameViewModel @Inject constructor( private val signUpUseCase: SignUpUseCase, private val nicknameValidationUseCase: NicknameValidationUseCase, - private val isDuplicatedNicknameUseCase: IsDuplicatedNicknameUseCase, ) : ViewModel() { private val _events = MutableSharedFlow() @@ -36,22 +34,13 @@ class NicknameViewModel @Inject constructor( val nickname = MutableStateFlow("") val nicknameValidationState: StateFlow = - nickname.map { nicknameValidationUseCase(it) } + nickname.map { nicknameValidationUseCase(it).single() } .stateIn( viewModelScope, SharingStarted.WhileSubscribed(), NicknameValidationResult.EMPTY ) - @OptIn(FlowPreview::class) - val isDuplicatedNickname = - nicknameValidationState.filter { it == NicknameValidationResult.VALID } - .debounce(300.milliseconds) - .map { _ -> - isDuplicatedNicknameUseCase(nickname.value).single() - } - .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), false) - fun signUp(googleToken: String) { if (nicknameValidationState.value != NicknameValidationResult.VALID) return diff --git a/android/gradle/libs.versions.toml b/android/gradle/libs.versions.toml index 32cc54c..f5a2b42 100644 --- a/android/gradle/libs.versions.toml +++ b/android/gradle/libs.versions.toml @@ -15,6 +15,7 @@ gms = "20.7.0" junit = "4.13.2" kotest = "5.8.0" +mockk = "1.13.8" retrofit = "2.9.0" okhttp = "4.11.0" @@ -46,6 +47,7 @@ kotest-runner = { group = "io.kotest", name = "kotest-runner-junit5", version.re kotest-property = { group = "io.kotest", name = "kotest-property", version.ref = "kotest"} kotest-extentions-junitxml = { group = "io.kotest", name = "kotest-extensions-junitxml", version.ref = "kotest"} junit = { group = "junit", name = "junit", version.ref = "junit" } +mockk = { group = "io.mockk", name = "mockk", version.ref = "mockk"} retrofit = { module = "com.squareup.retrofit2:retrofit", name = "retrofit", version.ref = "retrofit" } okhttp-logging-interceptor = { module = "com.squareup.okhttp3:logging-interceptor", name = "okhttp", version.ref = "okhttp" } From 01d011a73230aea7c8821fe92de34b0ec255b8b7 Mon Sep 17 00:00:00 2001 From: algosketch Date: Mon, 20 Nov 2023 23:48:12 +0900 Subject: [PATCH 024/165] =?UTF-8?q?feat=20:=20=EC=9C=A0=ED=9A=A8=EC=84=B1?= =?UTF-8?q?=20=EA=B2=80=EC=82=AC=20=EA=B2=B0=EA=B3=BC=EC=97=90=20=EB=94=B0?= =?UTF-8?q?=EB=9D=BC=20=EB=A9=94=EC=8B=9C=EC=A7=80=20=EC=B6=9C=EB=A0=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/signup/NicknameValidationUseCase.kt | 1 + .../core/ui/src/main/res/values-night/colors.xml | 2 ++ android/core/ui/src/main/res/values/colors.xml | 2 ++ .../catchytape/feature/login/NicknameFragment.kt | 16 ++++++++++++++++ .../src/main/res/layout/fragment_nickname.xml | 13 +++++++++++++ 5 files changed, 34 insertions(+) diff --git a/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/signup/NicknameValidationUseCase.kt b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/signup/NicknameValidationUseCase.kt index b6e00e2..c76bf91 100644 --- a/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/signup/NicknameValidationUseCase.kt +++ b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/signup/NicknameValidationUseCase.kt @@ -1,6 +1,7 @@ package com.ohdodok.catchytape.core.domain.signup import com.ohdodok.catchytape.core.domain.repository.AuthRepository +import kotlinx.coroutines.Job import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.single diff --git a/android/core/ui/src/main/res/values-night/colors.xml b/android/core/ui/src/main/res/values-night/colors.xml index 10f2491..d22039b 100644 --- a/android/core/ui/src/main/res/values-night/colors.xml +++ b/android/core/ui/src/main/res/values-night/colors.xml @@ -8,4 +8,6 @@ #FFFFFFFF #FFF2B8B5 #FF424242 + + #FF48CAE4 \ No newline at end of file diff --git a/android/core/ui/src/main/res/values/colors.xml b/android/core/ui/src/main/res/values/colors.xml index c8cb6d0..d053cd8 100644 --- a/android/core/ui/src/main/res/values/colors.xml +++ b/android/core/ui/src/main/res/values/colors.xml @@ -14,4 +14,6 @@ #FF212121 #FFB00020 #FF424242 + + #FF0096C7 \ No newline at end of file diff --git a/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/NicknameFragment.kt b/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/NicknameFragment.kt index ba5652a..e78a054 100644 --- a/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/NicknameFragment.kt +++ b/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/NicknameFragment.kt @@ -2,8 +2,11 @@ package com.ohdodok.catchytape.feature.login import android.os.Bundle import android.view.View +import android.widget.TextView +import androidx.databinding.BindingAdapter import androidx.fragment.app.viewModels import androidx.navigation.fragment.navArgs +import com.ohdodok.catchytape.core.domain.signup.NicknameValidationResult import com.ohdodok.catchytape.core.ui.BaseFragment import com.ohdodok.catchytape.feature.login.databinding.FragmentNicknameBinding import dagger.hilt.android.AndroidEntryPoint @@ -44,4 +47,17 @@ class NicknameFragment : BaseFragment(R.layout.fragment } } } +} + +@BindingAdapter("nicknameValidationState") +fun TextView.bindNicknameValidationState(state: NicknameValidationResult) { + val stateMessageMap = mapOf( + NicknameValidationResult.EMPTY to "", + NicknameValidationResult.VALID to "사용 가능한 닉네임이에요.", + NicknameValidationResult.DUPLICATED to "이미 사용중인 닉네임이에요.", + NicknameValidationResult.INVALID_LENGTH to "닉네임은 2~10글자까지 가능해요.", + NicknameValidationResult.INVALID_CHARACTER to "한글, 영어, 특수문자(-, _, .)만 입력 가능해요.", + ) + + this.text = stateMessageMap[state] ?: "" } \ No newline at end of file diff --git a/android/feature/login/src/main/res/layout/fragment_nickname.xml b/android/feature/login/src/main/res/layout/fragment_nickname.xml index 9f04ea1..b084a8b 100644 --- a/android/feature/login/src/main/res/layout/fragment_nickname.xml +++ b/android/feature/login/src/main/res/layout/fragment_nickname.xml @@ -47,6 +47,19 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/tv_nickname_title" /> + + Date: Tue, 21 Nov 2023 00:28:28 +0900 Subject: [PATCH 025/165] =?UTF-8?q?feat=20:=20=EC=9C=A0=ED=9A=A8=EC=84=B1?= =?UTF-8?q?=20=EA=B2=80=EC=82=AC=20=ED=97=AC=ED=8D=BC=20=EB=A9=94=EC=8B=9C?= =?UTF-8?q?=EC=A7=80=20=EB=AC=B8=EC=9E=90=EC=97=B4=20=EB=A6=AC=EC=86=8C?= =?UTF-8?q?=EC=8A=A4=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/login/NicknameFragment.kt | 18 +++++++++--------- .../login/src/main/res/values/strings.xml | 5 +++++ 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/NicknameFragment.kt b/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/NicknameFragment.kt index e78a054..8a221af 100644 --- a/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/NicknameFragment.kt +++ b/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/NicknameFragment.kt @@ -51,13 +51,13 @@ class NicknameFragment : BaseFragment(R.layout.fragment @BindingAdapter("nicknameValidationState") fun TextView.bindNicknameValidationState(state: NicknameValidationResult) { - val stateMessageMap = mapOf( - NicknameValidationResult.EMPTY to "", - NicknameValidationResult.VALID to "사용 가능한 닉네임이에요.", - NicknameValidationResult.DUPLICATED to "이미 사용중인 닉네임이에요.", - NicknameValidationResult.INVALID_LENGTH to "닉네임은 2~10글자까지 가능해요.", - NicknameValidationResult.INVALID_CHARACTER to "한글, 영어, 특수문자(-, _, .)만 입력 가능해요.", - ) - - this.text = stateMessageMap[state] ?: "" + val messageId = when(state) { + NicknameValidationResult.EMPTY -> R.string.empty_nickname + NicknameValidationResult.VALID -> R.string.valid_nickname + NicknameValidationResult.DUPLICATED -> R.string.duplicated_nickname + NicknameValidationResult.INVALID_LENGTH -> R.string.invalid_length + NicknameValidationResult.INVALID_CHARACTER -> R.string.invalid_character + } + + this.text = resources.getString(messageId) } \ No newline at end of file diff --git a/android/feature/login/src/main/res/values/strings.xml b/android/feature/login/src/main/res/values/strings.xml index 8ab66be..bb0ce0f 100644 --- a/android/feature/login/src/main/res/values/strings.xml +++ b/android/feature/login/src/main/res/values/strings.xml @@ -6,4 +6,9 @@ 사용하실 닉네임을\n입력해주세요 :) 시작하기 + + 사용 가능한 닉네임이에요. + 이미 사용중인 닉네임이에요. + 한글, 영어, 특수문자(-, _, .)만 입력 가능해요. + 닉네임은 2~10글자까지 가능해요. \ No newline at end of file From fa1526924fa33197ecd66bf73111ae586194011d Mon Sep 17 00:00:00 2001 From: Yura Park <62279741+youlalala@users.noreply.github.com> Date: Tue, 21 Nov 2023 00:38:18 +0900 Subject: [PATCH 026/165] =?UTF-8?q?=08docs=20:=20README=20=EC=97=85?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 93b64d6..628df62 100644 --- a/README.md +++ b/README.md @@ -25,12 +25,14 @@ ### 🤖 Android | Category | TechStack | 기록 | | ------------- | ------------- | ------------- | -| Architecture | Clean Architecture, Multi Module, MVVM | [프로젝트 구조](https://tral-lalala.tistory.com/126) +| Architecture | Clean Architecture, MultiModule, MVVM | [프로젝트 구조](https://tral-lalala.tistory.com/126) | DI | Hilt | | Network | Retrofit, Kotlin Serialization | [역/직렬화 라이브러리 비교](https://github.com/boostcampwm2023/and04-catchy-tape/wiki/%EC%97%AD-%EC%A7%81%EB%A0%AC%ED%99%94-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-%EB%B9%84%EA%B5%90) -| Jetpack | Navigation | -| CI/CD | Github Actions | [PR에 대한 단위 테스트 자동화](https://algosketch.tistory.com/178) / [APK Release 자동화](https://tral-lalala.tistory.com/127) -
+| Asynchronous | Coroutines, Flow +| Jetpack | DataBinding, Navigation | +| CI/CD | Github Actions |[PR에 대한 단위 테스트 자동화](https://algosketch.tistory.com/178)⎮[Github Release 자동화](https://tral-lalala.tistory.com/127)⎮[Firebase App 배포 자동화](https://tral-lalala.tistory.com/128) +| Test | Kotest +
그 외 기록 - [프로젝트 생성](https://github.com/boostcampwm2023/and04-catchy-tape/wiki/Android#%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%83%9D%EC%84%B1%EC%8B%9C-%EA%B3%A0%EB%A0%A4%ED%95%9C-%EB%82%B4%EC%9A%A9) From 56b77112f4ddc401a876a4c367430419a62005d5 Mon Sep 17 00:00:00 2001 From: Yura Park <62279741+youlalala@users.noreply.github.com> Date: Tue, 21 Nov 2023 00:39:09 +0900 Subject: [PATCH 027/165] =?UTF-8?q?docs=20:=20README=20=EC=97=85=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 628df62..b2345e1 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ ### 🤖 Android | Category | TechStack | 기록 | | ------------- | ------------- | ------------- | -| Architecture | Clean Architecture, MultiModule, MVVM | [프로젝트 구조](https://tral-lalala.tistory.com/126) +| Architecture | Clean Architecture, Multi Module, MVVM | [프로젝트 구조](https://tral-lalala.tistory.com/126) | DI | Hilt | | Network | Retrofit, Kotlin Serialization | [역/직렬화 라이브러리 비교](https://github.com/boostcampwm2023/and04-catchy-tape/wiki/%EC%97%AD-%EC%A7%81%EB%A0%AC%ED%99%94-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-%EB%B9%84%EA%B5%90) | Asynchronous | Coroutines, Flow From f39ff65b4909aad0cbf5cd132255c57e6c2c1079 Mon Sep 17 00:00:00 2001 From: Seokyung Lim Date: Tue, 21 Nov 2023 00:46:48 +0900 Subject: [PATCH 028/165] =?UTF-8?q?chore=20:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EA=B2=B0=EA=B3=BC=20=EC=BD=94=EB=A9=98=ED=8A=B8=20=EB=8B=A4?= =?UTF-8?q?=EB=8A=94=20=EB=B6=80=EB=B6=84=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 이 부분에 적힌 file이 다른 언어의 환경에서는 build하면 테스트 결과가 포함되는 파일이라고 하는데 서버 환경에는 없고, 굳이 결과를 표기할 필요성은 느끼지 못해 일단 제거함. --- .github/workflows/server-dev-ci.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.github/workflows/server-dev-ci.yml b/.github/workflows/server-dev-ci.yml index ea241a1..2a02fb5 100644 --- a/.github/workflows/server-dev-ci.yml +++ b/.github/workflows/server-dev-ci.yml @@ -36,9 +36,3 @@ jobs: - name: Execute Test run: npm run test - - - name: Record Result in Pull Request - uses: EnricoMi/publish-unit-test-result-action@v1 - if: always() - with: - files: "**/build/test-results/test/TEST-*.xml" From bf2c2e57ca85c4bba31f0bc5054707293c6a0476 Mon Sep 17 00:00:00 2001 From: algosketch Date: Tue, 21 Nov 2023 01:29:09 +0900 Subject: [PATCH 029/165] =?UTF-8?q?feat=20:=20=EC=A4=91=EB=B3=B5=20?= =?UTF-8?q?=EA=B2=80=EC=82=AC=20=EB=A1=9C=EC=A7=81=EC=97=90=20=EB=8C=80?= =?UTF-8?q?=ED=95=B4=EC=84=9C=EB=A7=8C=20debounce=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../signup/NicknameValidationUseCase.kt | 37 ---------------- .../domain/signup/ValidateNicknameUseCase.kt | 44 +++++++++++++++++++ .../signup/NicknameValidationUseCaseTest.kt | 35 ++++++++++----- .../feature/login/NicknameViewModel.kt | 12 ++--- 4 files changed, 70 insertions(+), 58 deletions(-) delete mode 100644 android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/signup/NicknameValidationUseCase.kt create mode 100644 android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/signup/ValidateNicknameUseCase.kt diff --git a/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/signup/NicknameValidationUseCase.kt b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/signup/NicknameValidationUseCase.kt deleted file mode 100644 index c76bf91..0000000 --- a/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/signup/NicknameValidationUseCase.kt +++ /dev/null @@ -1,37 +0,0 @@ -package com.ohdodok.catchytape.core.domain.signup - -import com.ohdodok.catchytape.core.domain.repository.AuthRepository -import kotlinx.coroutines.Job -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.flow.single -import javax.inject.Inject - -enum class NicknameValidationResult { - VALID, - EMPTY, - INVALID_LENGTH, - INVALID_CHARACTER, - DUPLICATED, -} - -class NicknameValidationUseCase @Inject constructor( - private val authRepository: AuthRepository, -) { - operator fun invoke(nickname: String): Flow = flow { - val regex = "(^[ㄱ-ㅎ가-힣\\w_.]{2,10}$)".toRegex() - - val result = when { - regex.matches(nickname) -> { - val response = authRepository.isDuplicatedNickname(nickname).single() - if(response) NicknameValidationResult.DUPLICATED - else NicknameValidationResult.VALID - } - nickname.isBlank() -> NicknameValidationResult.EMPTY - nickname.length !in 2..10 -> NicknameValidationResult.INVALID_LENGTH - else -> NicknameValidationResult.INVALID_CHARACTER - } - - emit(result) - } -} \ No newline at end of file diff --git a/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/signup/ValidateNicknameUseCase.kt b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/signup/ValidateNicknameUseCase.kt new file mode 100644 index 0000000..21b246d --- /dev/null +++ b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/signup/ValidateNicknameUseCase.kt @@ -0,0 +1,44 @@ +package com.ohdodok.catchytape.core.domain.signup + +import com.ohdodok.catchytape.core.domain.repository.AuthRepository +import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.single +import javax.inject.Inject + +enum class NicknameValidationResult { + VALID, + EMPTY, + INVALID_LENGTH, + INVALID_CHARACTER, + DUPLICATED, +} + +class ValidateNicknameUseCase @Inject constructor( + private val authRepository: AuthRepository, +) { + @OptIn(FlowPreview::class) + operator fun invoke(nicknameStream: Flow): Flow = + nicknameStream.map { nickname -> + val regex = "(^[ㄱ-ㅎ가-힣\\w_.]{2,10}$)".toRegex() + + val result = when { + regex.matches(nickname) -> { + val validNickname = nicknameStream.debounce(300).first() + val response = authRepository.isDuplicatedNickname(validNickname).single() + + if (response) NicknameValidationResult.DUPLICATED + else NicknameValidationResult.VALID + } + + nickname.isBlank() -> NicknameValidationResult.EMPTY + nickname.length !in 2..10 -> NicknameValidationResult.INVALID_LENGTH + else -> NicknameValidationResult.INVALID_CHARACTER + } + + result + } +} \ No newline at end of file diff --git a/android/core/domain/src/test/java/com/ohdodok/catchytape/core/domain/signup/NicknameValidationUseCaseTest.kt b/android/core/domain/src/test/java/com/ohdodok/catchytape/core/domain/signup/NicknameValidationUseCaseTest.kt index c7afde1..c7c2a02 100644 --- a/android/core/domain/src/test/java/com/ohdodok/catchytape/core/domain/signup/NicknameValidationUseCaseTest.kt +++ b/android/core/domain/src/test/java/com/ohdodok/catchytape/core/domain/signup/NicknameValidationUseCaseTest.kt @@ -5,12 +5,16 @@ import io.kotest.core.spec.style.BehaviorSpec import io.kotest.matchers.shouldBe import io.mockk.every import io.mockk.mockk +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.flow.single +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach class NicknameValidationUseCaseTest : BehaviorSpec() { private val authRepository: AuthRepository = mockk() - private val nicknameValidationUseCase = NicknameValidationUseCase(authRepository) + private val validateNicknameUseCase = ValidateNicknameUseCase(authRepository) init { every { @@ -20,9 +24,12 @@ class NicknameValidationUseCaseTest : BehaviorSpec() { given("유효한 닉네임이 주어지고") { `when`("유효성을 검사하면") { then("Valid를 반환한다") { - listOf("아이유", "iu", "20", "가a1_.", "특수문자_.").forEach { - nicknameValidationUseCase(nickname = it).single() shouldBe NicknameValidationResult.VALID - } + // fixme : 화이트 박스 테스트임 (중복 검사에 대해서만 300ms를 기다리는데 이거 때문에 블랙 박스 테스트가 어려움) + // 현재 VALID 여부를 정확하게 테스트 하고 있기는 하다. (거짓 음성을 뱉지 않음) + val nicknameFlow = flowOf("아이유", "iu", "20", "가a1_.", "특수문자_.") + validateNicknameUseCase(nicknameFlow).onEach { + it shouldBe NicknameValidationResult.VALID + }.launchIn(this) } } } @@ -30,7 +37,9 @@ class NicknameValidationUseCaseTest : BehaviorSpec() { given("비어 있는 닉네임이 주어지고") { `when`("유효성을 검사하면") { then("Empty를 반환한다") { - nicknameValidationUseCase(nickname = "").single() shouldBe NicknameValidationResult.EMPTY + validateNicknameUseCase(flowOf("")).onEach { + it shouldBe NicknameValidationResult.EMPTY + }.launchIn(this) } } } @@ -38,9 +47,10 @@ class NicknameValidationUseCaseTest : BehaviorSpec() { given("짧거나 긴 닉네임이 주어지고") { `when`("유효성을 검사하면") { then("Invalid length를 반환한다") { - listOf("한", "닉네임을이렇게길게지으면어떡해", "a").forEach { - nicknameValidationUseCase(nickname = it).single() shouldBe NicknameValidationResult.INVALID_LENGTH - } + val nicknameFlow = flowOf("한", "닉네임을이렇게길게지으면어떡해", "a") + validateNicknameUseCase(nicknameFlow).onEach { + it shouldBe NicknameValidationResult.INVALID_LENGTH + }.launchIn(this) } } } @@ -48,9 +58,10 @@ class NicknameValidationUseCaseTest : BehaviorSpec() { given("사용할 수 없는 문자가 포함된 닉네임이 주어지고") { `when`("유효성을 검사하면") { then("Invalid length를 반환한다") { - listOf("안 돼", "특수문자^", "특수문자*").forEach { - nicknameValidationUseCase(nickname = it).single() shouldBe NicknameValidationResult.INVALID_CHARACTER - } + val nicknameFlow = flowOf("안 돼", "특수문자^", "특수문자*") + validateNicknameUseCase(nicknameFlow).onEach { + it shouldBe NicknameValidationResult.INVALID_CHARACTER + }.launchIn(this) } } } diff --git a/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/NicknameViewModel.kt b/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/NicknameViewModel.kt index bde42b6..576c4bb 100644 --- a/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/NicknameViewModel.kt +++ b/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/NicknameViewModel.kt @@ -3,29 +3,23 @@ package com.ohdodok.catchytape.feature.login import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.ohdodok.catchytape.core.domain.signup.NicknameValidationResult -import com.ohdodok.catchytape.core.domain.signup.NicknameValidationUseCase +import com.ohdodok.catchytape.core.domain.signup.ValidateNicknameUseCase import com.ohdodok.catchytape.core.domain.usecase.SignUpUseCase import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asSharedFlow -import kotlinx.coroutines.flow.debounce -import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.flow.single import kotlinx.coroutines.flow.stateIn import javax.inject.Inject -import kotlin.time.Duration.Companion.milliseconds @HiltViewModel class NicknameViewModel @Inject constructor( private val signUpUseCase: SignUpUseCase, - private val nicknameValidationUseCase: NicknameValidationUseCase, + private val validateNicknameUseCase: ValidateNicknameUseCase, ) : ViewModel() { private val _events = MutableSharedFlow() @@ -34,7 +28,7 @@ class NicknameViewModel @Inject constructor( val nickname = MutableStateFlow("") val nicknameValidationState: StateFlow = - nickname.map { nicknameValidationUseCase(it).single() } + validateNicknameUseCase(nickname) .stateIn( viewModelScope, SharingStarted.WhileSubscribed(), From 24cf34692709a2d58015508f249e0ebfba0daa3c Mon Sep 17 00:00:00 2001 From: algosketch Date: Tue, 21 Nov 2023 01:39:21 +0900 Subject: [PATCH 030/165] =?UTF-8?q?feat=20:=20=EC=B5=9C=EB=8C=80=20?= =?UTF-8?q?=EA=B8=B8=EC=9D=B4=20=EC=A0=9C=ED=95=9C=2010=EC=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- android/feature/login/src/main/res/layout/fragment_nickname.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/android/feature/login/src/main/res/layout/fragment_nickname.xml b/android/feature/login/src/main/res/layout/fragment_nickname.xml index b084a8b..44b467d 100644 --- a/android/feature/login/src/main/res/layout/fragment_nickname.xml +++ b/android/feature/login/src/main/res/layout/fragment_nickname.xml @@ -43,6 +43,7 @@ android:layout_marginTop="@dimen/medium" android:hint="@string/nickname" android:text="@={viewModel.nickname}" + android:maxLength="10" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/tv_nickname_title" /> From 6850487ea55cb9441fb399280151859e51702596 Mon Sep 17 00:00:00 2001 From: algosketch Date: Tue, 21 Nov 2023 02:51:35 +0900 Subject: [PATCH 031/165] =?UTF-8?q?feat=20:=20kotest=20CI=20=ED=99=98?= =?UTF-8?q?=EA=B2=BD=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- android/build.gradle.kts | 8 -------- android/core/domain/build.gradle.kts | 8 ++++++++ .../ohdodok/catchytape/core/domain/signup/TestConfig.kt | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/android/build.gradle.kts b/android/build.gradle.kts index 7403d2c..de8e056 100644 --- a/android/build.gradle.kts +++ b/android/build.gradle.kts @@ -10,12 +10,4 @@ plugins { alias(libs.plugins.navigation.safe.args) apply false } -tasks.register("test") { - useJUnitPlatform() - reports { - junitXml.required.set(false) - } - systemProperty("gradle.build.dir", project.buildDir) -} - true // Needed to make the Suppress annotation work for the plugins block \ No newline at end of file diff --git a/android/core/domain/build.gradle.kts b/android/core/domain/build.gradle.kts index d6ebede..23a0478 100644 --- a/android/core/domain/build.gradle.kts +++ b/android/core/domain/build.gradle.kts @@ -7,6 +7,14 @@ tasks.withType().configureEach { useJUnitPlatform() } +tasks.getByName("test") { + useJUnitPlatform() + reports { + junitXml.required.set(false) + } + systemProperty("gradle.build.dir", project.buildDir) +} + dependencies { testImplementation(libs.junit) testImplementation(libs.kotest.runner) diff --git a/android/core/domain/src/test/java/com/ohdodok/catchytape/core/domain/signup/TestConfig.kt b/android/core/domain/src/test/java/com/ohdodok/catchytape/core/domain/signup/TestConfig.kt index 2a00dbe..3e6a2e8 100644 --- a/android/core/domain/src/test/java/com/ohdodok/catchytape/core/domain/signup/TestConfig.kt +++ b/android/core/domain/src/test/java/com/ohdodok/catchytape/core/domain/signup/TestConfig.kt @@ -10,7 +10,7 @@ class TestConfig : AbstractProjectConfig() { JunitXmlReporter( includeContainers = false, // don't write out status for all tests useTestPathAsName = true, // use the full test path (ie, includes parent test names) - outputDir = "test-results/excludeContainers" + outputDir = "../build/test-results" ) ) } \ No newline at end of file From 836a107e4f7e8f82decbe980a84ab63ebefa187b Mon Sep 17 00:00:00 2001 From: algosketch Date: Tue, 21 Nov 2023 03:21:13 +0900 Subject: [PATCH 032/165] =?UTF-8?q?chore=20:=20ci=20=ED=8C=8C=EC=9D=BC,=20?= =?UTF-8?q?test=20task=EB=A5=BC=20=EC=8B=A4=ED=96=89=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/android-pull-request-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/android-pull-request-ci.yml b/.github/workflows/android-pull-request-ci.yml index 90de42e..c3ebd11 100644 --- a/.github/workflows/android-pull-request-ci.yml +++ b/.github/workflows/android-pull-request-ci.yml @@ -39,7 +39,7 @@ jobs: echo "$DEBUG_KEYSTORE" | base64 -d > debug.keystore echo "$KEYSTORE_PROPERTIES" > keystore.properties echo "$LOCAL_PROPERTIES" > local.properties - ./gradlew testDebugUnitTest --stacktrace + ./gradlew test --stacktrace - name: Publish Test Results if: always() From 16ea89f4ca63ebf38c28fe91f58a9d6636eb4c9c Mon Sep 17 00:00:00 2001 From: algosketch Date: Tue, 21 Nov 2023 11:04:03 +0900 Subject: [PATCH 033/165] =?UTF-8?q?feat=20:=20player=20=EB=AA=A8=EB=93=88?= =?UTF-8?q?=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- android/app/build.gradle.kts | 1 + android/feature/player/.gitignore | 1 + android/feature/player/build.gradle.kts | 30 +++++++++++++++++++ android/feature/player/consumer-rules.pro | 0 android/feature/player/proguard-rules.pro | 21 +++++++++++++ .../player/src/main/AndroidManifest.xml | 4 +++ android/settings.gradle.kts | 1 + 7 files changed, 58 insertions(+) create mode 100644 android/feature/player/.gitignore create mode 100644 android/feature/player/build.gradle.kts create mode 100644 android/feature/player/consumer-rules.pro create mode 100644 android/feature/player/proguard-rules.pro create mode 100644 android/feature/player/src/main/AndroidManifest.xml diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 87a95cd..676a593 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -63,6 +63,7 @@ dependencies { implementation(project(":feature:home")) implementation(project(":feature:login")) implementation(project(":feature:upload")) + implementation(project(":feature:player")) implementation(project(":core:data")) implementation(libs.core.ktx) diff --git a/android/feature/player/.gitignore b/android/feature/player/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/android/feature/player/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/android/feature/player/build.gradle.kts b/android/feature/player/build.gradle.kts new file mode 100644 index 0000000..cead45b --- /dev/null +++ b/android/feature/player/build.gradle.kts @@ -0,0 +1,30 @@ +@Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed +plugins { + id("catchytape.android.feature") +} + +android { + namespace = "com.ohdodok.catchytape.feature.player" + + defaultConfig { + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + kotlinOptions { + jvmTarget = "17" + } +} + +dependencies { + +} \ No newline at end of file diff --git a/android/feature/player/consumer-rules.pro b/android/feature/player/consumer-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/android/feature/player/proguard-rules.pro b/android/feature/player/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/android/feature/player/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/android/feature/player/src/main/AndroidManifest.xml b/android/feature/player/src/main/AndroidManifest.xml new file mode 100644 index 0000000..a5918e6 --- /dev/null +++ b/android/feature/player/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts index 6a5389d..44d82a7 100644 --- a/android/settings.gradle.kts +++ b/android/settings.gradle.kts @@ -22,3 +22,4 @@ include(":core:ui") include(":core:domain") include(":core:data") include(":feature:upload") +include(":feature:player") From 7cac240f0adbbf38263e7b48b26cac31f962c022 Mon Sep 17 00:00:00 2001 From: algosketch Date: Tue, 21 Nov 2023 14:28:19 +0900 Subject: [PATCH 034/165] =?UTF-8?q?feat=20:=20=EB=B2=84=ED=8A=BC=20?= =?UTF-8?q?=EB=B9=84=ED=99=9C=EC=84=B1=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/res/drawable/btn_background.xml | 23 +++++++++++++++---- .../src/main/res/layout/fragment_nickname.xml | 3 +++ 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/android/core/ui/src/main/res/drawable/btn_background.xml b/android/core/ui/src/main/res/drawable/btn_background.xml index 50da2f6..a33a838 100644 --- a/android/core/ui/src/main/res/drawable/btn_background.xml +++ b/android/core/ui/src/main/res/drawable/btn_background.xml @@ -1,6 +1,19 @@ - - - - \ No newline at end of file + + + + + + + + + + + + + + + + diff --git a/android/feature/login/src/main/res/layout/fragment_nickname.xml b/android/feature/login/src/main/res/layout/fragment_nickname.xml index 44b467d..77a9588 100644 --- a/android/feature/login/src/main/res/layout/fragment_nickname.xml +++ b/android/feature/login/src/main/res/layout/fragment_nickname.xml @@ -5,6 +5,8 @@ + + @@ -66,6 +68,7 @@ style="@style/Button" android:layout_width="0dp" android:layout_marginBottom="@dimen/extra_large" + android:enabled="@{viewModel.nicknameValidationState == NicknameValidationResult.VALID}" android:text="@string/start" android:textColor="@color/white" app:layout_constraintBottom_toBottomOf="parent" From 48ad7a9ad75ceaf0702da6148c47330dc928fa96 Mon Sep 17 00:00:00 2001 From: 2taezeat Date: Tue, 21 Nov 2023 14:32:13 +0900 Subject: [PATCH 035/165] =?UTF-8?q?chore=20:=20TestConfig=20->=20KoTestCon?= =?UTF-8?q?fig=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?=EB=B0=8F=20=EC=83=81=EC=9C=84=20=ED=8C=A8=ED=82=A4=EC=A7=80?= =?UTF-8?q?=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/domain/{signup/TestConfig.kt => KoTestConfig.kt} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename android/core/domain/src/test/java/com/ohdodok/catchytape/core/domain/{signup/TestConfig.kt => KoTestConfig.kt} (83%) diff --git a/android/core/domain/src/test/java/com/ohdodok/catchytape/core/domain/signup/TestConfig.kt b/android/core/domain/src/test/java/com/ohdodok/catchytape/core/domain/KoTestConfig.kt similarity index 83% rename from android/core/domain/src/test/java/com/ohdodok/catchytape/core/domain/signup/TestConfig.kt rename to android/core/domain/src/test/java/com/ohdodok/catchytape/core/domain/KoTestConfig.kt index 3e6a2e8..86d39d8 100644 --- a/android/core/domain/src/test/java/com/ohdodok/catchytape/core/domain/signup/TestConfig.kt +++ b/android/core/domain/src/test/java/com/ohdodok/catchytape/core/domain/KoTestConfig.kt @@ -1,10 +1,10 @@ -package com.ohdodok.catchytape.core.domain.signup +package com.ohdodok.catchytape.core.domain import io.kotest.core.config.AbstractProjectConfig import io.kotest.core.extensions.Extension import io.kotest.extensions.junitxml.JunitXmlReporter -class TestConfig : AbstractProjectConfig() { +class KoTestConfig : AbstractProjectConfig() { override fun extensions(): List = listOf( JunitXmlReporter( From eda283af66ed21d230872038bef0cb418ec8e91d Mon Sep 17 00:00:00 2001 From: algosketch Date: Tue, 21 Nov 2023 14:35:23 +0900 Subject: [PATCH 036/165] =?UTF-8?q?feat=20:=20flow=EC=97=90=EC=84=9C=20old?= =?UTF-8?q?=20value=EB=8A=94=20drop=ED=95=98=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/domain/signup/ValidateNicknameUseCase.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/signup/ValidateNicknameUseCase.kt b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/signup/ValidateNicknameUseCase.kt index 21b246d..e50a25a 100644 --- a/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/signup/ValidateNicknameUseCase.kt +++ b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/signup/ValidateNicknameUseCase.kt @@ -1,11 +1,13 @@ package com.ohdodok.catchytape.core.domain.signup import com.ohdodok.catchytape.core.domain.repository.AuthRepository +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.single import javax.inject.Inject @@ -20,9 +22,9 @@ enum class NicknameValidationResult { class ValidateNicknameUseCase @Inject constructor( private val authRepository: AuthRepository, ) { - @OptIn(FlowPreview::class) + @OptIn(FlowPreview::class, ExperimentalCoroutinesApi::class) operator fun invoke(nicknameStream: Flow): Flow = - nicknameStream.map { nickname -> + nicknameStream.mapLatest { nickname -> val regex = "(^[ㄱ-ㅎ가-힣\\w_.]{2,10}$)".toRegex() val result = when { From f33a2c63e39ffc0dac2c7b17828b2afe906b0681 Mon Sep 17 00:00:00 2001 From: algosketch Date: Tue, 21 Nov 2023 14:40:31 +0900 Subject: [PATCH 037/165] =?UTF-8?q?chore=20:=20coroutines-android=20->=20c?= =?UTF-8?q?oroutines-core=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- android/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/gradle/libs.versions.toml b/android/gradle/libs.versions.toml index f5a2b42..d09892e 100644 --- a/android/gradle/libs.versions.toml +++ b/android/gradle/libs.versions.toml @@ -53,7 +53,7 @@ retrofit = { module = "com.squareup.retrofit2:retrofit", name = "retrofit", vers okhttp-logging-interceptor = { module = "com.squareup.okhttp3:logging-interceptor", name = "okhttp", version.ref = "okhttp" } kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" } kotlinx-serialization-converter = { module = "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter", version.ref = "kotlinx-serialization-converter" } -coroutines = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "coroutines" } +coroutines = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "coroutines" } timber = { module = "com.jakewharton.timber:timber", version.ref = "timber" } From c08305628c3c8979bb8f528ad64875a938cecd34 Mon Sep 17 00:00:00 2001 From: algosketch Date: Tue, 21 Nov 2023 15:06:43 +0900 Subject: [PATCH 038/165] =?UTF-8?q?feat=20:=20=ED=94=8C=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EC=96=B4=20Fragment,=20ViewModel=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../catchytape/feature/player/PlayerFragment.kt | 16 ++++++++++++++++ .../catchytape/feature/player/PlayerViewModel.kt | 12 ++++++++++++ .../src/main/res/layout/fragment_player.xml | 16 ++++++++++++++++ 3 files changed, 44 insertions(+) create mode 100644 android/feature/player/src/main/java/com/ohdodok/catchytape/feature/player/PlayerFragment.kt create mode 100644 android/feature/player/src/main/java/com/ohdodok/catchytape/feature/player/PlayerViewModel.kt create mode 100644 android/feature/player/src/main/res/layout/fragment_player.xml diff --git a/android/feature/player/src/main/java/com/ohdodok/catchytape/feature/player/PlayerFragment.kt b/android/feature/player/src/main/java/com/ohdodok/catchytape/feature/player/PlayerFragment.kt new file mode 100644 index 0000000..24326ca --- /dev/null +++ b/android/feature/player/src/main/java/com/ohdodok/catchytape/feature/player/PlayerFragment.kt @@ -0,0 +1,16 @@ +package com.ohdodok.catchytape.feature.player + +import android.os.Bundle +import android.view.View +import androidx.fragment.app.viewModels +import com.ohdodok.catchytape.core.ui.BaseFragment +import com.ohdodok.catchytape.feature.player.databinding.FragmentPlayerBinding + +class PlayerFragment : BaseFragment(R.layout.fragment_player) { + private val viewModel: PlayerViewModel by viewModels() + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding.viewModel = viewModel + } +} \ No newline at end of file diff --git a/android/feature/player/src/main/java/com/ohdodok/catchytape/feature/player/PlayerViewModel.kt b/android/feature/player/src/main/java/com/ohdodok/catchytape/feature/player/PlayerViewModel.kt new file mode 100644 index 0000000..f251eb8 --- /dev/null +++ b/android/feature/player/src/main/java/com/ohdodok/catchytape/feature/player/PlayerViewModel.kt @@ -0,0 +1,12 @@ +package com.ohdodok.catchytape.feature.player + +import androidx.lifecycle.ViewModel +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject + +@HiltViewModel +class PlayerViewModel @Inject constructor( + +) : ViewModel() { + +} \ No newline at end of file diff --git a/android/feature/player/src/main/res/layout/fragment_player.xml b/android/feature/player/src/main/res/layout/fragment_player.xml new file mode 100644 index 0000000..0e0a637 --- /dev/null +++ b/android/feature/player/src/main/res/layout/fragment_player.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + \ No newline at end of file From 6662a09d16b8ce09198509adb8c5ad07481403d9 Mon Sep 17 00:00:00 2001 From: youlalala Date: Tue, 21 Nov 2023 15:08:41 +0900 Subject: [PATCH 039/165] =?UTF-8?q?feat=20:=20music=20api=20=EC=A0=95?= =?UTF-8?q?=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/ohdodok/catchytape/core/data/api/MusicApi.kt | 12 ++++++++++++ .../com/ohdodok/catchytape/core/data/di/ApiModule.kt | 7 +++++++ .../core/data/model/MusicGenresResponse.kt | 8 ++++++++ .../core/domain/repository/MusicRepository.kt | 6 ++++++ .../catchytape/feature/upload/UploadFragment.kt | 2 +- .../upload/src/main/res/layout/fragment_upload.xml | 4 ++-- 6 files changed, 36 insertions(+), 3 deletions(-) create mode 100644 android/core/data/src/main/java/com/ohdodok/catchytape/core/data/api/MusicApi.kt create mode 100644 android/core/data/src/main/java/com/ohdodok/catchytape/core/data/model/MusicGenresResponse.kt create mode 100644 android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/repository/MusicRepository.kt diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/api/MusicApi.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/api/MusicApi.kt new file mode 100644 index 0000000..6903920 --- /dev/null +++ b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/api/MusicApi.kt @@ -0,0 +1,12 @@ +package com.ohdodok.catchytape.core.data.api + +import com.ohdodok.catchytape.core.data.model.MusicGenresResponse +import retrofit2.Response +import retrofit2.http.GET + +interface MusicApi { + + @GET("musics/genres") + suspend fun getGenres() : Response + +} \ No newline at end of file diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/di/ApiModule.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/di/ApiModule.kt index 9d94f86..9d2c873 100644 --- a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/di/ApiModule.kt +++ b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/di/ApiModule.kt @@ -1,5 +1,6 @@ package com.ohdodok.catchytape.core.data.di +import com.ohdodok.catchytape.core.data.api.MusicApi import com.ohdodok.catchytape.core.data.api.UserApi import dagger.Module import dagger.Provides @@ -17,4 +18,10 @@ object ApiModule { fun provideSignupApi(retrofit: Retrofit): UserApi { return retrofit.create(UserApi::class.java) } + + @Provides + @Singleton + fun provideMusicApi(retrofit: Retrofit): MusicApi { + return retrofit.create(MusicApi::class.java) + } } \ No newline at end of file diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/model/MusicGenresResponse.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/model/MusicGenresResponse.kt new file mode 100644 index 0000000..2d57e52 --- /dev/null +++ b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/model/MusicGenresResponse.kt @@ -0,0 +1,8 @@ +package com.ohdodok.catchytape.core.data.model + +import kotlinx.serialization.Serializable + +@Serializable +data class MusicGenresResponse( + val genres: List +) \ No newline at end of file diff --git a/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/repository/MusicRepository.kt b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/repository/MusicRepository.kt new file mode 100644 index 0000000..02cf2ed --- /dev/null +++ b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/repository/MusicRepository.kt @@ -0,0 +1,6 @@ +package com.ohdodok.catchytape.core.domain.repository + +interface MusicRepository { + + fun getGenres(): List +} \ No newline at end of file diff --git a/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadFragment.kt b/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadFragment.kt index 75ae687..2af7630 100644 --- a/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadFragment.kt +++ b/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadFragment.kt @@ -33,7 +33,7 @@ class UploadFragment : BaseFragment(R.layout.fragment_upl super.onViewCreated(view, savedInstanceState) binding.viewModel = viewModel - setupBackStack(binding.tbUploadAppbar) + setupBackStack(binding.tbUpload) setupSelectThumbnailImage() } diff --git a/android/feature/upload/src/main/res/layout/fragment_upload.xml b/android/feature/upload/src/main/res/layout/fragment_upload.xml index 93b4ae6..71e939e 100644 --- a/android/feature/upload/src/main/res/layout/fragment_upload.xml +++ b/android/feature/upload/src/main/res/layout/fragment_upload.xml @@ -15,7 +15,7 @@ android:background="@color/surface"> Date: Tue, 21 Nov 2023 15:15:33 +0900 Subject: [PATCH 040/165] =?UTF-8?q?feat=20:=20Dockerfile=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 배포를 위한 dockerfile, dockerignore 작성 --- server/.dockerignore | 4 ++++ server/Dockerfile | 11 +++++++++++ 2 files changed, 15 insertions(+) create mode 100644 server/.dockerignore create mode 100644 server/Dockerfile diff --git a/server/.dockerignore b/server/.dockerignore new file mode 100644 index 0000000..56b80bd --- /dev/null +++ b/server/.dockerignore @@ -0,0 +1,4 @@ +.git +Dockerfile +node_modules +dist \ No newline at end of file diff --git a/server/Dockerfile b/server/Dockerfile new file mode 100644 index 0000000..92b58b0 --- /dev/null +++ b/server/Dockerfile @@ -0,0 +1,11 @@ +FROM node:20 + +WORKDIR /catchy-tape + +COPY . . + +RUN npm install +RUN npm run build + +EXPOSE 3000 +CMD [ "npm", "run", "start:prod" ] \ No newline at end of file From bde774312941763cea2ed91f562295c0570aa9b0 Mon Sep 17 00:00:00 2001 From: 2taezeat Date: Tue, 21 Nov 2023 15:37:59 +0900 Subject: [PATCH 041/165] =?UTF-8?q?chore=20:=20root=20gradle=20=EC=97=90?= =?UTF-8?q?=20tasks=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- android/build.gradle.kts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/android/build.gradle.kts b/android/build.gradle.kts index de8e056..db2e4ee 100644 --- a/android/build.gradle.kts +++ b/android/build.gradle.kts @@ -10,4 +10,16 @@ plugins { alias(libs.plugins.navigation.safe.args) apply false } + +tasks.register("domain-unit-test") { + commandLine = listOf("gradle", "core:domain:test") +} + + +tasks.register("debug-unit-test") { + dependsOn("domain-unit-test") + commandLine = listOf("gradle", "testDebugUnitTest") +} + + true // Needed to make the Suppress annotation work for the plugins block \ No newline at end of file From f385d519c9c1bff5ba2b3250ec132db6a17cd962 Mon Sep 17 00:00:00 2001 From: 2taezeat Date: Tue, 21 Nov 2023 15:40:03 +0900 Subject: [PATCH 042/165] =?UTF-8?q?test=20:=20gradle=20=ED=99=95=EC=9D=B8?= =?UTF-8?q?=EC=9A=A9=20SampleTest=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ohdodok/catchytape/core/domain/SampleTest.kt | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 android/core/domain/src/test/java/com/ohdodok/catchytape/core/domain/SampleTest.kt diff --git a/android/core/domain/src/test/java/com/ohdodok/catchytape/core/domain/SampleTest.kt b/android/core/domain/src/test/java/com/ohdodok/catchytape/core/domain/SampleTest.kt new file mode 100644 index 0000000..bedb075 --- /dev/null +++ b/android/core/domain/src/test/java/com/ohdodok/catchytape/core/domain/SampleTest.kt @@ -0,0 +1,15 @@ +package com.ohdodok.catchytape.core.domain + +import io.kotest.core.spec.style.StringSpec +import io.kotest.matchers.shouldBe + +class SampleTest : StringSpec({ + "Strings" { + "length는 문자열의 길이를 반환해야 한다" { + "hello".length shouldBe 5 + } + } + "중첩 하지 않아도 됨" { + "hello".length shouldBe 5 + } +}) \ No newline at end of file From 2fea3ac41eaab08efb6df90f776da1969c12db53 Mon Sep 17 00:00:00 2001 From: 2taezeat Date: Tue, 21 Nov 2023 15:42:54 +0900 Subject: [PATCH 043/165] =?UTF-8?q?chore:=20android-ci,=20run=20test=20?= =?UTF-8?q?=EB=AA=85=EB=A0=B9=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/android-pull-request-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/android-pull-request-ci.yml b/.github/workflows/android-pull-request-ci.yml index c3ebd11..b7ba6e6 100644 --- a/.github/workflows/android-pull-request-ci.yml +++ b/.github/workflows/android-pull-request-ci.yml @@ -39,7 +39,7 @@ jobs: echo "$DEBUG_KEYSTORE" | base64 -d > debug.keystore echo "$KEYSTORE_PROPERTIES" > keystore.properties echo "$LOCAL_PROPERTIES" > local.properties - ./gradlew test --stacktrace + ./gradlew debug-unit-test --stacktrace - name: Publish Test Results if: always() From fa297b707f4b9e13481ca39fd5842313faf89cb7 Mon Sep 17 00:00:00 2001 From: 2taezeat Date: Tue, 21 Nov 2023 15:46:36 +0900 Subject: [PATCH 044/165] =?UTF-8?q?test=20:=20SampleTest=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../catchytape/core/domain/SampleTest.kt | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/android/core/domain/src/test/java/com/ohdodok/catchytape/core/domain/SampleTest.kt b/android/core/domain/src/test/java/com/ohdodok/catchytape/core/domain/SampleTest.kt index bedb075..6c68c9d 100644 --- a/android/core/domain/src/test/java/com/ohdodok/catchytape/core/domain/SampleTest.kt +++ b/android/core/domain/src/test/java/com/ohdodok/catchytape/core/domain/SampleTest.kt @@ -1,15 +1,20 @@ package com.ohdodok.catchytape.core.domain -import io.kotest.core.spec.style.StringSpec +import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.shouldBe -class SampleTest : StringSpec({ - "Strings" { - "length는 문자열의 길이를 반환해야 한다" { - "hello".length shouldBe 5 - } +class SampleTest : FunSpec({ + + test("test sample 1") { + 1 + 2 shouldBe 3 } - "중첩 하지 않아도 됨" { - "hello".length shouldBe 5 + + test("test sample 2") { + 3 + 4 shouldBe 7 } + + test("test sample 3") { + 3 + 4 shouldBe 10 + } + }) \ No newline at end of file From 0b3e793f154d022405bfafe662e47596bf79459e Mon Sep 17 00:00:00 2001 From: 2taezeat Date: Tue, 21 Nov 2023 15:54:11 +0900 Subject: [PATCH 045/165] =?UTF-8?q?test=20:=20SampleTest=20=EB=AA=A8?= =?UTF-8?q?=EB=91=90=20=ED=86=B5=EA=B3=BC=ED=95=98=EA=B2=8C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../test/java/com/ohdodok/catchytape/core/domain/SampleTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/core/domain/src/test/java/com/ohdodok/catchytape/core/domain/SampleTest.kt b/android/core/domain/src/test/java/com/ohdodok/catchytape/core/domain/SampleTest.kt index 6c68c9d..bcda4eb 100644 --- a/android/core/domain/src/test/java/com/ohdodok/catchytape/core/domain/SampleTest.kt +++ b/android/core/domain/src/test/java/com/ohdodok/catchytape/core/domain/SampleTest.kt @@ -14,7 +14,7 @@ class SampleTest : FunSpec({ } test("test sample 3") { - 3 + 4 shouldBe 10 + 3 + 7 shouldBe 10 } }) \ No newline at end of file From 3c0d1a51138081424661c22ea470b44e813ca7b0 Mon Sep 17 00:00:00 2001 From: hyungun Date: Tue, 21 Nov 2023 15:56:38 +0900 Subject: [PATCH 046/165] =?UTF-8?q?feat=20:=20/users/verify=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * JWT Access Token 으로 사용자 검증 * UserId 를 반환 --- server/src/auth/auth.controller.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/server/src/auth/auth.controller.ts b/server/src/auth/auth.controller.ts index c50840c..95d79b1 100644 --- a/server/src/auth/auth.controller.ts +++ b/server/src/auth/auth.controller.ts @@ -1,14 +1,18 @@ import { Body, Controller, + Get, HttpCode, Post, + Req, + UseGuards, UsePipes, ValidationPipe, } from '@nestjs/common'; import { AuthService } from './auth.service'; import { HTTP_STATUS_CODE } from 'src/httpStatusCode.enum'; import { UserCreateDto } from 'src/dto/userCreate.dto'; +import { AuthGuard } from '@nestjs/passport'; @Controller('users') export class AuthController { @@ -31,4 +35,11 @@ export class AuthController { ): Promise<{ accessToken: string }> { return this.authService.signup(userCreateDto); } + + @Get('verify') + @UseGuards(AuthGuard()) + verifyToken(@Req() req): { userId: string } { + const user = req.user; + return { userId: user.user_id }; + } } From f8618f14c5b219906c32af7f692f0306ed660abf Mon Sep 17 00:00:00 2001 From: algosketch Date: Tue, 21 Nov 2023 15:57:54 +0900 Subject: [PATCH 047/165] =?UTF-8?q?add=20:=20=ED=95=84=EC=9A=94=ED=95=9C?= =?UTF-8?q?=20=EC=95=84=EC=9D=B4=EC=BD=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- android/core/ui/src/main/res/drawable/ic_next.xml | 9 +++++++++ android/core/ui/src/main/res/drawable/ic_pause.xml | 9 +++++++++ android/core/ui/src/main/res/drawable/ic_play.xml | 9 +++++++++ android/core/ui/src/main/res/drawable/ic_playlist.xml | 9 +++++++++ android/core/ui/src/main/res/drawable/ic_previous.xml | 9 +++++++++ 5 files changed, 45 insertions(+) create mode 100644 android/core/ui/src/main/res/drawable/ic_next.xml create mode 100644 android/core/ui/src/main/res/drawable/ic_pause.xml create mode 100644 android/core/ui/src/main/res/drawable/ic_play.xml create mode 100644 android/core/ui/src/main/res/drawable/ic_playlist.xml create mode 100644 android/core/ui/src/main/res/drawable/ic_previous.xml diff --git a/android/core/ui/src/main/res/drawable/ic_next.xml b/android/core/ui/src/main/res/drawable/ic_next.xml new file mode 100644 index 0000000..a0f7a6f --- /dev/null +++ b/android/core/ui/src/main/res/drawable/ic_next.xml @@ -0,0 +1,9 @@ + + + diff --git a/android/core/ui/src/main/res/drawable/ic_pause.xml b/android/core/ui/src/main/res/drawable/ic_pause.xml new file mode 100644 index 0000000..604f7cb --- /dev/null +++ b/android/core/ui/src/main/res/drawable/ic_pause.xml @@ -0,0 +1,9 @@ + + + diff --git a/android/core/ui/src/main/res/drawable/ic_play.xml b/android/core/ui/src/main/res/drawable/ic_play.xml new file mode 100644 index 0000000..f711f9c --- /dev/null +++ b/android/core/ui/src/main/res/drawable/ic_play.xml @@ -0,0 +1,9 @@ + + + diff --git a/android/core/ui/src/main/res/drawable/ic_playlist.xml b/android/core/ui/src/main/res/drawable/ic_playlist.xml new file mode 100644 index 0000000..e82f36b --- /dev/null +++ b/android/core/ui/src/main/res/drawable/ic_playlist.xml @@ -0,0 +1,9 @@ + + + diff --git a/android/core/ui/src/main/res/drawable/ic_previous.xml b/android/core/ui/src/main/res/drawable/ic_previous.xml new file mode 100644 index 0000000..5e76bb2 --- /dev/null +++ b/android/core/ui/src/main/res/drawable/ic_previous.xml @@ -0,0 +1,9 @@ + + + From 901f1c1bdbf397573ecb30ddce1814d995c4ba2c Mon Sep 17 00:00:00 2001 From: hyungun Date: Tue, 21 Nov 2023 16:05:13 +0900 Subject: [PATCH 048/165] =?UTF-8?q?feat=20:=20=EC=82=AC=EC=9A=A9=EC=9E=90?= =?UTF-8?q?=20=ED=83=88=ED=87=B4=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * DELETE /users 로 사용자 탈퇴 * 성공하면 userId 를 반환 --- server/src/auth/auth.controller.ts | 13 ++++++++++++- server/src/auth/auth.service.ts | 5 +++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/server/src/auth/auth.controller.ts b/server/src/auth/auth.controller.ts index 95d79b1..a8a3519 100644 --- a/server/src/auth/auth.controller.ts +++ b/server/src/auth/auth.controller.ts @@ -1,6 +1,7 @@ import { Body, Controller, + Delete, Get, HttpCode, Post, @@ -13,6 +14,7 @@ import { AuthService } from './auth.service'; import { HTTP_STATUS_CODE } from 'src/httpStatusCode.enum'; import { UserCreateDto } from 'src/dto/userCreate.dto'; import { AuthGuard } from '@nestjs/passport'; +import { User } from 'src/entity/user.entity'; @Controller('users') export class AuthController { @@ -38,8 +40,17 @@ export class AuthController { @Get('verify') @UseGuards(AuthGuard()) + @HttpCode(HTTP_STATUS_CODE.SUCCESS) verifyToken(@Req() req): { userId: string } { - const user = req.user; + const user: User = req.user; return { userId: user.user_id }; } + + @Delete() + @UseGuards(AuthGuard()) + @HttpCode(HTTP_STATUS_CODE.SUCCESS) + async deleteUser(@Req() req): Promise<{userId: string}> { + const user: User = req.user; + return await this.authService.deleteUser(user); + } } diff --git a/server/src/auth/auth.service.ts b/server/src/auth/auth.service.ts index 9b1e870..c43b711 100644 --- a/server/src/auth/auth.service.ts +++ b/server/src/auth/auth.service.ts @@ -78,4 +78,9 @@ export class AuthService { return true; } } + + async deleteUser(user: User): Promise<{ userId: string }> { + await this.userRepository.delete(user.user_id); + return { userId: user.user_id }; + } } From 222762b0dff3fbec13696fb43033b19a6e86eb4e Mon Sep 17 00:00:00 2001 From: algosketch Date: Tue, 21 Nov 2023 16:05:41 +0900 Subject: [PATCH 049/165] =?UTF-8?q?feat=20:=20=EC=9E=AC=EC=83=9D=20?= =?UTF-8?q?=ED=99=94=EB=A9=B4=20=EB=A0=88=EC=9D=B4=EC=95=84=EC=9B=83=20?= =?UTF-8?q?=EB=B0=B0=EC=B9=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/res/drawable/ic_view_radius_12.xml | 5 + .../src/main/res/layout/fragment_player.xml | 133 +++++++++++++++++- 2 files changed, 137 insertions(+), 1 deletion(-) create mode 100644 android/core/ui/src/main/res/drawable/ic_view_radius_12.xml diff --git a/android/core/ui/src/main/res/drawable/ic_view_radius_12.xml b/android/core/ui/src/main/res/drawable/ic_view_radius_12.xml new file mode 100644 index 0000000..98eb7c3 --- /dev/null +++ b/android/core/ui/src/main/res/drawable/ic_view_radius_12.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/android/feature/player/src/main/res/layout/fragment_player.xml b/android/feature/player/src/main/res/layout/fragment_player.xml index 0e0a637..a3af9dd 100644 --- a/android/feature/player/src/main/res/layout/fragment_player.xml +++ b/android/feature/player/src/main/res/layout/fragment_player.xml @@ -1,5 +1,7 @@ - + @@ -12,5 +14,134 @@ android:layout_width="match_parent" android:layout_height="match_parent"> + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 5f700864e735586e828dba10d166c9228203a2c1 Mon Sep 17 00:00:00 2001 From: youlalala Date: Tue, 21 Nov 2023 16:18:40 +0900 Subject: [PATCH 050/165] =?UTF-8?q?feat=20:=20=EC=9E=A5=EB=A5=B4=20?= =?UTF-8?q?=EA=B0=80=EC=A0=B8=EC=98=A4=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/data/di/RepositoryModule.kt | 5 ++++ .../data/repository/MusicRepositoryImpl.kt | 20 ++++++++++++++++ .../core/domain/repository/MusicRepository.kt | 4 +++- .../domain/usecase/GetMusicGenresUseCase.kt | 12 ++++++++++ .../core/ui/src/main/res/values/arrays.xml | 11 --------- .../feature/upload/BindingAdapter.kt | 8 +++++++ .../feature/upload/UploadViewModel.kt | 23 +++++++++++++++---- .../src/main/res/layout/fragment_upload.xml | 5 ++-- 8 files changed, 70 insertions(+), 18 deletions(-) create mode 100644 android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/MusicRepositoryImpl.kt create mode 100644 android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/GetMusicGenresUseCase.kt delete mode 100644 android/core/ui/src/main/res/values/arrays.xml diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/di/RepositoryModule.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/di/RepositoryModule.kt index 8aff86e..0b3179a 100644 --- a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/di/RepositoryModule.kt +++ b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/di/RepositoryModule.kt @@ -1,7 +1,9 @@ package com.ohdodok.catchytape.core.data.di import com.ohdodok.catchytape.core.data.repository.AuthRepositoryImpl +import com.ohdodok.catchytape.core.data.repository.MusicRepositoryImpl import com.ohdodok.catchytape.core.domain.repository.AuthRepository +import com.ohdodok.catchytape.core.domain.repository.MusicRepository import dagger.Binds import dagger.Module import dagger.hilt.InstallIn @@ -13,4 +15,7 @@ interface RepositoryModule { @Binds fun bindAuthRepository(authRepositoryImpl: AuthRepositoryImpl): AuthRepository + + @Binds + fun bindMusicRepository(musicRepositoryImpl: MusicRepositoryImpl): MusicRepository } \ No newline at end of file diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/MusicRepositoryImpl.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/MusicRepositoryImpl.kt new file mode 100644 index 0000000..fd94496 --- /dev/null +++ b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/MusicRepositoryImpl.kt @@ -0,0 +1,20 @@ +package com.ohdodok.catchytape.core.data.repository + +import com.ohdodok.catchytape.core.data.api.MusicApi +import com.ohdodok.catchytape.core.domain.repository.MusicRepository +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import javax.inject.Inject + +class MusicRepositoryImpl @Inject constructor( + private val musicApi: MusicApi +) : MusicRepository { + override fun getGenres(): Flow> = flow { + val response = musicApi.getGenres() + when (response.code()) { + // TODO : 네트워크 에러 로직 처리 + in 200..299 -> emit(response.body()?.genres ?: emptyList()) + else -> throw RuntimeException("네트워크 에러") + } + } +} \ No newline at end of file diff --git a/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/repository/MusicRepository.kt b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/repository/MusicRepository.kt index 02cf2ed..2dbacd5 100644 --- a/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/repository/MusicRepository.kt +++ b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/repository/MusicRepository.kt @@ -1,6 +1,8 @@ package com.ohdodok.catchytape.core.domain.repository +import kotlinx.coroutines.flow.Flow + interface MusicRepository { - fun getGenres(): List + fun getGenres(): Flow> } \ No newline at end of file diff --git a/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/GetMusicGenresUseCase.kt b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/GetMusicGenresUseCase.kt new file mode 100644 index 0000000..4f93f64 --- /dev/null +++ b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/GetMusicGenresUseCase.kt @@ -0,0 +1,12 @@ +package com.ohdodok.catchytape.core.domain.usecase + +import com.ohdodok.catchytape.core.domain.repository.MusicRepository +import kotlinx.coroutines.flow.Flow +import javax.inject.Inject + +class GetMusicGenresUseCase @Inject constructor( + private val musicRepository: MusicRepository +) { + + operator fun invoke(): Flow> = musicRepository.getGenres() +} \ No newline at end of file diff --git a/android/core/ui/src/main/res/values/arrays.xml b/android/core/ui/src/main/res/values/arrays.xml deleted file mode 100644 index 02cfab3..0000000 --- a/android/core/ui/src/main/res/values/arrays.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - 장르를 선택해 주세요. - genre1 - genre2 - genre3 - genre4 - genre5 - - \ No newline at end of file diff --git a/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/BindingAdapter.kt b/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/BindingAdapter.kt index 85f6a5e..4af0ce0 100644 --- a/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/BindingAdapter.kt +++ b/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/BindingAdapter.kt @@ -1,5 +1,7 @@ package com.ohdodok.catchytape.feature.upload +import android.R +import android.widget.ArrayAdapter import android.widget.AutoCompleteTextView import androidx.databinding.BindingAdapter @@ -7,4 +9,10 @@ import androidx.databinding.BindingAdapter @BindingAdapter("changeSelectedPosition") fun AutoCompleteTextView.bindPosition(onChange: (Int) -> Unit) { setOnItemClickListener { _, _, position, _ -> onChange(position) } +} + +@BindingAdapter("list") +fun AutoCompleteTextView.setAdapter(list: List) { + val adapter = ArrayAdapter(this.context, R.layout.simple_list_item_1, list) + setAdapter(adapter) } \ No newline at end of file diff --git a/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadViewModel.kt b/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadViewModel.kt index 2e703b5..2e600c7 100644 --- a/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadViewModel.kt +++ b/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadViewModel.kt @@ -5,6 +5,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel import java.io.File import javax.inject.Inject import androidx.lifecycle.viewModelScope +import com.ohdodok.catchytape.core.domain.usecase.GetMusicGenresUseCase import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow @@ -16,22 +17,36 @@ import kotlinx.coroutines.flow.stateIn @HiltViewModel class UploadViewModel @Inject constructor( - + private val getMusicGenresUseCase: GetMusicGenresUseCase ) : ViewModel() { private var uploadedImage: String? = null val uploadedMusicTitle = MutableStateFlow("") - private val _uploadedMusicGenrePosition = MutableStateFlow(0) + private val _uploadedMusicGenrePosition = MutableStateFlow(-1) val uploadedMusicGenrePosition = _uploadedMusicGenrePosition.asStateFlow() val isUploadEnable: StateFlow = combine(uploadedMusicTitle, uploadedMusicGenrePosition) { title, genrePosition -> - title.isNotBlank() && genrePosition != 0 + title.isNotBlank() && genrePosition != -1 }.stateIn(viewModelScope, SharingStarted.Eagerly, false) - val onChangePosition: (Int) -> Unit = { position: Int -> _uploadedMusicGenrePosition.value = position } + val onChangePosition: (Int) -> Unit = + { position: Int -> _uploadedMusicGenrePosition.value = position } + + private val _musicGenres: MutableStateFlow> = MutableStateFlow(emptyList()) + val musicGenres = _musicGenres.asStateFlow() + + init { + fetchGenres() + } + + private fun fetchGenres() { + getMusicGenresUseCase().onEach { + _musicGenres.value = it + }.launchIn(viewModelScope) + } fun uploadImage(imageFile: File) { // todo : image 파일을 업로드 한다. diff --git a/android/feature/upload/src/main/res/layout/fragment_upload.xml b/android/feature/upload/src/main/res/layout/fragment_upload.xml index 71e939e..b118288 100644 --- a/android/feature/upload/src/main/res/layout/fragment_upload.xml +++ b/android/feature/upload/src/main/res/layout/fragment_upload.xml @@ -102,11 +102,12 @@ app:layout_constraintTop_toBottomOf="@id/til_title"> + app:list="@{viewModel.musicGenres}" + app:changeSelectedPosition="@{viewModel.onChangePosition}" /> From 28d62290d8348bffa3b8df93764a715c5bec13da Mon Sep 17 00:00:00 2001 From: youlalala Date: Tue, 21 Nov 2023 16:29:40 +0900 Subject: [PATCH 051/165] =?UTF-8?q?chore=20:=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=A0=95=EB=A0=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/ohdodok/catchytape/core/data/api/MusicApi.kt | 2 +- .../core/data/repository/MusicRepositoryImpl.kt | 1 + .../upload/src/main/res/layout/fragment_upload.xml | 12 ++++++------ 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/api/MusicApi.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/api/MusicApi.kt index 6903920..83c712b 100644 --- a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/api/MusicApi.kt +++ b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/api/MusicApi.kt @@ -7,6 +7,6 @@ import retrofit2.http.GET interface MusicApi { @GET("musics/genres") - suspend fun getGenres() : Response + suspend fun getGenres(): Response } \ No newline at end of file diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/MusicRepositoryImpl.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/MusicRepositoryImpl.kt index fd94496..a60a139 100644 --- a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/MusicRepositoryImpl.kt +++ b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/MusicRepositoryImpl.kt @@ -9,6 +9,7 @@ import javax.inject.Inject class MusicRepositoryImpl @Inject constructor( private val musicApi: MusicApi ) : MusicRepository { + override fun getGenres(): Flow> = flow { val response = musicApi.getGenres() when (response.code()) { diff --git a/android/feature/upload/src/main/res/layout/fragment_upload.xml b/android/feature/upload/src/main/res/layout/fragment_upload.xml index b118288..762b7ed 100644 --- a/android/feature/upload/src/main/res/layout/fragment_upload.xml +++ b/android/feature/upload/src/main/res/layout/fragment_upload.xml @@ -42,13 +42,13 @@ android:layout_width="200dp" android:layout_height="200dp" android:layout_marginTop="@dimen/extra_large" + android:contentDescription="@string/select_thumbnail" app:cardBackgroundColor="@color/on_surface_variant" app:cardCornerRadius="20dp" app:cardElevation="0dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/tb_upload" - android:contentDescription="@string/select_thumbnail"> + app:layout_constraintTop_toBottomOf="@id/tb_upload"> + android:importantForAccessibility="no" + android:src="@drawable/ic_camera" /> @@ -106,8 +106,8 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:inputType="none" - app:list="@{viewModel.musicGenres}" - app:changeSelectedPosition="@{viewModel.onChangePosition}" /> + app:changeSelectedPosition="@{viewModel.onChangePosition}" + app:list="@{viewModel.musicGenres}" /> From 1d81f0cb0557043ce8f13da91a8535e7a0443bc7 Mon Sep 17 00:00:00 2001 From: 2taezeat Date: Tue, 21 Nov 2023 16:32:19 +0900 Subject: [PATCH 052/165] =?UTF-8?q?chore=20:=20test=20Task=20=EC=B9=B4?= =?UTF-8?q?=EB=A9=9C=20=EC=BC=80=EC=9D=B4=EC=8A=A4=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- android/build.gradle.kts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/android/build.gradle.kts b/android/build.gradle.kts index db2e4ee..91b40a9 100644 --- a/android/build.gradle.kts +++ b/android/build.gradle.kts @@ -11,13 +11,13 @@ plugins { } -tasks.register("domain-unit-test") { +tasks.register("domainUnitTest") { commandLine = listOf("gradle", "core:domain:test") } -tasks.register("debug-unit-test") { - dependsOn("domain-unit-test") +tasks.register("debugUnitTest") { + dependsOn("domainUnitTest") commandLine = listOf("gradle", "testDebugUnitTest") } From 0d45f277fba6ec82e6199e2664197711f1b7e6f4 Mon Sep 17 00:00:00 2001 From: 2taezeat Date: Tue, 21 Nov 2023 16:33:44 +0900 Subject: [PATCH 053/165] =?UTF-8?q?chore:=20android-ci,=20run=20test=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/android-pull-request-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/android-pull-request-ci.yml b/.github/workflows/android-pull-request-ci.yml index b7ba6e6..6cc7755 100644 --- a/.github/workflows/android-pull-request-ci.yml +++ b/.github/workflows/android-pull-request-ci.yml @@ -39,7 +39,7 @@ jobs: echo "$DEBUG_KEYSTORE" | base64 -d > debug.keystore echo "$KEYSTORE_PROPERTIES" > keystore.properties echo "$LOCAL_PROPERTIES" > local.properties - ./gradlew debug-unit-test --stacktrace + ./gradlew debugUnitTest --stacktrace - name: Publish Test Results if: always() From d2884cac6e86e34fd43f9efed0de79d4acbec33a Mon Sep 17 00:00:00 2001 From: algosketch Date: Tue, 21 Nov 2023 16:58:50 +0900 Subject: [PATCH 054/165] =?UTF-8?q?refactor=20:=20UseCase=20=ED=8C=A8?= =?UTF-8?q?=ED=82=A4=EC=A7=80=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/{ => usecase}/signup/ValidateNicknameUseCase.kt | 3 +-- .../{ => usecase}/signup/NicknameValidationUseCaseTest.kt | 4 +--- .../com/ohdodok/catchytape/feature/login/NicknameFragment.kt | 2 +- .../com/ohdodok/catchytape/feature/login/NicknameViewModel.kt | 4 ++-- .../feature/login/src/main/res/layout/fragment_nickname.xml | 2 +- 5 files changed, 6 insertions(+), 9 deletions(-) rename android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/{ => usecase}/signup/ValidateNicknameUseCase.kt (94%) rename android/core/domain/src/test/java/com/ohdodok/catchytape/core/domain/{ => usecase}/signup/NicknameValidationUseCaseTest.kt (96%) diff --git a/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/signup/ValidateNicknameUseCase.kt b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/signup/ValidateNicknameUseCase.kt similarity index 94% rename from android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/signup/ValidateNicknameUseCase.kt rename to android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/signup/ValidateNicknameUseCase.kt index e50a25a..18924a3 100644 --- a/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/signup/ValidateNicknameUseCase.kt +++ b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/signup/ValidateNicknameUseCase.kt @@ -1,4 +1,4 @@ -package com.ohdodok.catchytape.core.domain.signup +package com.ohdodok.catchytape.core.domain.usecase.signup import com.ohdodok.catchytape.core.domain.repository.AuthRepository import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -6,7 +6,6 @@ import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.single import javax.inject.Inject diff --git a/android/core/domain/src/test/java/com/ohdodok/catchytape/core/domain/signup/NicknameValidationUseCaseTest.kt b/android/core/domain/src/test/java/com/ohdodok/catchytape/core/domain/usecase/signup/NicknameValidationUseCaseTest.kt similarity index 96% rename from android/core/domain/src/test/java/com/ohdodok/catchytape/core/domain/signup/NicknameValidationUseCaseTest.kt rename to android/core/domain/src/test/java/com/ohdodok/catchytape/core/domain/usecase/signup/NicknameValidationUseCaseTest.kt index c7c2a02..ff01cfc 100644 --- a/android/core/domain/src/test/java/com/ohdodok/catchytape/core/domain/signup/NicknameValidationUseCaseTest.kt +++ b/android/core/domain/src/test/java/com/ohdodok/catchytape/core/domain/usecase/signup/NicknameValidationUseCaseTest.kt @@ -1,15 +1,13 @@ -package com.ohdodok.catchytape.core.domain.signup +package com.ohdodok.catchytape.core.domain.usecase.signup import com.ohdodok.catchytape.core.domain.repository.AuthRepository import io.kotest.core.spec.style.BehaviorSpec import io.kotest.matchers.shouldBe import io.mockk.every import io.mockk.mockk -import kotlinx.coroutines.delay import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach class NicknameValidationUseCaseTest : BehaviorSpec() { diff --git a/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/NicknameFragment.kt b/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/NicknameFragment.kt index 8a221af..e2fc9ce 100644 --- a/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/NicknameFragment.kt +++ b/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/NicknameFragment.kt @@ -6,7 +6,7 @@ import android.widget.TextView import androidx.databinding.BindingAdapter import androidx.fragment.app.viewModels import androidx.navigation.fragment.navArgs -import com.ohdodok.catchytape.core.domain.signup.NicknameValidationResult +import com.ohdodok.catchytape.core.domain.usecase.signup.NicknameValidationResult import com.ohdodok.catchytape.core.ui.BaseFragment import com.ohdodok.catchytape.feature.login.databinding.FragmentNicknameBinding import dagger.hilt.android.AndroidEntryPoint diff --git a/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/NicknameViewModel.kt b/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/NicknameViewModel.kt index 576c4bb..4e84c90 100644 --- a/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/NicknameViewModel.kt +++ b/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/NicknameViewModel.kt @@ -2,8 +2,8 @@ package com.ohdodok.catchytape.feature.login import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.ohdodok.catchytape.core.domain.signup.NicknameValidationResult -import com.ohdodok.catchytape.core.domain.signup.ValidateNicknameUseCase +import com.ohdodok.catchytape.core.domain.usecase.signup.NicknameValidationResult +import com.ohdodok.catchytape.core.domain.usecase.signup.ValidateNicknameUseCase import com.ohdodok.catchytape.core.domain.usecase.SignUpUseCase import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableSharedFlow diff --git a/android/feature/login/src/main/res/layout/fragment_nickname.xml b/android/feature/login/src/main/res/layout/fragment_nickname.xml index 77a9588..089dfe8 100644 --- a/android/feature/login/src/main/res/layout/fragment_nickname.xml +++ b/android/feature/login/src/main/res/layout/fragment_nickname.xml @@ -5,7 +5,7 @@ - + Date: Tue, 21 Nov 2023 17:15:39 +0900 Subject: [PATCH 055/165] =?UTF-8?q?chore=20:=20=EC=8A=A4=ED=86=A0=EB=A6=AC?= =?UTF-8?q?=EC=A7=80=EC=97=90=20=EC=97=85=EB=A1=9C=EB=93=9C=20=ED=95=98?= =?UTF-8?q?=EA=B8=B0=20=EC=9C=84=ED=95=9C=20=EC=A0=95=EB=B3=B4=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/src/config/ncloud.config.ts | 1 + server/src/constants.ts | 2 ++ 2 files changed, 3 insertions(+) diff --git a/server/src/config/ncloud.config.ts b/server/src/config/ncloud.config.ts index 09d2038..47cf405 100644 --- a/server/src/config/ncloud.config.ts +++ b/server/src/config/ncloud.config.ts @@ -14,6 +14,7 @@ export class NcloudConfigService { accessKeyId: this.configService.get('ACCESS_ID'), secretAccessKey: this.configService.get('SECRET_ACCESS_KEY'), }, + signatureVersion: 'v4', }); } } diff --git a/server/src/constants.ts b/server/src/constants.ts index a994751..72c9cd0 100644 --- a/server/src/constants.ts +++ b/server/src/constants.ts @@ -13,3 +13,5 @@ export enum Genres { 'dance' = 'dance', 'etc' = 'etc', } + +export const fileExt = ['jpeg', 'jpg', 'png', 'mp3']; From 8585cfe37ed31d52f3ca9650990bea3247a40d96 Mon Sep 17 00:00:00 2001 From: Seokyung Lim Date: Tue, 21 Nov 2023 17:18:12 +0900 Subject: [PATCH 056/165] =?UTF-8?q?feat=20:=20singedURL=20=EB=B0=9C?= =?UTF-8?q?=EA=B8=89=20=EC=9C=84=ED=95=9C=20API=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * GET 요청 : /upload params: path, fileName, ext * 다만 업로드 된 파일의 접근 권한을 바꿔줘야 하는데, 아직까진 마땅한 방법을 찾지 못함 (Object Storage에서 현재는 수동으로 파일 권한 열어 링크에 접근할 수 있도록 함) --- server/src/upload/upload.controller.ts | 60 ++++++++------------------ server/src/upload/upload.module.ts | 2 + server/src/upload/upload.service.ts | 60 ++++++++++++++------------ 3 files changed, 52 insertions(+), 70 deletions(-) diff --git a/server/src/upload/upload.controller.ts b/server/src/upload/upload.controller.ts index 86f1357..b7ee50a 100644 --- a/server/src/upload/upload.controller.ts +++ b/server/src/upload/upload.controller.ts @@ -1,52 +1,28 @@ -import { - Controller, - Post, - UploadedFile, - UseInterceptors, - ParseFilePipe, - MaxFileSizeValidator, - FileTypeValidator, -} from '@nestjs/common'; +import { Controller, Get, Query, HttpCode } from '@nestjs/common'; import { UploadService } from './upload.service'; import { HTTP_STATUS_CODE } from 'src/httpStatusCode.enum'; -import { FileInterceptor } from '@nestjs/platform-express'; -import { fileSize } from 'src/constants'; @Controller('upload') export class UploadController { constructor(private uploadService: UploadService) {} - @Post('/music') - @UseInterceptors(FileInterceptor('file')) - async uploadMusic( - @UploadedFile( - new ParseFilePipe({ - validators: [ - new MaxFileSizeValidator({ maxSize: fileSize.MUSIC_FILE_LIMIT_SIZE }), - new FileTypeValidator({ fileType: 'audio/mpeg' }), - ], - }), - ) - file: Express.Multer.File, - ): Promise<{ url: string }> { - const { url } = await this.uploadService.uploadMusic(file); - return { url }; - } + @Get() + @HttpCode(HTTP_STATUS_CODE.SUCCESS) + async getSignedURL( + @Query('path') path: string, + @Query('fileName') fileName: string, + @Query('ext') ext: string, + ): Promise<{ signedUrl: string }> { + try { + const signedUrl = await this.uploadService.getSignedURL( + path, + fileName, + ext, + ); - @Post('/image') - @UseInterceptors(FileInterceptor('file')) - async uploadImage( - @UploadedFile( - new ParseFilePipe({ - validators: [ - new MaxFileSizeValidator({ maxSize: fileSize.IMAGE_FILE_LIMIT_SIZE }), - new FileTypeValidator({ fileType: 'image/jpeg' }), - ], - }), - ) - file: Express.Multer.File, - ): Promise<{ url: string }> { - const { url } = await this.uploadService.uploadImage(file); - return { url }; + return { signedUrl }; + } catch (error) { + throw error; + } } } diff --git a/server/src/upload/upload.module.ts b/server/src/upload/upload.module.ts index 6fc1701..2a777c0 100644 --- a/server/src/upload/upload.module.ts +++ b/server/src/upload/upload.module.ts @@ -2,8 +2,10 @@ import { Module } from '@nestjs/common'; import { UploadController } from './upload.controller'; import { UploadService } from './upload.service'; import { NcloudConfigService } from 'src/config/ncloud.config'; +import { HttpModule } from '@nestjs/axios'; @Module({ + imports: [HttpModule], controllers: [UploadController], providers: [UploadService, NcloudConfigService], }) diff --git a/server/src/upload/upload.service.ts b/server/src/upload/upload.service.ts index 4b2edd1..c28804e 100644 --- a/server/src/upload/upload.service.ts +++ b/server/src/upload/upload.service.ts @@ -2,44 +2,48 @@ import { HttpException, Injectable } from '@nestjs/common'; import { HTTP_STATUS_CODE } from 'src/httpStatusCode.enum'; import { NcloudConfigService } from './../config/ncloud.config'; import { S3 } from 'aws-sdk'; -import { Readable } from 'stream'; +import { fileExt } from './../constants'; @Injectable() export class UploadService { - private readonly objectStorage: S3; - + private objectStorage: S3; constructor(private readonly nCloudConfigService: NcloudConfigService) { this.objectStorage = nCloudConfigService.createObjectStorageOption(); } - async uploadMusic(file: Express.Multer.File): Promise<{ url: string }> { - try { - const uploadResult = await this.objectStorage - .upload({ - Bucket: 'catchy-tape-bucket2', - Key: `music/original/${file.originalname}`, - Body: Readable.from(file.buffer), - }) - .promise(); - - return { url: uploadResult.Location }; - } catch { - throw new HttpException('SERVER ERROR', HTTP_STATUS_CODE.SERVER_ERROR); - } + private isValidPath(path: string) { + if (path.startsWith('image/user/')) return true; + if (path.startsWith('image/cover/')) return true; + if (path.startsWith('music/')) return true; + + return false; } - async uploadImage(file: Express.Multer.File): Promise<{ url: string }> { + private isValidExt(ext: string) { + if (fileExt.includes(ext)) return true; + + return false; + } + + async getSignedURL( + keyPath: string, + fileName: string, + ext: string, + ): Promise { try { - const uploadResult = await this.objectStorage - .upload({ - Bucket: 'catchy-tape-bucket2', - Key: `image/${file.originalname}`, - Body: Readable.from(file.buffer), - }) - .promise(); - - return { url: uploadResult.Location }; - } catch { + if (!this.isValidPath(keyPath) || !this.isValidExt(ext)) + throw new HttpException('BAD REQUEST', HTTP_STATUS_CODE.BAD_REQUEST); + + const url = await this.objectStorage.getSignedUrlPromise('putObject', { + Bucket: 'catchy-tape-bucket2', + Key: `${keyPath}/${fileName}.${ext}`, + Expires: 60, + }); + + return url; + } catch (error) { + if (error instanceof HttpException) throw error; + throw new HttpException('SERVER ERROR', HTTP_STATUS_CODE.SERVER_ERROR); } } From 02aff2181d63176082089b92dba49fcc1d532462 Mon Sep 17 00:00:00 2001 From: Seokyung Lim Date: Tue, 21 Nov 2023 17:36:04 +0900 Subject: [PATCH 057/165] =?UTF-8?q?refactor=20:=20path=20=EC=9C=A0?= =?UTF-8?q?=ED=9A=A8=EC=84=B1=20=EA=B2=80=EC=82=AC=20=EA=B5=AC=EC=B2=B4?= =?UTF-8?q?=EC=A0=81=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 세 개의 경로 (image/user/아이디, image/music/아이디, music/아이디)에 맞는 정규표현식 추가해서 검사 --- server/src/constants.ts | 6 ++++++ server/src/upload/upload.service.ts | 9 ++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/server/src/constants.ts b/server/src/constants.ts index 72c9cd0..f1361a5 100644 --- a/server/src/constants.ts +++ b/server/src/constants.ts @@ -15,3 +15,9 @@ export enum Genres { } export const fileExt = ['jpeg', 'jpg', 'png', 'mp3']; + +export const pathPattern = [ + /^image\/user\/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/, + /^image\/cover\/\d+$/, + /^music\/\d+$/, +]; diff --git a/server/src/upload/upload.service.ts b/server/src/upload/upload.service.ts index c28804e..cadd443 100644 --- a/server/src/upload/upload.service.ts +++ b/server/src/upload/upload.service.ts @@ -3,6 +3,7 @@ import { HTTP_STATUS_CODE } from 'src/httpStatusCode.enum'; import { NcloudConfigService } from './../config/ncloud.config'; import { S3 } from 'aws-sdk'; import { fileExt } from './../constants'; +import { pathPattern } from './../constants'; @Injectable() export class UploadService { @@ -12,9 +13,11 @@ export class UploadService { } private isValidPath(path: string) { - if (path.startsWith('image/user/')) return true; - if (path.startsWith('image/cover/')) return true; - if (path.startsWith('music/')) return true; + for (let i = 0; i < pathPattern.length; i++) { + if (pathPattern[i].test(path)) { + return true; + } + } return false; } From 4739a82fff935b5f108c34dac33e6e463856dff7 Mon Sep 17 00:00:00 2001 From: algosketch Date: Tue, 21 Nov 2023 17:38:30 +0900 Subject: [PATCH 058/165] =?UTF-8?q?feat=20:=20SeekBar=20UI=20=EB=94=94?= =?UTF-8?q?=ED=85=8C=EC=9D=BC=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/player/src/main/res/layout/fragment_player.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/android/feature/player/src/main/res/layout/fragment_player.xml b/android/feature/player/src/main/res/layout/fragment_player.xml index a3af9dd..ee70b33 100644 --- a/android/feature/player/src/main/res/layout/fragment_player.xml +++ b/android/feature/player/src/main/res/layout/fragment_player.xml @@ -83,7 +83,12 @@ android:id="@+id/sb_music_progress" android:layout_width="0dp" android:layout_height="wrap_content" + android:layout_marginHorizontal="@dimen/margin_horizontal" android:layout_marginTop="8dp" + android:padding="0dp" + android:progressBackgroundTint="@color/on_surface_variant" + android:progressTint="@color/on_surface" + android:thumbTint="@color/on_surface" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/btn_add_to_playlist" From a583a937f869be23491b38294b5310388fe5cd52 Mon Sep 17 00:00:00 2001 From: algosketch Date: Tue, 21 Nov 2023 17:40:34 +0900 Subject: [PATCH 059/165] =?UTF-8?q?feat=20:=20=ED=94=8C=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EC=96=B4=20=EB=84=A4=EB=B9=84=EA=B2=8C=EC=9D=B4=EC=85=98=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/res/navigation/catchytape_navigation.xml | 1 + .../src/main/res/navigation/player_navigation.xml | 14 ++++++++++++++ 2 files changed, 15 insertions(+) create mode 100644 android/feature/player/src/main/res/navigation/player_navigation.xml diff --git a/android/app/src/main/res/navigation/catchytape_navigation.xml b/android/app/src/main/res/navigation/catchytape_navigation.xml index dc9a4e9..a4cda82 100644 --- a/android/app/src/main/res/navigation/catchytape_navigation.xml +++ b/android/app/src/main/res/navigation/catchytape_navigation.xml @@ -7,5 +7,6 @@ + \ No newline at end of file diff --git a/android/feature/player/src/main/res/navigation/player_navigation.xml b/android/feature/player/src/main/res/navigation/player_navigation.xml new file mode 100644 index 0000000..1633715 --- /dev/null +++ b/android/feature/player/src/main/res/navigation/player_navigation.xml @@ -0,0 +1,14 @@ + + + + + + \ No newline at end of file From af21abbe61830a49367b869d5e5b803a75ae4b8c Mon Sep 17 00:00:00 2001 From: algosketch Date: Tue, 21 Nov 2023 17:41:52 +0900 Subject: [PATCH 060/165] =?UTF-8?q?feat=20:=20=EC=9D=B4=EC=A0=84,=20?= =?UTF-8?q?=EB=8B=A4=EC=9D=8C=20=EB=B2=84=ED=8A=BC=20id=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- android/feature/player/src/main/res/layout/fragment_player.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/android/feature/player/src/main/res/layout/fragment_player.xml b/android/feature/player/src/main/res/layout/fragment_player.xml index ee70b33..53ee751 100644 --- a/android/feature/player/src/main/res/layout/fragment_player.xml +++ b/android/feature/player/src/main/res/layout/fragment_player.xml @@ -129,6 +129,7 @@ app:layout_constraintTop_toBottomOf="@id/tv_current_time" /> Date: Tue, 21 Nov 2023 17:45:01 +0900 Subject: [PATCH 061/165] =?UTF-8?q?chore=20:=20=EC=9D=8C=EC=95=85=20?= =?UTF-8?q?=EC=A0=9C=EB=AA=A9=20=EA=B8=B8=EC=9D=B4=20=EC=A0=9C=ED=95=9C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/src/upload/upload.service.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/server/src/upload/upload.service.ts b/server/src/upload/upload.service.ts index cadd443..3d250a0 100644 --- a/server/src/upload/upload.service.ts +++ b/server/src/upload/upload.service.ts @@ -12,10 +12,11 @@ export class UploadService { this.objectStorage = nCloudConfigService.createObjectStorageOption(); } - private isValidPath(path: string) { + private isValidPath(path: string, fileName: string) { for (let i = 0; i < pathPattern.length; i++) { if (pathPattern[i].test(path)) { - return true; + if (i < 2) return true; + if (i == 2 && fileName.length <= 50) return true; } } @@ -34,7 +35,7 @@ export class UploadService { ext: string, ): Promise { try { - if (!this.isValidPath(keyPath) || !this.isValidExt(ext)) + if (!this.isValidPath(keyPath, fileName) || !this.isValidExt(ext)) throw new HttpException('BAD REQUEST', HTTP_STATUS_CODE.BAD_REQUEST); const url = await this.objectStorage.getSignedUrlPromise('putObject', { From b9aafb050a0002809aefd6cddc8044874a56fd0e Mon Sep 17 00:00:00 2001 From: 2taezeat Date: Tue, 21 Nov 2023 17:26:23 +0900 Subject: [PATCH 062/165] =?UTF-8?q?chore=20:=20ui=20->=20domain=20?= =?UTF-8?q?=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- android/core/ui/build.gradle.kts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/android/core/ui/build.gradle.kts b/android/core/ui/build.gradle.kts index c8d81ba..1a1a69c 100644 --- a/android/core/ui/build.gradle.kts +++ b/android/core/ui/build.gradle.kts @@ -37,4 +37,6 @@ dependencies { api(libs.navigation.fragment.ktx) api(libs.navigation.ui.ktx) + + implementation(project(":core:domain")) } \ No newline at end of file From bd6f7086f8c55c7e913be68f3bc860fee2daaf60 Mon Sep 17 00:00:00 2001 From: 2taezeat Date: Tue, 21 Nov 2023 17:29:46 +0900 Subject: [PATCH 063/165] feat : item_music_vertical UI --- .../core/ui/src/main/res/drawable/ic_more.xml | 4 ++ .../main/res/layout/item_music_vertical.xml | 62 +++++++++++++++++++ .../core/ui/src/main/res/values/dimens.xml | 1 + 3 files changed, 67 insertions(+) create mode 100644 android/core/ui/src/main/res/drawable/ic_more.xml create mode 100644 android/core/ui/src/main/res/layout/item_music_vertical.xml diff --git a/android/core/ui/src/main/res/drawable/ic_more.xml b/android/core/ui/src/main/res/drawable/ic_more.xml new file mode 100644 index 0000000..2fe5b80 --- /dev/null +++ b/android/core/ui/src/main/res/drawable/ic_more.xml @@ -0,0 +1,4 @@ + + + diff --git a/android/core/ui/src/main/res/layout/item_music_vertical.xml b/android/core/ui/src/main/res/layout/item_music_vertical.xml new file mode 100644 index 0000000..1413730 --- /dev/null +++ b/android/core/ui/src/main/res/layout/item_music_vertical.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/core/ui/src/main/res/values/dimens.xml b/android/core/ui/src/main/res/values/dimens.xml index aaac538..62fa497 100644 --- a/android/core/ui/src/main/res/values/dimens.xml +++ b/android/core/ui/src/main/res/values/dimens.xml @@ -9,6 +9,7 @@ 32dp 120dp + 56dp 168dp 50dp From 52df723caa6cce7db65c717ad4d4ab722d48342c Mon Sep 17 00:00:00 2001 From: 2taezeat Date: Tue, 21 Nov 2023 18:07:08 +0900 Subject: [PATCH 064/165] =?UTF-8?q?chore=20:=20=EB=B6=88=20=ED=95=84?= =?UTF-8?q?=EC=9A=94=ED=95=9C=20SampleTest=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../catchytape/core/domain/SampleTest.kt | 20 ------------------- 1 file changed, 20 deletions(-) delete mode 100644 android/core/domain/src/test/java/com/ohdodok/catchytape/core/domain/SampleTest.kt diff --git a/android/core/domain/src/test/java/com/ohdodok/catchytape/core/domain/SampleTest.kt b/android/core/domain/src/test/java/com/ohdodok/catchytape/core/domain/SampleTest.kt deleted file mode 100644 index bcda4eb..0000000 --- a/android/core/domain/src/test/java/com/ohdodok/catchytape/core/domain/SampleTest.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.ohdodok.catchytape.core.domain - -import io.kotest.core.spec.style.FunSpec -import io.kotest.matchers.shouldBe - -class SampleTest : FunSpec({ - - test("test sample 1") { - 1 + 2 shouldBe 3 - } - - test("test sample 2") { - 3 + 4 shouldBe 7 - } - - test("test sample 3") { - 3 + 7 shouldBe 10 - } - -}) \ No newline at end of file From 1262db831a55f627be929820d01adef4839c03a1 Mon Sep 17 00:00:00 2001 From: 2taezeat Date: Tue, 21 Nov 2023 18:07:42 +0900 Subject: [PATCH 065/165] =?UTF-8?q?feat=20:=20MusicAdapter=20=EB=A1=9C=20V?= =?UTF-8?q?ertical,=20Horizontal=20=ED=86=B5=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../catchytape/core/ui/MusicAdapter.kt | 74 +++++++++++++++++++ .../ohdodok/catchytape/core/ui/Orientation.kt | 5 ++ .../catchytape/feature/home/HomeFragment.kt | 6 +- .../feature/home/MusicHorizontalAdapter.kt | 50 ------------- 4 files changed, 82 insertions(+), 53 deletions(-) create mode 100644 android/core/ui/src/main/java/com/ohdodok/catchytape/core/ui/MusicAdapter.kt create mode 100644 android/core/ui/src/main/java/com/ohdodok/catchytape/core/ui/Orientation.kt delete mode 100644 android/feature/home/src/main/java/com/ohdodok/catchytape/feature/home/MusicHorizontalAdapter.kt diff --git a/android/core/ui/src/main/java/com/ohdodok/catchytape/core/ui/MusicAdapter.kt b/android/core/ui/src/main/java/com/ohdodok/catchytape/core/ui/MusicAdapter.kt new file mode 100644 index 0000000..161fc86 --- /dev/null +++ b/android/core/ui/src/main/java/com/ohdodok/catchytape/core/ui/MusicAdapter.kt @@ -0,0 +1,74 @@ +package com.ohdodok.catchytape.core.ui + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import com.ohdodok.catchytape.core.domain.model.Music +import com.ohdodok.catchytape.core.ui.databinding.ItemMusicHorizontalBinding +import com.ohdodok.catchytape.core.ui.databinding.ItemMusicVerticalBinding + + +class MusicAdapter(private val orientation: Orientation) : + ListAdapter(MusicDiffUtil) { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + return when (orientation) { + Orientation.Horizontal -> HorizontalViewHolder.from(parent) + else -> VerticalViewHolder.from(parent) + } + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + when (orientation) { + Orientation.Horizontal -> (holder as HorizontalViewHolder).bind(currentList[position]) + Orientation.Vertical -> (holder as VerticalViewHolder).bind(currentList[position]) + } + } + + + class HorizontalViewHolder private constructor(private val binding: ItemMusicHorizontalBinding) : + RecyclerView.ViewHolder(binding.root) { + + fun bind(item: Music) { + binding.music = item + } + + companion object { + fun from(parent: ViewGroup) = HorizontalViewHolder( + ItemMusicHorizontalBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + ) + } + } + + class VerticalViewHolder private constructor(private val binding: ItemMusicVerticalBinding) : + RecyclerView.ViewHolder(binding.root) { + + fun bind(item: Music) { + binding.music = item + } + + companion object { + fun from(parent: ViewGroup) = VerticalViewHolder( + ItemMusicVerticalBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + ) + } + } +} + +object MusicDiffUtil : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: Music, newItem: Music) = + oldItem.id == newItem.id + + override fun areContentsTheSame(oldItem: Music, newItem: Music) = + oldItem == newItem +} \ No newline at end of file diff --git a/android/core/ui/src/main/java/com/ohdodok/catchytape/core/ui/Orientation.kt b/android/core/ui/src/main/java/com/ohdodok/catchytape/core/ui/Orientation.kt new file mode 100644 index 0000000..191be40 --- /dev/null +++ b/android/core/ui/src/main/java/com/ohdodok/catchytape/core/ui/Orientation.kt @@ -0,0 +1,5 @@ +package com.ohdodok.catchytape.core.ui + +enum class Orientation { + Horizontal, Vertical +} \ No newline at end of file diff --git a/android/feature/home/src/main/java/com/ohdodok/catchytape/feature/home/HomeFragment.kt b/android/feature/home/src/main/java/com/ohdodok/catchytape/feature/home/HomeFragment.kt index 2fff1b5..c2ac9f7 100644 --- a/android/feature/home/src/main/java/com/ohdodok/catchytape/feature/home/HomeFragment.kt +++ b/android/feature/home/src/main/java/com/ohdodok/catchytape/feature/home/HomeFragment.kt @@ -7,6 +7,8 @@ import androidx.fragment.app.viewModels import androidx.navigation.NavDeepLinkRequest import androidx.navigation.fragment.findNavController import com.ohdodok.catchytape.core.ui.BaseFragment +import com.ohdodok.catchytape.core.ui.MusicAdapter +import com.ohdodok.catchytape.core.ui.Orientation import com.ohdodok.catchytape.feature.home.databinding.FragmentHomeBinding import dagger.hilt.android.AndroidEntryPoint @@ -18,9 +20,7 @@ class HomeFragment : BaseFragment(R.layout.fragment_home) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) binding.viewModel = viewModel - binding.rvRecentlyAddedSong.adapter = MusicHorizontalAdapter() - - + binding.rvRecentlyAddedSong.adapter = MusicAdapter(Orientation.Horizontal) binding.ibUpload.setOnClickListener { val request = NavDeepLinkRequest.Builder diff --git a/android/feature/home/src/main/java/com/ohdodok/catchytape/feature/home/MusicHorizontalAdapter.kt b/android/feature/home/src/main/java/com/ohdodok/catchytape/feature/home/MusicHorizontalAdapter.kt deleted file mode 100644 index 56c5ab1..0000000 --- a/android/feature/home/src/main/java/com/ohdodok/catchytape/feature/home/MusicHorizontalAdapter.kt +++ /dev/null @@ -1,50 +0,0 @@ -package com.ohdodok.catchytape.feature.home - -import android.view.LayoutInflater -import android.view.ViewGroup -import androidx.recyclerview.widget.DiffUtil -import androidx.recyclerview.widget.ListAdapter -import androidx.recyclerview.widget.RecyclerView -import com.ohdodok.catchytape.core.domain.model.Music -import com.ohdodok.catchytape.feature.home.databinding.ItemMusicHorizontalBinding - -class MusicHorizontalAdapter : - ListAdapter(MusicDiffUtil) { - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MusicHorizontalViewHolder { - return MusicHorizontalViewHolder.from(parent) - } - - override fun onBindViewHolder(holder: MusicHorizontalViewHolder, position: Int) { - holder.bind(currentList[position]) - } - - class MusicHorizontalViewHolder private constructor( - private val binding: ItemMusicHorizontalBinding - ) : RecyclerView.ViewHolder(binding.root) { - - fun bind(item: Music) { - binding.music = item - } - - companion object { - fun from(parent: ViewGroup): MusicHorizontalViewHolder { - return MusicHorizontalViewHolder( - ItemMusicHorizontalBinding.inflate( - LayoutInflater.from(parent.context), - parent, - false - ) - ) - } - } - } -} - -object MusicDiffUtil : DiffUtil.ItemCallback() { - override fun areItemsTheSame(oldItem: Music, newItem: Music) = - oldItem.id == newItem.id - - override fun areContentsTheSame(oldItem: Music, newItem: Music) = - oldItem == newItem -} \ No newline at end of file From 4d5e212db9d1f0bbab2ba60befc753162ab0c82f Mon Sep 17 00:00:00 2001 From: 2taezeat Date: Tue, 21 Nov 2023 18:08:06 +0900 Subject: [PATCH 066/165] =?UTF-8?q?feat=20:=20submitData=20BindingAdapter?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ohdodok/catchytape/core/ui/BindingAdapter.kt | 13 +++++++++++++ .../src/main/res/layout/item_music_horizontal.xml | 0 2 files changed, 13 insertions(+) create mode 100644 android/core/ui/src/main/java/com/ohdodok/catchytape/core/ui/BindingAdapter.kt rename android/{feature/home => core/ui}/src/main/res/layout/item_music_horizontal.xml (100%) diff --git a/android/core/ui/src/main/java/com/ohdodok/catchytape/core/ui/BindingAdapter.kt b/android/core/ui/src/main/java/com/ohdodok/catchytape/core/ui/BindingAdapter.kt new file mode 100644 index 0000000..10496ac --- /dev/null +++ b/android/core/ui/src/main/java/com/ohdodok/catchytape/core/ui/BindingAdapter.kt @@ -0,0 +1,13 @@ +package com.ohdodok.catchytape.core.ui + +import androidx.databinding.BindingAdapter +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView + + +@BindingAdapter("submitData") +fun RecyclerView.bindItems(items: List) { + val adapter = this.adapter ?: return + val listAdapter: ListAdapter = adapter as ListAdapter + listAdapter.submitList(items) +} \ No newline at end of file diff --git a/android/feature/home/src/main/res/layout/item_music_horizontal.xml b/android/core/ui/src/main/res/layout/item_music_horizontal.xml similarity index 100% rename from android/feature/home/src/main/res/layout/item_music_horizontal.xml rename to android/core/ui/src/main/res/layout/item_music_horizontal.xml From ef94e557306f4114b2ffce302474b4d35535d6a5 Mon Sep 17 00:00:00 2001 From: 2taezeat Date: Tue, 21 Nov 2023 18:24:50 +0900 Subject: [PATCH 067/165] =?UTF-8?q?feat=20:=20dummy=20data=20binding=20?= =?UTF-8?q?=EB=B0=8F=20UI=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/res/layout/item_music_horizontal.xml | 2 ++ .../catchytape/feature/home/HomeViewModel.kt | 15 ++++++++++++--- .../home/src/main/res/layout/fragment_home.xml | 2 ++ 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/android/core/ui/src/main/res/layout/item_music_horizontal.xml b/android/core/ui/src/main/res/layout/item_music_horizontal.xml index fa078e8..05c0fec 100644 --- a/android/core/ui/src/main/res/layout/item_music_horizontal.xml +++ b/android/core/ui/src/main/res/layout/item_music_horizontal.xml @@ -30,6 +30,7 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="@dimen/small" + android:text="@{music.title}" android:textColor="@color/on_surface" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" @@ -42,6 +43,7 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="@dimen/extra_small" + android:text="@{music.artist}" android:textColor="@color/on_surface_variant" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" diff --git a/android/feature/home/src/main/java/com/ohdodok/catchytape/feature/home/HomeViewModel.kt b/android/feature/home/src/main/java/com/ohdodok/catchytape/feature/home/HomeViewModel.kt index 067f8e7..4d9649d 100644 --- a/android/feature/home/src/main/java/com/ohdodok/catchytape/feature/home/HomeViewModel.kt +++ b/android/feature/home/src/main/java/com/ohdodok/catchytape/feature/home/HomeViewModel.kt @@ -2,6 +2,7 @@ package com.ohdodok.catchytape.feature.home import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.ohdodok.catchytape.core.domain.model.Music import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -12,13 +13,21 @@ import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.update data class HomeUiState( - val recentlyUploadedMusics: List = emptyList() + val recentlyUploadedMusics: List = emptyList() ) class HomeViewModel constructor( // todo : DI로 주입하는 코드로 변경 private val getMusicUseCase: GetMusicUseCase = GetMusicUseCase { - flow { emit(listOf()) } + flow { + emit( + listOf( + Music("1", "title1", "artist1", ""), + Music("2", "title2", "artist2", ""), + Music("3", "title3", "artist3", "") + ) // 화면 확인용 Dummy data 입니다. + ) + } }, ) : ViewModel() { @@ -44,5 +53,5 @@ class HomeViewModel constructor( // todo : domain layer로 이동 fun interface GetMusicUseCase { - operator fun invoke(): Flow> + operator fun invoke(): Flow> } diff --git a/android/feature/home/src/main/res/layout/fragment_home.xml b/android/feature/home/src/main/res/layout/fragment_home.xml index 96dcdef..9afb755 100644 --- a/android/feature/home/src/main/res/layout/fragment_home.xml +++ b/android/feature/home/src/main/res/layout/fragment_home.xml @@ -74,11 +74,13 @@ android:id="@+id/rv_recently_added_song" android:layout_width="0dp" android:layout_height="wrap_content" + android:layout_marginTop="@dimen/small" android:orientation="horizontal" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" app:layout_constraintEnd_toEndOf="@id/tv_recently_added_song" app:layout_constraintStart_toStartOf="@id/tv_recently_added_song" app:layout_constraintTop_toBottomOf="@id/tv_recently_added_song" + app:submitData="@{viewModel.uiState.recentlyUploadedMusics}" tools:listitem="@layout/item_music_horizontal" /> From 7bf00dea0c97e75a67185373ccb4e671c2f5352f Mon Sep 17 00:00:00 2001 From: 2taezeat Date: Tue, 21 Nov 2023 18:30:24 +0900 Subject: [PATCH 068/165] =?UTF-8?q?refactor=20:=20else=20=EB=AC=B8=20?= =?UTF-8?q?=EB=8C=80=EC=8B=A0,=20=EB=AA=85=ED=99=95=ED=9E=88=20=ED=83=80?= =?UTF-8?q?=EC=9E=85=20=EB=AA=85=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/ohdodok/catchytape/core/ui/MusicAdapter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/core/ui/src/main/java/com/ohdodok/catchytape/core/ui/MusicAdapter.kt b/android/core/ui/src/main/java/com/ohdodok/catchytape/core/ui/MusicAdapter.kt index 161fc86..3a4be0d 100644 --- a/android/core/ui/src/main/java/com/ohdodok/catchytape/core/ui/MusicAdapter.kt +++ b/android/core/ui/src/main/java/com/ohdodok/catchytape/core/ui/MusicAdapter.kt @@ -16,7 +16,7 @@ class MusicAdapter(private val orientation: Orientation) : override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { return when (orientation) { Orientation.Horizontal -> HorizontalViewHolder.from(parent) - else -> VerticalViewHolder.from(parent) + Orientation.Vertical -> VerticalViewHolder.from(parent) } } From 3e161377ac510659b4a3d7fb379f911e7f2ff64f Mon Sep 17 00:00:00 2001 From: 2taezeat Date: Tue, 21 Nov 2023 23:33:05 +0900 Subject: [PATCH 069/165] =?UTF-8?q?refactor=20:=20=EB=84=A4=EC=9D=B4?= =?UTF-8?q?=EB=B0=8D=20=EA=B0=9C=EC=84=A0=20=EB=B0=8F=20named=20parameter?= =?UTF-8?q?=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/ohdodok/catchytape/core/ui/BindingAdapter.kt | 2 +- .../java/com/ohdodok/catchytape/core/ui/MusicAdapter.kt | 6 +++--- .../com/ohdodok/catchytape/feature/home/HomeFragment.kt | 2 +- android/feature/home/src/main/res/layout/fragment_home.xml | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/android/core/ui/src/main/java/com/ohdodok/catchytape/core/ui/BindingAdapter.kt b/android/core/ui/src/main/java/com/ohdodok/catchytape/core/ui/BindingAdapter.kt index 10496ac..8306400 100644 --- a/android/core/ui/src/main/java/com/ohdodok/catchytape/core/ui/BindingAdapter.kt +++ b/android/core/ui/src/main/java/com/ohdodok/catchytape/core/ui/BindingAdapter.kt @@ -5,7 +5,7 @@ import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView -@BindingAdapter("submitData") +@BindingAdapter("submitList") fun RecyclerView.bindItems(items: List) { val adapter = this.adapter ?: return val listAdapter: ListAdapter = adapter as ListAdapter diff --git a/android/core/ui/src/main/java/com/ohdodok/catchytape/core/ui/MusicAdapter.kt b/android/core/ui/src/main/java/com/ohdodok/catchytape/core/ui/MusicAdapter.kt index 3a4be0d..947b818 100644 --- a/android/core/ui/src/main/java/com/ohdodok/catchytape/core/ui/MusicAdapter.kt +++ b/android/core/ui/src/main/java/com/ohdodok/catchytape/core/ui/MusicAdapter.kt @@ -10,18 +10,18 @@ import com.ohdodok.catchytape.core.ui.databinding.ItemMusicHorizontalBinding import com.ohdodok.catchytape.core.ui.databinding.ItemMusicVerticalBinding -class MusicAdapter(private val orientation: Orientation) : +class MusicAdapter(private val musicItemOrientation: Orientation) : ListAdapter(MusicDiffUtil) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { - return when (orientation) { + return when (musicItemOrientation) { Orientation.Horizontal -> HorizontalViewHolder.from(parent) Orientation.Vertical -> VerticalViewHolder.from(parent) } } override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { - when (orientation) { + when (musicItemOrientation) { Orientation.Horizontal -> (holder as HorizontalViewHolder).bind(currentList[position]) Orientation.Vertical -> (holder as VerticalViewHolder).bind(currentList[position]) } diff --git a/android/feature/home/src/main/java/com/ohdodok/catchytape/feature/home/HomeFragment.kt b/android/feature/home/src/main/java/com/ohdodok/catchytape/feature/home/HomeFragment.kt index c2ac9f7..c490d9b 100644 --- a/android/feature/home/src/main/java/com/ohdodok/catchytape/feature/home/HomeFragment.kt +++ b/android/feature/home/src/main/java/com/ohdodok/catchytape/feature/home/HomeFragment.kt @@ -20,7 +20,7 @@ class HomeFragment : BaseFragment(R.layout.fragment_home) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) binding.viewModel = viewModel - binding.rvRecentlyAddedSong.adapter = MusicAdapter(Orientation.Horizontal) + binding.rvRecentlyAddedSong.adapter = MusicAdapter(musicItemOrientation = Orientation.Horizontal) binding.ibUpload.setOnClickListener { val request = NavDeepLinkRequest.Builder diff --git a/android/feature/home/src/main/res/layout/fragment_home.xml b/android/feature/home/src/main/res/layout/fragment_home.xml index 9afb755..f204355 100644 --- a/android/feature/home/src/main/res/layout/fragment_home.xml +++ b/android/feature/home/src/main/res/layout/fragment_home.xml @@ -80,7 +80,7 @@ app:layout_constraintEnd_toEndOf="@id/tv_recently_added_song" app:layout_constraintStart_toStartOf="@id/tv_recently_added_song" app:layout_constraintTop_toBottomOf="@id/tv_recently_added_song" - app:submitData="@{viewModel.uiState.recentlyUploadedMusics}" + app:submitList="@{viewModel.uiState.recentlyUploadedMusics}" tools:listitem="@layout/item_music_horizontal" /> From ec85633dcdbaafbc998adfdc73e9d19a03362597 Mon Sep 17 00:00:00 2001 From: 2taezeat Date: Tue, 21 Nov 2023 23:44:10 +0900 Subject: [PATCH 070/165] =?UTF-8?q?feat=20:=20textView=20=ED=95=9C?= =?UTF-8?q?=EC=A4=84=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/src/main/res/layout/item_music_horizontal.xml | 4 ++++ .../ui/src/main/res/layout/item_music_vertical.xml | 11 +++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/android/core/ui/src/main/res/layout/item_music_horizontal.xml b/android/core/ui/src/main/res/layout/item_music_horizontal.xml index 05c0fec..3f4bf6e 100644 --- a/android/core/ui/src/main/res/layout/item_music_horizontal.xml +++ b/android/core/ui/src/main/res/layout/item_music_horizontal.xml @@ -30,6 +30,8 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="@dimen/small" + android:ellipsize="end" + android:maxLines="1" android:text="@{music.title}" android:textColor="@color/on_surface" app:layout_constraintEnd_toEndOf="parent" @@ -43,6 +45,8 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="@dimen/extra_small" + android:ellipsize="end" + android:maxLines="1" android:text="@{music.artist}" android:textColor="@color/on_surface_variant" app:layout_constraintEnd_toEndOf="parent" diff --git a/android/core/ui/src/main/res/layout/item_music_vertical.xml b/android/core/ui/src/main/res/layout/item_music_vertical.xml index 1413730..7c60559 100644 --- a/android/core/ui/src/main/res/layout/item_music_vertical.xml +++ b/android/core/ui/src/main/res/layout/item_music_vertical.xml @@ -30,8 +30,11 @@ android:layout_height="wrap_content" android:layout_marginStart="@dimen/medium" android:layout_marginTop="@dimen/small" + android:ellipsize="end" + android:maxLines="1" android:text="@{music.title}" android:textColor="@color/on_surface" + app:layout_constraintEnd_toStartOf="@+id/ib_more" app:layout_constraintStart_toEndOf="@+id/iv_thumbnail" app:layout_constraintTop_toTopOf="parent" tools:text="어떨것같애" /> @@ -43,15 +46,19 @@ android:layout_height="wrap_content" android:layout_marginStart="@dimen/medium" android:text="@{music.artist}" + android:ellipsize="end" + android:maxLines="1" + app:layout_constraintEnd_toStartOf="@+id/ib_more" android:textColor="@color/on_surface_variant" app:layout_constraintStart_toEndOf="@+id/iv_thumbnail" app:layout_constraintTop_toBottomOf="@id/tv_title" tools:text="MEENOI" /> - Date: Wed, 22 Nov 2023 00:07:19 +0900 Subject: [PATCH 071/165] =?UTF-8?q?fix=20:=20light=20=EC=BB=AC=EB=9F=AC=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD,=20=EC=A0=80=EC=9E=A5=20=EB=B2=84=ED=8A=BC?= =?UTF-8?q?=EC=9D=98=20text,=20resource=20=EB=A7=81=ED=81=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- android/core/ui/src/main/res/values/colors.xml | 2 +- android/feature/player/src/main/res/layout/fragment_player.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/android/core/ui/src/main/res/values/colors.xml b/android/core/ui/src/main/res/values/colors.xml index c8cb6d0..b552fb4 100644 --- a/android/core/ui/src/main/res/values/colors.xml +++ b/android/core/ui/src/main/res/values/colors.xml @@ -9,7 +9,7 @@ #FFBB2649 #FFF8E9ED #FFFCF4F6 - #FF212121 + #FFFCF4F6 #FF757575 #FF212121 #FFB00020 diff --git a/android/feature/player/src/main/res/layout/fragment_player.xml b/android/feature/player/src/main/res/layout/fragment_player.xml index 53ee751..76b7ca1 100644 --- a/android/feature/player/src/main/res/layout/fragment_player.xml +++ b/android/feature/player/src/main/res/layout/fragment_player.xml @@ -72,7 +72,7 @@ android:minWidth="0dp" android:paddingHorizontal="12dp" android:paddingVertical="0dp" - android:text="저장" + android:text="@string/save" android:textColor="@color/on_surface" app:icon="@drawable/ic_playlist" app:iconTint="@color/on_surface" From c52def20b66c9ee522654b433dc55416e3bcc3d0 Mon Sep 17 00:00:00 2001 From: algosketch Date: Wed, 22 Nov 2023 00:12:44 +0900 Subject: [PATCH 072/165] =?UTF-8?q?feat=20:=20toolbar=20=EB=92=A4=EB=A1=9C?= =?UTF-8?q?=EA=B0=80=EA=B8=B0=20=EB=B2=84=ED=8A=BC=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/ohdodok/catchytape/feature/player/PlayerFragment.kt | 2 ++ android/feature/player/src/main/res/layout/fragment_player.xml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/android/feature/player/src/main/java/com/ohdodok/catchytape/feature/player/PlayerFragment.kt b/android/feature/player/src/main/java/com/ohdodok/catchytape/feature/player/PlayerFragment.kt index 24326ca..7f7d9e2 100644 --- a/android/feature/player/src/main/java/com/ohdodok/catchytape/feature/player/PlayerFragment.kt +++ b/android/feature/player/src/main/java/com/ohdodok/catchytape/feature/player/PlayerFragment.kt @@ -12,5 +12,7 @@ class PlayerFragment : BaseFragment(R.layout.fragment_pla override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) binding.viewModel = viewModel + + setupBackStack(binding.tbPlayer) } } \ No newline at end of file diff --git a/android/feature/player/src/main/res/layout/fragment_player.xml b/android/feature/player/src/main/res/layout/fragment_player.xml index 76b7ca1..42eb729 100644 --- a/android/feature/player/src/main/res/layout/fragment_player.xml +++ b/android/feature/player/src/main/res/layout/fragment_player.xml @@ -14,7 +14,7 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - Date: Wed, 22 Nov 2023 00:24:24 +0900 Subject: [PATCH 073/165] =?UTF-8?q?feat=20:=20upload=20api=20=EC=A0=95?= =?UTF-8?q?=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../catchytape/core/data/api/MusicApi.kt | 8 ++++++++ .../catchytape/core/data/api/UploadApi.kt | 20 +++++++++++++++++++ .../catchytape/core/data/di/ApiModule.kt | 7 +++++++ .../core/data/model/MusicRequest.kt | 7 +++++++ .../catchytape/core/data/model/UrlResponse.kt | 5 +++++ 5 files changed, 47 insertions(+) create mode 100644 android/core/data/src/main/java/com/ohdodok/catchytape/core/data/api/UploadApi.kt create mode 100644 android/core/data/src/main/java/com/ohdodok/catchytape/core/data/model/MusicRequest.kt create mode 100644 android/core/data/src/main/java/com/ohdodok/catchytape/core/data/model/UrlResponse.kt diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/api/MusicApi.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/api/MusicApi.kt index 83c712b..f93f719 100644 --- a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/api/MusicApi.kt +++ b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/api/MusicApi.kt @@ -1,12 +1,20 @@ package com.ohdodok.catchytape.core.data.api import com.ohdodok.catchytape.core.data.model.MusicGenresResponse +import com.ohdodok.catchytape.core.data.model.MusicRequest import retrofit2.Response +import retrofit2.http.Body import retrofit2.http.GET +import retrofit2.http.POST interface MusicApi { @GET("musics/genres") suspend fun getGenres(): Response + @POST("musics") + suspend fun postMusic( + @Body music: MusicRequest + ): Response + } \ No newline at end of file diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/api/UploadApi.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/api/UploadApi.kt new file mode 100644 index 0000000..902ee7f --- /dev/null +++ b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/api/UploadApi.kt @@ -0,0 +1,20 @@ +package com.ohdodok.catchytape.core.data.api + +import com.ohdodok.catchytape.core.data.model.UrlResponse +import retrofit2.Response +import retrofit2.http.Headers +import retrofit2.http.POST + +interface UploadApi { + + @POST("upload/music") + @Headers("Content-Type: audio/mpeg") + suspend fun uploadMusic( + ): Response + + @POST("upload/image") + @Headers("Content-Type: image/png") + suspend fun uploadImage( + ): Response + +} \ No newline at end of file diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/di/ApiModule.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/di/ApiModule.kt index 9d2c873..7ad8229 100644 --- a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/di/ApiModule.kt +++ b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/di/ApiModule.kt @@ -1,6 +1,7 @@ package com.ohdodok.catchytape.core.data.di import com.ohdodok.catchytape.core.data.api.MusicApi +import com.ohdodok.catchytape.core.data.api.UploadApi import com.ohdodok.catchytape.core.data.api.UserApi import dagger.Module import dagger.Provides @@ -24,4 +25,10 @@ object ApiModule { fun provideMusicApi(retrofit: Retrofit): MusicApi { return retrofit.create(MusicApi::class.java) } + + @Provides + @Singleton + fun provideUploadApi(retrofit: Retrofit): UploadApi { + return retrofit.create(UploadApi::class.java) + } } \ No newline at end of file diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/model/MusicRequest.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/model/MusicRequest.kt new file mode 100644 index 0000000..ed724fe --- /dev/null +++ b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/model/MusicRequest.kt @@ -0,0 +1,7 @@ +package com.ohdodok.catchytape.core.data.model + +data class MusicRequest ( + val title: String, + val cover: String, + val file: String +) \ No newline at end of file diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/model/UrlResponse.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/model/UrlResponse.kt new file mode 100644 index 0000000..c359d14 --- /dev/null +++ b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/model/UrlResponse.kt @@ -0,0 +1,5 @@ +package com.ohdodok.catchytape.core.data.model + +data class UrlResponse( + val url: String +) \ No newline at end of file From d08c1a5cb033279a0185d7ccd4bda5a93f98da58 Mon Sep 17 00:00:00 2001 From: Kim Hyung Un Date: Wed, 22 Nov 2023 02:51:45 +0900 Subject: [PATCH 074/165] =?UTF-8?q?=08CD=20workflow=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * github actions 를 이용해서 구현 * server 측 코드의 변경이 있는 PR 이 merge 됐을 때 작동하는 workflow 작성 --- .github/workflows/server-cd.yml | 79 +++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 .github/workflows/server-cd.yml diff --git a/.github/workflows/server-cd.yml b/.github/workflows/server-cd.yml new file mode 100644 index 0000000..ccfbc59 --- /dev/null +++ b/.github/workflows/server-cd.yml @@ -0,0 +1,79 @@ +name: Server CD + +on: + pull_request: + branches: [ "develop", "main" ] + paths: + - "server/**" + types: + - closed + +jobs: + deploy: + runs-on: ubuntu-20.04 + + strategy: + matrix: + node-version: [20.x] + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Set up Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + cache: 'npm' + cache-dependency-path: '**/package-lock.json' + + - name: Install depenencies + run: | + cd server + npm install + + - name: Create prod.env file + env: + DB_HOST_IP: ${{ secrets.SERVER_ENV_DB_HOST_IP }} + DB_PORT: ${{ secrets.SERVER_ENV_DB_PORT }} + DB_USER_NAME: ${{ secrets.SERVER_ENV_DB_USER_NAME }} + DB_PASSWORD: ${{ secrets.SERVER_ENV_DB_PASSWORD }} + DB_DATABASE_NAME: ${{ secrets.SERVER_ENV_DB_DATABASE_NAME }} + ACCESS_ID: ${{ secrets.SERVER_ENV_ACCESS_ID }} + SECRET_ACCESS_KEY: ${{ secrets.SERVER_ENV_SECRET_ACCESS_KEY }} + JWT_SECRET_KEY: ${{ secrets.SERVER_ENV_JWT_SECRET_KEY }} + run: | + cd server + touch prod.env + echo "DB_HOST_IP=$DB_HOST_IP" >> prod.env + echo "DB_PORT=$DB_PORT" >> prod.env + echo "DB_USER_NAME=$DB_USER_NAME" >> prod.env + echo "DB_PASSWORD=$DB_PASSWORD" >> prod.env + echo "DB_DATABASE_NAME=$DB_DATABASE_NAME" >> prod.env + echo "ACCESS_ID=$ACCESS_ID" >> prod.env + echo "SECRET_ACCESS_KEY=$SECRET_ACCESS_KEY" >> prod.env + echo "JWT_SECRET_KEY=$JWT_SECRET_KEY" >> prod.env + + - name: Build Docker image + run: docker build --platform linux/amd64 ./server -t ${{ secrets.NCP_REGISTRY }}/catchy-tape:latest + + - name: Login NCP container registry + run: docker login ${{ secrets.NCP_REGISTRY }} -u ${{ secrets.NCP_DOCKER_ACCESS_KEY_ID }} -p ${{ secrets.NCP_DOCKER_SECRET_KEY }} + + - name: Push Docker image to registry + run: docker push ${{ secrets.NCP_REGISTRY }}/catchy-tape:latest + + - name: SSH into Server + uses: appleboy/ssh-action@master + with: + host: ${{ secrets.SERVER_SSH_HOST }} + username: ${{ secrets.SERVER_SSH_USER }} + password: ${{ secrets.SERVER_SSH_PASSWORD }} + port: ${{ secrets.SERVER_SSH_PORT }} + script: | + docker login ${{ secrets.NCP_REGISTRY }} -u ${{ secrets.NCP_DOCKER_ACCESS_KEY_ID }} -p ${{ secrets.NCP_DOCKER_SECRET_KEY }} + docker pull ${{ secrets.NCP_REGISTRY }}/catchy-tape:latest + docker stop catchy-tape-latest + docker rm catchy-tape-latest + docker run -d -p 3000:3000 --name catchy-tape-latest ${{ secrets.NCP_REGISTRY }}/catchy-tape:latest + curl -X POST -H 'Content-type: application/json' --data '{"text":"서버 배포 성공!"}' ${{ secrets.SLACK_WEBHOOK_URL }} From 5aac10c221adc2d7a7764ceb6069e83442f2ed8f Mon Sep 17 00:00:00 2001 From: 2taezeat Date: Wed, 22 Nov 2023 10:16:29 +0900 Subject: [PATCH 075/165] =?UTF-8?q?refactor=20:=20onBindViewHolder=20holde?= =?UTF-8?q?r=20=ED=83=80=EC=9E=85=EC=9C=BC=EB=A1=9C=20=EB=B6=84=EA=B8=B0?= =?UTF-8?q?=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/ohdodok/catchytape/core/ui/MusicAdapter.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/android/core/ui/src/main/java/com/ohdodok/catchytape/core/ui/MusicAdapter.kt b/android/core/ui/src/main/java/com/ohdodok/catchytape/core/ui/MusicAdapter.kt index 947b818..7944224 100644 --- a/android/core/ui/src/main/java/com/ohdodok/catchytape/core/ui/MusicAdapter.kt +++ b/android/core/ui/src/main/java/com/ohdodok/catchytape/core/ui/MusicAdapter.kt @@ -21,9 +21,9 @@ class MusicAdapter(private val musicItemOrientation: Orientation) : } override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { - when (musicItemOrientation) { - Orientation.Horizontal -> (holder as HorizontalViewHolder).bind(currentList[position]) - Orientation.Vertical -> (holder as VerticalViewHolder).bind(currentList[position]) + when (holder) { + is HorizontalViewHolder -> holder.bind(currentList[position]) + is VerticalViewHolder -> holder.bind(currentList[position]) } } From 395c5f40127e544070910f56448da9a93babadc2 Mon Sep 17 00:00:00 2001 From: 2taezeat Date: Wed, 22 Nov 2023 10:17:05 +0900 Subject: [PATCH 076/165] =?UTF-8?q?refactor=20:=20UI=20=EA=B0=9C=EC=84=A0?= =?UTF-8?q?=20(dp=EA=B0=92=20=EB=B3=80=EA=B2=BD)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/src/main/res/layout/item_music_vertical.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/android/core/ui/src/main/res/layout/item_music_vertical.xml b/android/core/ui/src/main/res/layout/item_music_vertical.xml index 7c60559..5b11df3 100644 --- a/android/core/ui/src/main/res/layout/item_music_vertical.xml +++ b/android/core/ui/src/main/res/layout/item_music_vertical.xml @@ -44,20 +44,20 @@ style="@style/BodySmall" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginStart="@dimen/medium" - android:text="@{music.artist}" android:ellipsize="end" android:maxLines="1" - app:layout_constraintEnd_toStartOf="@+id/ib_more" + android:text="@{music.artist}" android:textColor="@color/on_surface_variant" + app:layout_constraintEnd_toStartOf="@+id/ib_more" app:layout_constraintStart_toEndOf="@+id/iv_thumbnail" + app:layout_constraintStart_toStartOf="@+id/tv_title" app:layout_constraintTop_toBottomOf="@id/tv_title" tools:text="MEENOI" /> Date: Wed, 22 Nov 2023 13:25:12 +0900 Subject: [PATCH 077/165] =?UTF-8?q?feat=20:=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=97=85=EB=A1=9C=EB=93=9C=20=EB=B2=84=ED=8A=BC=20UI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/upload/UploadFragment.kt | 15 +++++++++------ .../feature/upload/UploadViewModel.kt | 6 ++++-- .../src/main/res/layout/fragment_upload.xml | 19 ++++++++++++++++--- .../upload/src/main/res/values/strings.xml | 4 ++++ 4 files changed, 33 insertions(+), 11 deletions(-) create mode 100644 android/feature/upload/src/main/res/values/strings.xml diff --git a/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadFragment.kt b/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadFragment.kt index 2af7630..b2e31bf 100644 --- a/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadFragment.kt +++ b/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadFragment.kt @@ -5,7 +5,6 @@ import android.view.View import androidx.activity.result.PickVisualMediaRequest import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia -import androidx.core.net.toFile import androidx.fragment.app.viewModels import com.ohdodok.catchytape.catchytape.upload.R import com.ohdodok.catchytape.catchytape.upload.databinding.FragmentUploadBinding @@ -18,25 +17,29 @@ class UploadFragment : BaseFragment(R.layout.fragment_upl private val imagePickerLauncher = registerForActivityResult(PickVisualMedia()) { uri -> if (uri == null) return@registerForActivityResult - - viewModel.uploadImage(uri.toFile()) + viewModel.uploadImage(uri) } private val filePickerLauncher = registerForActivityResult(ActivityResultContracts.GetContent()) { uri -> if (uri == null) return@registerForActivityResult - - viewModel.uploadAudio(uri.toFile()) + viewModel.uploadAudio(uri) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) binding.viewModel = viewModel - + setUpFileBtn() setupBackStack(binding.tbUpload) setupSelectThumbnailImage() } + private fun setUpFileBtn() { + binding.btnFile.setOnClickListener { + filePickerLauncher.launch("audio/*") + } + } + private fun setupSelectThumbnailImage() { binding.cvUploadThumbnail.setOnClickListener { imagePickerLauncher.launch(PickVisualMediaRequest(PickVisualMedia.ImageOnly)) diff --git a/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadViewModel.kt b/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadViewModel.kt index 2e600c7..2a031c4 100644 --- a/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadViewModel.kt +++ b/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadViewModel.kt @@ -1,5 +1,6 @@ package com.ohdodok.catchytape.feature.upload +import android.net.Uri import androidx.lifecycle.ViewModel import dagger.hilt.android.lifecycle.HiltViewModel import java.io.File @@ -48,12 +49,13 @@ class UploadViewModel @Inject constructor( }.launchIn(viewModelScope) } - fun uploadImage(imageFile: File) { + fun uploadImage(imageUri: Uri) { // todo : image 파일을 업로드 한다. // todo : 반환 값을 uploadedImage에 저장한다. } - fun uploadAudio(audioFile: File) { + fun uploadAudio(audioUri: Uri) { // todo : audio 파일을 업로드 한다. + val file = audioUri.path?.let { File(it) } } } \ No newline at end of file diff --git a/android/feature/upload/src/main/res/layout/fragment_upload.xml b/android/feature/upload/src/main/res/layout/fragment_upload.xml index 762b7ed..dd19b5d 100644 --- a/android/feature/upload/src/main/res/layout/fragment_upload.xml +++ b/android/feature/upload/src/main/res/layout/fragment_upload.xml @@ -68,18 +68,31 @@ + + + app:layout_constraintTop_toBottomOf="@id/btn_file"> + + 파일 업로드 + \ No newline at end of file From 85d82e13d932df4f8742ca83920c9360a23d0f73 Mon Sep 17 00:00:00 2001 From: youlalala Date: Wed, 22 Nov 2023 13:44:33 +0900 Subject: [PATCH 078/165] =?UTF-8?q?refactor=20:=20=EC=9E=A5=EB=A5=B4=20?= =?UTF-8?q?=EC=84=A0=ED=83=9D=20=EB=A1=9C=EC=A7=81=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../catchytape/feature/upload/BindingAdapter.kt | 6 ------ .../catchytape/feature/upload/UploadViewModel.kt | 15 +++++---------- .../src/main/res/layout/fragment_upload.xml | 2 +- 3 files changed, 6 insertions(+), 17 deletions(-) diff --git a/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/BindingAdapter.kt b/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/BindingAdapter.kt index 4af0ce0..8489ec3 100644 --- a/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/BindingAdapter.kt +++ b/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/BindingAdapter.kt @@ -5,12 +5,6 @@ import android.widget.ArrayAdapter import android.widget.AutoCompleteTextView import androidx.databinding.BindingAdapter - -@BindingAdapter("changeSelectedPosition") -fun AutoCompleteTextView.bindPosition(onChange: (Int) -> Unit) { - setOnItemClickListener { _, _, position, _ -> onChange(position) } -} - @BindingAdapter("list") fun AutoCompleteTextView.setAdapter(list: List) { val adapter = ArrayAdapter(this.context, R.layout.simple_list_item_1, list) diff --git a/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadViewModel.kt b/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadViewModel.kt index 2a031c4..76e7a3c 100644 --- a/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadViewModel.kt +++ b/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadViewModel.kt @@ -21,21 +21,16 @@ class UploadViewModel @Inject constructor( private val getMusicGenresUseCase: GetMusicGenresUseCase ) : ViewModel() { - private var uploadedImage: String? = null - + private var uploadedImage: Uri? = null + private var uploadedAudio: Uri? = null val uploadedMusicTitle = MutableStateFlow("") - - private val _uploadedMusicGenrePosition = MutableStateFlow(-1) - val uploadedMusicGenrePosition = _uploadedMusicGenrePosition.asStateFlow() + val uploadedMusicGenre= MutableStateFlow("") val isUploadEnable: StateFlow = - combine(uploadedMusicTitle, uploadedMusicGenrePosition) { title, genrePosition -> - title.isNotBlank() && genrePosition != -1 + combine(uploadedMusicTitle, uploadedMusicGenre) { title, genrePosition -> + title.isNotBlank() && genrePosition.isNotBlank() }.stateIn(viewModelScope, SharingStarted.Eagerly, false) - val onChangePosition: (Int) -> Unit = - { position: Int -> _uploadedMusicGenrePosition.value = position } - private val _musicGenres: MutableStateFlow> = MutableStateFlow(emptyList()) val musicGenres = _musicGenres.asStateFlow() diff --git a/android/feature/upload/src/main/res/layout/fragment_upload.xml b/android/feature/upload/src/main/res/layout/fragment_upload.xml index dd19b5d..e0169a2 100644 --- a/android/feature/upload/src/main/res/layout/fragment_upload.xml +++ b/android/feature/upload/src/main/res/layout/fragment_upload.xml @@ -119,7 +119,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:inputType="none" - app:changeSelectedPosition="@{viewModel.onChangePosition}" + android:text="@={viewModel.uploadedMusicGenre}" app:list="@{viewModel.musicGenres}" /> From e1b51f39cb8f65e9da333d0b65eb14835b567c3e Mon Sep 17 00:00:00 2001 From: youlalala Date: Wed, 22 Nov 2023 14:16:03 +0900 Subject: [PATCH 079/165] =?UTF-8?q?feat=20:=20=ED=8C=8C=EC=9D=BC=EB=AA=85?= =?UTF-8?q?=20=EA=B0=80=EC=A0=B8=EC=98=A4=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../catchytape/feature/upload/UploadFragment.kt | 13 +++++++++++++ .../catchytape/feature/upload/UploadViewModel.kt | 5 +++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadFragment.kt b/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadFragment.kt index b2e31bf..3253885 100644 --- a/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadFragment.kt +++ b/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadFragment.kt @@ -1,6 +1,8 @@ package com.ohdodok.catchytape.feature.upload +import android.net.Uri import android.os.Bundle +import android.provider.OpenableColumns import android.view.View import androidx.activity.result.PickVisualMediaRequest import androidx.activity.result.contract.ActivityResultContracts @@ -23,6 +25,7 @@ class UploadFragment : BaseFragment(R.layout.fragment_upl private val filePickerLauncher = registerForActivityResult(ActivityResultContracts.GetContent()) { uri -> if (uri == null) return@registerForActivityResult + binding.btnFile.text = getFileName(uri) viewModel.uploadAudio(uri) } @@ -45,4 +48,14 @@ class UploadFragment : BaseFragment(R.layout.fragment_upl imagePickerLauncher.launch(PickVisualMediaRequest(PickVisualMedia.ImageOnly)) } } + + private fun getFileName(uri: Uri): String? { + val contentResolver = requireContext().contentResolver + contentResolver.query(uri, null, null, null, null)?.use { cursor -> + val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME) + cursor.moveToFirst() + return cursor.getString(nameIndex) + } + return null + } } \ No newline at end of file diff --git a/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadViewModel.kt b/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadViewModel.kt index 76e7a3c..e8360c6 100644 --- a/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadViewModel.kt +++ b/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadViewModel.kt @@ -21,8 +21,8 @@ class UploadViewModel @Inject constructor( private val getMusicGenresUseCase: GetMusicGenresUseCase ) : ViewModel() { - private var uploadedImage: Uri? = null - private var uploadedAudio: Uri? = null +// private var uploadedImage: Uri? = null +// private var uploadedAudio: Uri? = null val uploadedMusicTitle = MutableStateFlow("") val uploadedMusicGenre= MutableStateFlow("") @@ -53,4 +53,5 @@ class UploadViewModel @Inject constructor( // todo : audio 파일을 업로드 한다. val file = audioUri.path?.let { File(it) } } + } \ No newline at end of file From c600fe4cb500f5aac7c032bb3e4a3d31c0591059 Mon Sep 17 00:00:00 2001 From: youlalala Date: Wed, 22 Nov 2023 14:29:59 +0900 Subject: [PATCH 080/165] =?UTF-8?q?chore=20:=20glide=20library=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- android/core/ui/build.gradle.kts | 2 ++ .../ohdodok/catchytape/core/ui/BindingAdapters.kt | 12 ++++++++++++ android/gradle/libs.versions.toml | 2 ++ 3 files changed, 16 insertions(+) create mode 100644 android/core/ui/src/main/java/com/ohdodok/catchytape/core/ui/BindingAdapters.kt diff --git a/android/core/ui/build.gradle.kts b/android/core/ui/build.gradle.kts index c8d81ba..f08a30f 100644 --- a/android/core/ui/build.gradle.kts +++ b/android/core/ui/build.gradle.kts @@ -37,4 +37,6 @@ dependencies { api(libs.navigation.fragment.ktx) api(libs.navigation.ui.ktx) + + implementation(libs.glide) } \ No newline at end of file diff --git a/android/core/ui/src/main/java/com/ohdodok/catchytape/core/ui/BindingAdapters.kt b/android/core/ui/src/main/java/com/ohdodok/catchytape/core/ui/BindingAdapters.kt new file mode 100644 index 0000000..fe02a20 --- /dev/null +++ b/android/core/ui/src/main/java/com/ohdodok/catchytape/core/ui/BindingAdapters.kt @@ -0,0 +1,12 @@ +package com.ohdodok.catchytape.core.ui + +import android.widget.ImageView +import androidx.databinding.BindingAdapter +import com.bumptech.glide.Glide + +@BindingAdapter("imageUrl") +fun ImageView.bindImg(url: String) { + Glide.with(this.context) + .load(url) + .into(this) +} \ No newline at end of file diff --git a/android/gradle/libs.versions.toml b/android/gradle/libs.versions.toml index 32cc54c..96a1935 100644 --- a/android/gradle/libs.versions.toml +++ b/android/gradle/libs.versions.toml @@ -22,6 +22,7 @@ kotlinx-serialization = "1.6.0" kotlinx-serialization-converter = "1.0.0" coroutines = "1.3.5" +glide = "4.15.0" timber = "5.0.1" [libraries] @@ -53,6 +54,7 @@ kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serializa kotlinx-serialization-converter = { module = "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter", version.ref = "kotlinx-serialization-converter" } coroutines = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "coroutines" } +glide = { group = "com.github.bumptech.glide", name = "glide", version.ref = "glide" } timber = { module = "com.jakewharton.timber:timber", version.ref = "timber" } [plugins] From 48abf4349fac054bfebc26cf6cf6975ba8330af6 Mon Sep 17 00:00:00 2001 From: youlalala Date: Wed, 22 Nov 2023 14:38:35 +0900 Subject: [PATCH 081/165] =?UTF-8?q?feat=20:=20progress=20indicator=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/src/main/res/drawable/ic_camera.xml | 2 +- .../src/main/res/layout/fragment_upload.xml | 18 ++++++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/android/core/ui/src/main/res/drawable/ic_camera.xml b/android/core/ui/src/main/res/drawable/ic_camera.xml index 364476e..5a798d6 100644 --- a/android/core/ui/src/main/res/drawable/ic_camera.xml +++ b/android/core/ui/src/main/res/drawable/ic_camera.xml @@ -5,5 +5,5 @@ android:viewportHeight="24"> + android:fillColor="@color/black"/> diff --git a/android/feature/upload/src/main/res/layout/fragment_upload.xml b/android/feature/upload/src/main/res/layout/fragment_upload.xml index e0169a2..9b54ffc 100644 --- a/android/feature/upload/src/main/res/layout/fragment_upload.xml +++ b/android/feature/upload/src/main/res/layout/fragment_upload.xml @@ -34,9 +34,16 @@ android:enabled="@{viewModel.isUploadEnable}" android:text="@string/complete" /> - + + + app:layout_constraintTop_toBottomOf="@id/progress_upload"> @@ -124,6 +131,5 @@ - \ No newline at end of file From 616f5a2f48e8128f8235d0b2aa1b60ee58c9c1bf Mon Sep 17 00:00:00 2001 From: youlalala Date: Wed, 22 Nov 2023 14:59:13 +0900 Subject: [PATCH 082/165] =?UTF-8?q?feat=20:=20=EC=99=84=EB=A3=8C=20?= =?UTF-8?q?=EB=B2=84=ED=8A=BC=20=ED=99=9C=EC=84=B1=ED=99=94=20=EC=9C=A0?= =?UTF-8?q?=EB=AC=B4=20=EC=A1=B0=EA=B1=B4=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/upload/UploadViewModel.kt | 19 +++++++++++++------ .../src/main/res/layout/fragment_upload.xml | 5 +++-- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadViewModel.kt b/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadViewModel.kt index e8360c6..aa055dd 100644 --- a/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadViewModel.kt +++ b/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadViewModel.kt @@ -21,14 +21,21 @@ class UploadViewModel @Inject constructor( private val getMusicGenresUseCase: GetMusicGenresUseCase ) : ViewModel() { -// private var uploadedImage: Uri? = null -// private var uploadedAudio: Uri? = null - val uploadedMusicTitle = MutableStateFlow("") - val uploadedMusicGenre= MutableStateFlow("") + val musicTitle = MutableStateFlow("") + val musicGenre = MutableStateFlow("") + + private val _thumbnailUrl: MutableStateFlow = MutableStateFlow("") + val thumbnailUrl = _thumbnailUrl.asStateFlow() + + private val _audioUrl: MutableStateFlow = MutableStateFlow("") + val audioUrl = _audioUrl.asStateFlow() val isUploadEnable: StateFlow = - combine(uploadedMusicTitle, uploadedMusicGenre) { title, genrePosition -> - title.isNotBlank() && genrePosition.isNotBlank() + combine(musicTitle, musicGenre, thumbnailUrl, audioUrl) { title, genre, thumbnail, audio -> + title.isNotBlank() + && genre.isNotBlank() + && thumbnail.isNotBlank() + && audio.isNotBlank() }.stateIn(viewModelScope, SharingStarted.Eagerly, false) private val _musicGenres: MutableStateFlow> = MutableStateFlow(emptyList()) diff --git a/android/feature/upload/src/main/res/layout/fragment_upload.xml b/android/feature/upload/src/main/res/layout/fragment_upload.xml index 9b54ffc..59aadd7 100644 --- a/android/feature/upload/src/main/res/layout/fragment_upload.xml +++ b/android/feature/upload/src/main/res/layout/fragment_upload.xml @@ -63,6 +63,7 @@ android:layout_height="match_parent" android:layout_gravity="center" android:background="@color/white" + imageUrl="@{viewModel.thumbnailUrl}" android:importantForAccessibility="no" /> + android:text="@={viewModel.musicTitle}" /> @@ -126,7 +127,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:inputType="none" - android:text="@={viewModel.uploadedMusicGenre}" + android:text="@={viewModel.musicGenre}" app:list="@{viewModel.musicGenres}" /> From 634e41e494384cc5539262da0d42843175cb7ff2 Mon Sep 17 00:00:00 2001 From: youlalala Date: Wed, 22 Nov 2023 16:23:50 +0900 Subject: [PATCH 083/165] =?UTF-8?q?feat=20:=20progress=20bar=20visibility?= =?UTF-8?q?=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/domain/usecase/GetFileUrlUseCase.kt | 18 +++++++++++ .../feature/upload/UploadViewModel.kt | 32 +++++++++++++++++-- .../src/main/res/layout/fragment_upload.xml | 10 ++++-- 3 files changed, 54 insertions(+), 6 deletions(-) create mode 100644 android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/GetFileUrlUseCase.kt diff --git a/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/GetFileUrlUseCase.kt b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/GetFileUrlUseCase.kt new file mode 100644 index 0000000..95d55f6 --- /dev/null +++ b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/GetFileUrlUseCase.kt @@ -0,0 +1,18 @@ +package com.ohdodok.catchytape.core.domain.usecase + +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import java.io.File +import javax.inject.Inject + +class GetFileUrlUseCase @Inject constructor( + +) { + operator fun invoke(file: File): Flow = flow { + // todo : file을 업로드 하고 url을 받아온다. + // todo : 임시 방편 + delay(5000) + emit("https://kr.object.ncloudstorage.com/catchy-tape-bucket2/music/2/%EC%9D%B4%EB%85%B8%EB%9E%98.mp3") + } +} \ No newline at end of file diff --git a/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadViewModel.kt b/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadViewModel.kt index aa055dd..232ef88 100644 --- a/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadViewModel.kt +++ b/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadViewModel.kt @@ -6,19 +6,23 @@ import dagger.hilt.android.lifecycle.HiltViewModel import java.io.File import javax.inject.Inject import androidx.lifecycle.viewModelScope +import com.ohdodok.catchytape.core.domain.usecase.GetFileUrlUseCase import com.ohdodok.catchytape.core.domain.usecase.GetMusicGenresUseCase import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn @HiltViewModel class UploadViewModel @Inject constructor( - private val getMusicGenresUseCase: GetMusicGenresUseCase + private val getMusicGenresUseCase: GetMusicGenresUseCase, + private val getFileUrlUseCase: GetFileUrlUseCase ) : ViewModel() { val musicTitle = MutableStateFlow("") @@ -41,6 +45,9 @@ class UploadViewModel @Inject constructor( private val _musicGenres: MutableStateFlow> = MutableStateFlow(emptyList()) val musicGenres = _musicGenres.asStateFlow() + private val _uploadUiState: MutableStateFlow = MutableStateFlow(UploadUiState()) + val uploadUiState = _uploadUiState.asStateFlow() + init { fetchGenres() } @@ -54,11 +61,30 @@ class UploadViewModel @Inject constructor( fun uploadImage(imageUri: Uri) { // todo : image 파일을 업로드 한다. // todo : 반환 값을 uploadedImage에 저장한다. + imageUri.path?.let { path -> + getFileUrlUseCase(File(path)).onEach { + _thumbnailUrl.value = it + }.launchIn(viewModelScope) + } } fun uploadAudio(audioUri: Uri) { // todo : audio 파일을 업로드 한다. - val file = audioUri.path?.let { File(it) } + audioUri.path?.let { path -> + getFileUrlUseCase(File(path)).onEach { + _uploadUiState.value = uploadUiState.value.copy(isAudioLoading = false) + _audioUrl.value = it + }.onStart { + _uploadUiState.value = uploadUiState.value.copy(isAudioLoading = true) + }.catch { + _uploadUiState.value = uploadUiState.value.copy(isAudioLoading = false) + }.launchIn(viewModelScope) + } } -} \ No newline at end of file +} + +data class UploadUiState ( + val isAudioLoading: Boolean = false, +) + diff --git a/android/feature/upload/src/main/res/layout/fragment_upload.xml b/android/feature/upload/src/main/res/layout/fragment_upload.xml index 59aadd7..bd1bf34 100644 --- a/android/feature/upload/src/main/res/layout/fragment_upload.xml +++ b/android/feature/upload/src/main/res/layout/fragment_upload.xml @@ -4,6 +4,8 @@ + + @@ -40,6 +42,8 @@ android:id="@+id/progress_upload" android:layout_width="0dp" android:layout_height="wrap_content" + android:indeterminate="true" + android:visibility="@{viewModel.uploadUiState.audioLoading ? View.VISIBLE : View.GONE}" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/tb_upload" /> @@ -55,7 +59,7 @@ app:cardElevation="0dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/progress_upload"> + app:layout_constraintTop_toBottomOf="@id/tb_upload"> + android:importantForAccessibility="no" + app:imageUrl="@{viewModel.thumbnailUrl}" /> Date: Wed, 22 Nov 2023 16:33:00 +0900 Subject: [PATCH 084/165] =?UTF-8?q?feat=20:=20playlist=20entity=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * playlist entity 생성 * user와 관계 설정 --- server/src/entity/playlist.entity.ts | 34 ++++++++++++++++++++++++++++ server/src/entity/user.entity.ts | 5 ++++ 2 files changed, 39 insertions(+) create mode 100644 server/src/entity/playlist.entity.ts diff --git a/server/src/entity/playlist.entity.ts b/server/src/entity/playlist.entity.ts new file mode 100644 index 0000000..75733df --- /dev/null +++ b/server/src/entity/playlist.entity.ts @@ -0,0 +1,34 @@ +import { + BaseEntity, + Column, + CreateDateColumn, + Entity, + JoinColumn, + ManyToOne, + PrimaryGeneratedColumn, +} from 'typeorm'; +import { User } from './user.entity'; + +@Entity({ name: 'playlist' }) +export class Playlist extends BaseEntity { + @PrimaryGeneratedColumn() + playlistId: string; + + @Column() + playlist_title: string; + + @CreateDateColumn() + created_at: Date; + + @Column() + updated_at: Date; + + @ManyToOne( + () => User, + (user) => { + user.playlists; + }, + ) + @JoinColumn({ name: 'user_id' }) + user: User; +} diff --git a/server/src/entity/user.entity.ts b/server/src/entity/user.entity.ts index accdb4e..cd8da6f 100644 --- a/server/src/entity/user.entity.ts +++ b/server/src/entity/user.entity.ts @@ -4,7 +4,9 @@ import { CreateDateColumn, BaseEntity, PrimaryColumn, + OneToMany, } from 'typeorm'; +import { Playlist } from './playlist.entity'; @Entity({ name: 'user' }) export class User extends BaseEntity { @@ -22,4 +24,7 @@ export class User extends BaseEntity { @CreateDateColumn() created_at: Date; + + @OneToMany(() => Playlist, (playlist) => playlist.user) + playlists: Playlist[]; } From fce5a53b1866c4bb5f9d7d7b045b6d383cb930bf Mon Sep 17 00:00:00 2001 From: hyungun Date: Wed, 22 Nov 2023 16:54:33 +0900 Subject: [PATCH 085/165] =?UTF-8?q?feat=20:=20music=5Fplaylist=20entity=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * playlist 와 music 의 관계를 정리해주는 music_playlist 테이블 생성 --- server/src/entity/music.entity.ts | 7 ++++++- server/src/entity/music_playlist.entity.ts | 17 +++++++++++++++++ server/src/entity/playlist.entity.ts | 12 ++++++------ 3 files changed, 29 insertions(+), 7 deletions(-) create mode 100644 server/src/entity/music_playlist.entity.ts diff --git a/server/src/entity/music.entity.ts b/server/src/entity/music.entity.ts index 9486049..bf4e841 100644 --- a/server/src/entity/music.entity.ts +++ b/server/src/entity/music.entity.ts @@ -6,14 +6,16 @@ import { JoinColumn, PrimaryGeneratedColumn, ManyToOne, + OneToMany, } from 'typeorm'; import { User } from './user.entity'; import { Genres } from 'src/constants'; +import { Music_Playlist } from './music_playlist.entity'; @Entity({ name: 'music' }) export class Music extends BaseEntity { @PrimaryGeneratedColumn() - musicId: string; + musicId: number; @Column() title: string; @@ -36,4 +38,7 @@ export class Music extends BaseEntity { @ManyToOne(() => User) @JoinColumn({ name: 'user_id' }) user_id: string; + + @OneToMany(() => Music_Playlist, (music_playlist) => music_playlist.music) + music_playlist: Music_Playlist[]; } diff --git a/server/src/entity/music_playlist.entity.ts b/server/src/entity/music_playlist.entity.ts new file mode 100644 index 0000000..a323aec --- /dev/null +++ b/server/src/entity/music_playlist.entity.ts @@ -0,0 +1,17 @@ +import { BaseEntity, Entity, JoinColumn, ManyToOne, PrimaryGeneratedColumn } from 'typeorm'; +import { Music } from './music.entity'; +import { Playlist } from './playlist.entity'; + +@Entity({ name: 'music_playlist' }) +export class Music_Playlist extends BaseEntity { + @PrimaryGeneratedColumn() + music_playlist_id: number; + + @ManyToOne(() => Music, (music) => music.music_playlist) + @JoinColumn({name: 'music_id'}) + music: Music + + @ManyToOne(() => Playlist, (playlist) => playlist.music_playlist) + @JoinColumn({name: 'playlist_id'}) + playlist: Playlist +} diff --git a/server/src/entity/playlist.entity.ts b/server/src/entity/playlist.entity.ts index 75733df..e0ddeca 100644 --- a/server/src/entity/playlist.entity.ts +++ b/server/src/entity/playlist.entity.ts @@ -5,9 +5,11 @@ import { Entity, JoinColumn, ManyToOne, + OneToMany, PrimaryGeneratedColumn, } from 'typeorm'; import { User } from './user.entity'; +import { Music_Playlist } from './music_playlist.entity'; @Entity({ name: 'playlist' }) export class Playlist extends BaseEntity { @@ -23,12 +25,10 @@ export class Playlist extends BaseEntity { @Column() updated_at: Date; - @ManyToOne( - () => User, - (user) => { - user.playlists; - }, - ) + @ManyToOne(() => User, (user) => user.playlists) @JoinColumn({ name: 'user_id' }) user: User; + + @OneToMany(() => Music_Playlist, (music_playlist) => music_playlist.playlist) + music_playlist: Music_Playlist[]; } From 907baebf6c2efec7914ad6232da79d780b8d414b Mon Sep 17 00:00:00 2001 From: hyungun Date: Wed, 22 Nov 2023 17:10:27 +0900 Subject: [PATCH 086/165] =?UTF-8?q?fix=20:=20music=20=EA=B3=BC=20user=20?= =?UTF-8?q?=EA=B4=80=EA=B3=84=20=EC=84=A4=EC=A0=95=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * manyToOne, oneToMany 를 적극적으로 사용하여 설정 * musicId numnber 타입으로 수정 --- server/src/entity/music.entity.ts | 4 ++-- server/src/entity/user.entity.ts | 4 ++++ server/src/music/music.service.ts | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/server/src/entity/music.entity.ts b/server/src/entity/music.entity.ts index bf4e841..5f94661 100644 --- a/server/src/entity/music.entity.ts +++ b/server/src/entity/music.entity.ts @@ -35,9 +35,9 @@ export class Music extends BaseEntity { @CreateDateColumn() created_at: Date; - @ManyToOne(() => User) + @ManyToOne(() => User, (user) => user.musics) @JoinColumn({ name: 'user_id' }) - user_id: string; + user: User; @OneToMany(() => Music_Playlist, (music_playlist) => music_playlist.music) music_playlist: Music_Playlist[]; diff --git a/server/src/entity/user.entity.ts b/server/src/entity/user.entity.ts index cd8da6f..e0d5ae6 100644 --- a/server/src/entity/user.entity.ts +++ b/server/src/entity/user.entity.ts @@ -7,6 +7,7 @@ import { OneToMany, } from 'typeorm'; import { Playlist } from './playlist.entity'; +import { Music } from './music.entity'; @Entity({ name: 'user' }) export class User extends BaseEntity { @@ -25,6 +26,9 @@ export class User extends BaseEntity { @CreateDateColumn() created_at: Date; + @OneToMany(() => Music, (music) => music.user) + musics: Music[]; + @OneToMany(() => Playlist, (playlist) => playlist.user) playlists: Playlist[]; } diff --git a/server/src/music/music.service.ts b/server/src/music/music.service.ts index 1cfb3d5..20ec5b9 100644 --- a/server/src/music/music.service.ts +++ b/server/src/music/music.service.ts @@ -38,7 +38,7 @@ export class MusicService { musicFile, created_at: new Date(), genre, - user_id, + user: { user_id: user_id }, }); this.musicRepository.save(newMusic); From 5b4bb6c47425f614fef6b19f158e6e378866f605 Mon Sep 17 00:00:00 2001 From: hyungun Date: Wed, 22 Nov 2023 17:27:02 +0900 Subject: [PATCH 087/165] =?UTF-8?q?feat=20:=20playlist=20module=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * playlist 요청 처리를 위한 모듈 생성 --- server/src/app.module.ts | 3 +++ .../src/playlist/playlist.controller.spec.ts | 18 ++++++++++++++++++ server/src/playlist/playlist.controller.ts | 7 +++++++ server/src/playlist/playlist.module.ts | 9 +++++++++ server/src/playlist/playlist.service.spec.ts | 18 ++++++++++++++++++ server/src/playlist/playlist.service.ts | 12 ++++++++++++ 6 files changed, 67 insertions(+) create mode 100644 server/src/playlist/playlist.controller.spec.ts create mode 100644 server/src/playlist/playlist.controller.ts create mode 100644 server/src/playlist/playlist.module.ts create mode 100644 server/src/playlist/playlist.service.spec.ts create mode 100644 server/src/playlist/playlist.service.ts diff --git a/server/src/app.module.ts b/server/src/app.module.ts index 83de3ce..d6b78c8 100644 --- a/server/src/app.module.ts +++ b/server/src/app.module.ts @@ -7,6 +7,8 @@ import { TypeOrmConfigService } from 'src/config/typeorm.config'; import { ConfigModule } from '@nestjs/config'; import { UploadModule } from './upload/upload.module'; import { MusicModule } from './music/music.module'; +import { PlaylistController } from './playlist/playlist.controller'; +import { PlaylistModule } from './playlist/playlist.module'; @Module({ imports: [ @@ -18,6 +20,7 @@ import { MusicModule } from './music/music.module'; UserModule, UploadModule, MusicModule, + PlaylistModule, ], controllers: [AppController], providers: [AppService], diff --git a/server/src/playlist/playlist.controller.spec.ts b/server/src/playlist/playlist.controller.spec.ts new file mode 100644 index 0000000..dbc64d1 --- /dev/null +++ b/server/src/playlist/playlist.controller.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { PlaylistController } from './playlist.controller'; + +describe('PlaylistController', () => { + let controller: PlaylistController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [PlaylistController], + }).compile(); + + controller = module.get(PlaylistController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/server/src/playlist/playlist.controller.ts b/server/src/playlist/playlist.controller.ts new file mode 100644 index 0000000..c534b28 --- /dev/null +++ b/server/src/playlist/playlist.controller.ts @@ -0,0 +1,7 @@ +import { Controller } from '@nestjs/common'; +import { PlaylistService } from './playlist.service'; + +@Controller('playlist') +export class PlaylistController { + constructor(private playlistService: PlaylistService) {} +} diff --git a/server/src/playlist/playlist.module.ts b/server/src/playlist/playlist.module.ts new file mode 100644 index 0000000..8fd6c48 --- /dev/null +++ b/server/src/playlist/playlist.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { PlaylistService } from './playlist.service'; +import { PlaylistController } from './playlist.controller'; + +@Module({ + controllers: [PlaylistController], + providers: [PlaylistService] +}) +export class PlaylistModule {} diff --git a/server/src/playlist/playlist.service.spec.ts b/server/src/playlist/playlist.service.spec.ts new file mode 100644 index 0000000..5dc1bfb --- /dev/null +++ b/server/src/playlist/playlist.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { PlaylistService } from './playlist.service'; + +describe('PlaylistService', () => { + let service: PlaylistService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [PlaylistService], + }).compile(); + + service = module.get(PlaylistService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/server/src/playlist/playlist.service.ts b/server/src/playlist/playlist.service.ts new file mode 100644 index 0000000..890f040 --- /dev/null +++ b/server/src/playlist/playlist.service.ts @@ -0,0 +1,12 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Playlist } from 'src/entity/playlist.entity'; +import { Repository } from 'typeorm'; + +@Injectable() +export class PlaylistService { + constructor( + @InjectRepository(Playlist) + private playlistRepository: Repository, + ) {} +} From 42cb4e27f2f27ad1e940e6f8e19d4fdce1a0bde2 Mon Sep 17 00:00:00 2001 From: hyungun Date: Wed, 22 Nov 2023 18:50:14 +0900 Subject: [PATCH 088/165] =?UTF-8?q?feat=20:=20playlistCreate=20DTO=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * playlist 를 생성할 때 사용하는 DTO 객체 추가 --- server/src/dto/playlistCreate.dto.ts | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 server/src/dto/playlistCreate.dto.ts diff --git a/server/src/dto/playlistCreate.dto.ts b/server/src/dto/playlistCreate.dto.ts new file mode 100644 index 0000000..c3ddf7f --- /dev/null +++ b/server/src/dto/playlistCreate.dto.ts @@ -0,0 +1,9 @@ +import { IsNotEmpty, IsString, Matches, MaxLength } from "class-validator"; + +export class PlaylistCreateDto { + @IsNotEmpty() + @IsString() + @MaxLength(20, { message: '글자 수가 50을 넘어갔습니다.' }) + @Matches(/^[가-힣a-zA-Z ]+$/) + title: string; + } \ No newline at end of file From 3b23d728e147439d02ccd62259b2ba0b3d53770e Mon Sep 17 00:00:00 2001 From: youlalala Date: Wed, 22 Nov 2023 18:59:39 +0900 Subject: [PATCH 089/165] =?UTF-8?q?feat=20:=20upload=20ui=20state=20?= =?UTF-8?q?=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/domain/usecase/GetFileUrlUseCase.kt | 18 ---- .../core/domain/usecase/UploadFileUseCase.kt | 24 +++++ android/core/ui/build.gradle.kts | 2 +- .../feature/upload/BindingAdapter.kt | 28 ++++++ .../feature/upload/UploadViewModel.kt | 92 ++++++++++++------- .../src/main/res/layout/fragment_upload.xml | 11 ++- 6 files changed, 117 insertions(+), 58 deletions(-) delete mode 100644 android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/GetFileUrlUseCase.kt create mode 100644 android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/UploadFileUseCase.kt diff --git a/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/GetFileUrlUseCase.kt b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/GetFileUrlUseCase.kt deleted file mode 100644 index 95d55f6..0000000 --- a/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/GetFileUrlUseCase.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.ohdodok.catchytape.core.domain.usecase - -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flow -import java.io.File -import javax.inject.Inject - -class GetFileUrlUseCase @Inject constructor( - -) { - operator fun invoke(file: File): Flow = flow { - // todo : file을 업로드 하고 url을 받아온다. - // todo : 임시 방편 - delay(5000) - emit("https://kr.object.ncloudstorage.com/catchy-tape-bucket2/music/2/%EC%9D%B4%EB%85%B8%EB%9E%98.mp3") - } -} \ No newline at end of file diff --git a/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/UploadFileUseCase.kt b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/UploadFileUseCase.kt new file mode 100644 index 0000000..b2a7d7f --- /dev/null +++ b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/UploadFileUseCase.kt @@ -0,0 +1,24 @@ +package com.ohdodok.catchytape.core.domain.usecase + +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import java.io.File +import javax.inject.Inject + +class UploadFileUseCase @Inject constructor( + +) { + + fun getImgUrl(file: File): Flow = flow { + // todo : 서버 기다리는 중.. + delay(1000) + emit("https://kr.object.ncloudstorage.com/catchy-tape-bucket2/image/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202023-11-21%20180100.png") + } + + fun getAudioUrl(file: File): Flow = flow { + // todo : 서버 기다리는 중.. + delay(1000) + emit("https://kr.object.ncloudstorage.com/catchy-tape-bucket2/music/2/%EC%9D%B4%EB%85%B8%EB%9E%98.mp3") + } +} \ No newline at end of file diff --git a/android/core/ui/build.gradle.kts b/android/core/ui/build.gradle.kts index f08a30f..bfad06e 100644 --- a/android/core/ui/build.gradle.kts +++ b/android/core/ui/build.gradle.kts @@ -38,5 +38,5 @@ dependencies { api(libs.navigation.fragment.ktx) api(libs.navigation.ui.ktx) - implementation(libs.glide) + api(libs.glide) } \ No newline at end of file diff --git a/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/BindingAdapter.kt b/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/BindingAdapter.kt index 8489ec3..0228058 100644 --- a/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/BindingAdapter.kt +++ b/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/BindingAdapter.kt @@ -3,10 +3,38 @@ package com.ohdodok.catchytape.feature.upload import android.R import android.widget.ArrayAdapter import android.widget.AutoCompleteTextView +import android.widget.Button +import android.widget.ImageView +import androidx.core.view.isVisible import androidx.databinding.BindingAdapter +import com.bumptech.glide.Glide +import com.google.android.material.progressindicator.LinearProgressIndicator @BindingAdapter("list") fun AutoCompleteTextView.setAdapter(list: List) { val adapter = ArrayAdapter(this.context, R.layout.simple_list_item_1, list) setAdapter(adapter) +} + +@BindingAdapter("visible") +fun LinearProgressIndicator.setVisible(uiState: UploadUiState) { + isVisible = uiState.audioState is InputState.Loading || uiState.imageState is InputState.Loading +} + +@BindingAdapter("completeBtnEnable") +fun Button.setCompleteBtnEnable(uiState: UploadUiState) { + isEnabled = + uiState.audioState is InputState.Success + && uiState.imageState is InputState.Success + && uiState.titleState is InputState.Success + && uiState.genreState is InputState.Success +} + +@BindingAdapter("uploadedThumbnail") +fun ImageView.bindUrl(uiState: UploadUiState) { + if (uiState.imageState is InputState.Success) { + Glide.with(this) + .load(uiState.imageState.value) + .into(this) + } } \ No newline at end of file diff --git a/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadViewModel.kt b/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadViewModel.kt index 232ef88..8ddda13 100644 --- a/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadViewModel.kt +++ b/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadViewModel.kt @@ -6,42 +6,24 @@ import dagger.hilt.android.lifecycle.HiltViewModel import java.io.File import javax.inject.Inject import androidx.lifecycle.viewModelScope -import com.ohdodok.catchytape.core.domain.usecase.GetFileUrlUseCase +import com.ohdodok.catchytape.core.domain.usecase.UploadFileUseCase import com.ohdodok.catchytape.core.domain.usecase.GetMusicGenresUseCase import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.catch -import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart -import kotlinx.coroutines.flow.stateIn @HiltViewModel class UploadViewModel @Inject constructor( private val getMusicGenresUseCase: GetMusicGenresUseCase, - private val getFileUrlUseCase: GetFileUrlUseCase + private val uploadFileUseCase: UploadFileUseCase ) : ViewModel() { val musicTitle = MutableStateFlow("") val musicGenre = MutableStateFlow("") - private val _thumbnailUrl: MutableStateFlow = MutableStateFlow("") - val thumbnailUrl = _thumbnailUrl.asStateFlow() - - private val _audioUrl: MutableStateFlow = MutableStateFlow("") - val audioUrl = _audioUrl.asStateFlow() - - val isUploadEnable: StateFlow = - combine(musicTitle, musicGenre, thumbnailUrl, audioUrl) { title, genre, thumbnail, audio -> - title.isNotBlank() - && genre.isNotBlank() - && thumbnail.isNotBlank() - && audio.isNotBlank() - }.stateIn(viewModelScope, SharingStarted.Eagerly, false) - private val _musicGenres: MutableStateFlow> = MutableStateFlow(emptyList()) val musicGenres = _musicGenres.asStateFlow() @@ -50,6 +32,8 @@ class UploadViewModel @Inject constructor( init { fetchGenres() + observeTitle() + observeGenre() } private fun fetchGenres() { @@ -58,33 +42,73 @@ class UploadViewModel @Inject constructor( }.launchIn(viewModelScope) } + private fun observeTitle() { + musicTitle.onEach { + if (it.isEmpty()) { + _uploadUiState.value = uploadUiState.value.copy(titleState = InputState.Empty) + return@onEach + } else { + _uploadUiState.value = + uploadUiState.value.copy(titleState = InputState.Success(value = it)) + } + }.launchIn(viewModelScope) + } + + private fun observeGenre() { + musicGenre.onEach { + if (it.isEmpty()) { + _uploadUiState.value = uploadUiState.value.copy(genreState = InputState.Empty) + return@onEach + } else { + _uploadUiState.value = + uploadUiState.value.copy(genreState = InputState.Success(value = it)) + } + }.launchIn(viewModelScope) + } + fun uploadImage(imageUri: Uri) { - // todo : image 파일을 업로드 한다. - // todo : 반환 값을 uploadedImage에 저장한다. imageUri.path?.let { path -> - getFileUrlUseCase(File(path)).onEach { - _thumbnailUrl.value = it + uploadFileUseCase.getImgUrl(File(path)).onStart { + _uploadUiState.value = uploadUiState.value.copy(imageState = InputState.Loading) + }.catch { + // TODO : 에러 처리 + _uploadUiState.value = + uploadUiState.value.copy(imageState = InputState.Error) + }.onEach { url -> + _uploadUiState.value = + uploadUiState.value.copy(imageState = InputState.Success(value = url)) }.launchIn(viewModelScope) } } fun uploadAudio(audioUri: Uri) { - // todo : audio 파일을 업로드 한다. audioUri.path?.let { path -> - getFileUrlUseCase(File(path)).onEach { - _uploadUiState.value = uploadUiState.value.copy(isAudioLoading = false) - _audioUrl.value = it - }.onStart { - _uploadUiState.value = uploadUiState.value.copy(isAudioLoading = true) + uploadFileUseCase.getAudioUrl(File(path)).onStart { + _uploadUiState.value = uploadUiState.value.copy(audioState = InputState.Loading) }.catch { - _uploadUiState.value = uploadUiState.value.copy(isAudioLoading = false) + // TODO : 에러 처리 + _uploadUiState.value = + uploadUiState.value.copy(audioState = InputState.Error) + }.onEach { + _uploadUiState.value = + uploadUiState.value.copy(audioState = InputState.Success(value = it)) }.launchIn(viewModelScope) } } - } -data class UploadUiState ( - val isAudioLoading: Boolean = false, +data class UploadUiState( + val audioState: InputState = InputState.Empty, + val imageState: InputState = InputState.Empty, + val titleState: InputState = InputState.Empty, + val genreState: InputState = InputState.Empty ) +sealed class InputState { + data object Empty : InputState() + + data object Loading : InputState() + data class Success(val value: String) : InputState() + data object Error: InputState() +} + diff --git a/android/feature/upload/src/main/res/layout/fragment_upload.xml b/android/feature/upload/src/main/res/layout/fragment_upload.xml index bd1bf34..022118f 100644 --- a/android/feature/upload/src/main/res/layout/fragment_upload.xml +++ b/android/feature/upload/src/main/res/layout/fragment_upload.xml @@ -9,6 +9,7 @@ + + android:text="@string/complete" + app:completeBtnEnable="@{viewModel.uploadUiState}" /> @@ -43,10 +44,10 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:indeterminate="true" - android:visibility="@{viewModel.uploadUiState.audioLoading ? View.VISIBLE : View.GONE}" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/tb_upload" /> + app:layout_constraintTop_toBottomOf="@id/tb_upload" + app:visible="@{viewModel.uploadUiState}" /> + app:uploadedThumbnail="@{viewModel.uploadUiState}" /> Date: Wed, 22 Nov 2023 19:17:11 +0900 Subject: [PATCH 090/165] =?UTF-8?q?feat=20:=20playlist=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * POST /playlists 로 생성 가능 --- server/src/playlist/playlist.controller.ts | 31 +++++++++++++++++++--- server/src/playlist/playlist.module.ts | 6 ++++- server/src/playlist/playlist.service.ts | 15 +++++++++++ 3 files changed, 48 insertions(+), 4 deletions(-) diff --git a/server/src/playlist/playlist.controller.ts b/server/src/playlist/playlist.controller.ts index c534b28..658508a 100644 --- a/server/src/playlist/playlist.controller.ts +++ b/server/src/playlist/playlist.controller.ts @@ -1,7 +1,32 @@ -import { Controller } from '@nestjs/common'; +import { + Body, + Controller, + HttpCode, + Post, + Req, + UseGuards, + UsePipes, + ValidationPipe, +} from '@nestjs/common'; import { PlaylistService } from './playlist.service'; +import { AuthGuard } from '@nestjs/passport'; +import { HTTP_STATUS_CODE } from 'src/httpStatusCode.enum'; +import { PlaylistCreateDto } from 'src/dto/playlistCreate.dto'; -@Controller('playlist') +@Controller('playlists') export class PlaylistController { - constructor(private playlistService: PlaylistService) {} + constructor(private playlistService: PlaylistService) {} + + @Post() + @UseGuards(AuthGuard()) + @UsePipes(ValidationPipe) + @HttpCode(HTTP_STATUS_CODE.SUCCESS) + async createPlaylist( + @Req() req, + @Body() playlistCreateDto: PlaylistCreateDto, + ): Promise<{ playlist_id: number }> { + const userId: string = req.user.user_id; + const playlistId: number = await this.playlistService.createPlaylist(userId, playlistCreateDto); + return { playlist_id: playlistId }; + } } diff --git a/server/src/playlist/playlist.module.ts b/server/src/playlist/playlist.module.ts index 8fd6c48..efa7d25 100644 --- a/server/src/playlist/playlist.module.ts +++ b/server/src/playlist/playlist.module.ts @@ -1,9 +1,13 @@ import { Module } from '@nestjs/common'; import { PlaylistService } from './playlist.service'; import { PlaylistController } from './playlist.controller'; +import { Playlist } from 'src/entity/playlist.entity'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { AuthModule } from 'src/auth/auth.module'; @Module({ + imports: [TypeOrmModule.forFeature([Playlist]), AuthModule], controllers: [PlaylistController], - providers: [PlaylistService] + providers: [PlaylistService], }) export class PlaylistModule {} diff --git a/server/src/playlist/playlist.service.ts b/server/src/playlist/playlist.service.ts index 890f040..7b35932 100644 --- a/server/src/playlist/playlist.service.ts +++ b/server/src/playlist/playlist.service.ts @@ -1,5 +1,6 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; +import { PlaylistCreateDto } from 'src/dto/playlistCreate.dto'; import { Playlist } from 'src/entity/playlist.entity'; import { Repository } from 'typeorm'; @@ -9,4 +10,18 @@ export class PlaylistService { @InjectRepository(Playlist) private playlistRepository: Repository, ) {} + + async createPlaylist(userId: string, playlistCreateDto: PlaylistCreateDto): Promise { + const title: string = playlistCreateDto.title; + const newPlaylist: Playlist = this.playlistRepository.create({ + playlist_title: title, + created_at: new Date(), + updated_at: new Date(), + user: { user_id: userId }, + }); + + const result: Playlist = await this.playlistRepository.save(newPlaylist); + const playlistId: number = parseInt(result.playlistId); + return playlistId; + } } From 44ddc0b8caca33e985b724574c30984ae1b2047c Mon Sep 17 00:00:00 2001 From: algosketch Date: Wed, 22 Nov 2023 19:36:16 +0900 Subject: [PATCH 091/165] =?UTF-8?q?feat=20:=20=EC=95=B1=20=EC=A7=84?= =?UTF-8?q?=EC=9E=85=ED=95=A0=20=EB=95=8C,=20=EB=84=A4=ED=8A=B8=EC=9B=8C?= =?UTF-8?q?=ED=81=AC=20=EC=97=B0=EA=B2=B0=EC=9D=B4=20=EB=81=8A=EA=B2=BC?= =?UTF-8?q?=EC=9D=84=20=EB=95=8C=20=EB=84=A4=ED=8A=B8=EC=9B=8C=ED=81=AC=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=20=EC=B2=B4=ED=81=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- android/app/src/main/AndroidManifest.xml | 11 ++++-- .../com/ohdodok/catchytape/MainActivity.kt | 37 +++++++++++++++++++ 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index dadac8d..8b77eed 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,6 +1,7 @@ + - - - - + + + + + + diff --git a/android/app/src/main/java/com/ohdodok/catchytape/MainActivity.kt b/android/app/src/main/java/com/ohdodok/catchytape/MainActivity.kt index 59ca690..042b76a 100644 --- a/android/app/src/main/java/com/ohdodok/catchytape/MainActivity.kt +++ b/android/app/src/main/java/com/ohdodok/catchytape/MainActivity.kt @@ -1,13 +1,50 @@ package com.ohdodok.catchytape +import android.net.ConnectivityManager +import android.net.Network +import android.net.NetworkCapabilities +import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED import android.os.Bundle +import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint class MainActivity : AppCompatActivity() { + private lateinit var connectivityManager: ConnectivityManager + + private val networkCallback = object : ConnectivityManager.NetworkCallback() { + override fun onLost(network: Network) { + super.onLost(network) + checkNetworkState() + } + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) + + connectivityManager = getSystemService(ConnectivityManager::class.java) + checkNetworkState() + } + + override fun onResume() { + super.onResume() + connectivityManager.registerDefaultNetworkCallback(networkCallback) + } + + override fun onPause() { + super.onPause() + connectivityManager.unregisterNetworkCallback(networkCallback) + } + + private fun checkNetworkState() { + val activeNetwork = connectivityManager.activeNetwork + val capabilities = connectivityManager.getNetworkCapabilities(activeNetwork) + val isNetworkAvailable = capabilities?.hasCapability(NET_CAPABILITY_VALIDATED) ?: false + + if(!isNetworkAvailable) { + Toast.makeText(this, "네트워크 연결을 확인해 주세요.", Toast.LENGTH_LONG).show() + } } } \ No newline at end of file From 39a4240cc9f4232ba378559c8a2cab253c57daec Mon Sep 17 00:00:00 2001 From: youlalala Date: Wed, 22 Nov 2023 19:38:06 +0900 Subject: [PATCH 092/165] =?UTF-8?q?feat=20:=20camera=20icon=20visible=20?= =?UTF-8?q?=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ohdodok/catchytape/feature/upload/BindingAdapter.kt | 7 +++++++ .../feature/upload/src/main/res/layout/fragment_upload.xml | 7 ++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/BindingAdapter.kt b/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/BindingAdapter.kt index 0228058..bc8da08 100644 --- a/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/BindingAdapter.kt +++ b/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/BindingAdapter.kt @@ -1,9 +1,11 @@ package com.ohdodok.catchytape.feature.upload import android.R +import android.graphics.drawable.Drawable import android.widget.ArrayAdapter import android.widget.AutoCompleteTextView import android.widget.Button +import android.widget.ImageButton import android.widget.ImageView import androidx.core.view.isVisible import androidx.databinding.BindingAdapter @@ -37,4 +39,9 @@ fun ImageView.bindUrl(uiState: UploadUiState) { .load(uiState.imageState.value) .into(this) } +} + +@BindingAdapter("visible") +fun ImageView.setVisible(uiState: UploadUiState) { + isVisible = uiState.imageState is InputState.Empty } \ No newline at end of file diff --git a/android/feature/upload/src/main/res/layout/fragment_upload.xml b/android/feature/upload/src/main/res/layout/fragment_upload.xml index 022118f..5257c1d 100644 --- a/android/feature/upload/src/main/res/layout/fragment_upload.xml +++ b/android/feature/upload/src/main/res/layout/fragment_upload.xml @@ -6,6 +6,10 @@ + + @@ -77,7 +81,8 @@ android:layout_height="48dp" android:layout_gravity="center" android:importantForAccessibility="no" - android:src="@drawable/ic_camera" /> + android:src="@drawable/ic_camera" + app:visible="@{viewModel.uploadUiState}" /> From 399ba9d9d6055d631e80a2c126bc134fd50aed96 Mon Sep 17 00:00:00 2001 From: youlalala Date: Wed, 22 Nov 2023 19:40:12 +0900 Subject: [PATCH 093/165] =?UTF-8?q?chore=20:=20=ED=95=84=EC=9A=94=20?= =?UTF-8?q?=EC=97=86=EB=8A=94=20import=20=EB=AC=B8=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../upload/src/main/res/layout/fragment_upload.xml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/android/feature/upload/src/main/res/layout/fragment_upload.xml b/android/feature/upload/src/main/res/layout/fragment_upload.xml index 5257c1d..6d84b39 100644 --- a/android/feature/upload/src/main/res/layout/fragment_upload.xml +++ b/android/feature/upload/src/main/res/layout/fragment_upload.xml @@ -3,17 +3,9 @@ xmlns:app="http://schemas.android.com/apk/res-auto"> - - - - - - Date: Wed, 22 Nov 2023 19:43:41 +0900 Subject: [PATCH 094/165] =?UTF-8?q?refactor=20:=20NetworkStateObserver=20?= =?UTF-8?q?=EC=B6=94=EC=B6=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/ohdodok/catchytape/MainActivity.kt | 18 ++---------- .../catchytape/NetworkStateObserver.kt | 29 +++++++++++++++++++ 2 files changed, 31 insertions(+), 16 deletions(-) create mode 100644 android/app/src/main/java/com/ohdodok/catchytape/NetworkStateObserver.kt diff --git a/android/app/src/main/java/com/ohdodok/catchytape/MainActivity.kt b/android/app/src/main/java/com/ohdodok/catchytape/MainActivity.kt index 042b76a..3a23c10 100644 --- a/android/app/src/main/java/com/ohdodok/catchytape/MainActivity.kt +++ b/android/app/src/main/java/com/ohdodok/catchytape/MainActivity.kt @@ -13,29 +13,15 @@ import dagger.hilt.android.AndroidEntryPoint class MainActivity : AppCompatActivity() { private lateinit var connectivityManager: ConnectivityManager - private val networkCallback = object : ConnectivityManager.NetworkCallback() { - override fun onLost(network: Network) { - super.onLost(network) - checkNetworkState() - } - } - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) connectivityManager = getSystemService(ConnectivityManager::class.java) checkNetworkState() - } - - override fun onResume() { - super.onResume() - connectivityManager.registerDefaultNetworkCallback(networkCallback) - } - override fun onPause() { - super.onPause() - connectivityManager.unregisterNetworkCallback(networkCallback) + val networkStateObserver = NetworkStateObserver(connectivityManager, ::checkNetworkState) + lifecycle.addObserver(networkStateObserver) } private fun checkNetworkState() { diff --git a/android/app/src/main/java/com/ohdodok/catchytape/NetworkStateObserver.kt b/android/app/src/main/java/com/ohdodok/catchytape/NetworkStateObserver.kt new file mode 100644 index 0000000..f3bd7af --- /dev/null +++ b/android/app/src/main/java/com/ohdodok/catchytape/NetworkStateObserver.kt @@ -0,0 +1,29 @@ +package com.ohdodok.catchytape + +import android.net.ConnectivityManager +import android.net.Network +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.LifecycleOwner + +class NetworkStateObserver( + private val connectivityManager: ConnectivityManager, + checkNetworkState: () -> Unit +) : DefaultLifecycleObserver { + + private val networkCallback = object : ConnectivityManager.NetworkCallback() { + override fun onLost(network: Network) { + super.onLost(network) + checkNetworkState() + } + } + + override fun onResume(owner: LifecycleOwner) { + super.onResume(owner) + connectivityManager.registerDefaultNetworkCallback(networkCallback) + } + + override fun onPause(owner: LifecycleOwner) { + super.onPause(owner) + connectivityManager.unregisterNetworkCallback(networkCallback) + } +} \ No newline at end of file From 6a09914827931732e40e0b283b5509812fdeae4a Mon Sep 17 00:00:00 2001 From: algosketch Date: Wed, 22 Nov 2023 19:48:41 +0900 Subject: [PATCH 095/165] =?UTF-8?q?refactor=20:=20=EB=AC=B8=EC=9E=90?= =?UTF-8?q?=EC=97=B4=20=EC=9E=90=EC=9B=90=EC=9C=BC=EB=A1=9C=20=EC=B6=94?= =?UTF-8?q?=EC=B6=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/ohdodok/catchytape/MainActivity.kt | 7 +++---- android/core/ui/src/main/res/values/strings.xml | 2 ++ 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/android/app/src/main/java/com/ohdodok/catchytape/MainActivity.kt b/android/app/src/main/java/com/ohdodok/catchytape/MainActivity.kt index 3a23c10..f7bb5a7 100644 --- a/android/app/src/main/java/com/ohdodok/catchytape/MainActivity.kt +++ b/android/app/src/main/java/com/ohdodok/catchytape/MainActivity.kt @@ -1,13 +1,12 @@ package com.ohdodok.catchytape import android.net.ConnectivityManager -import android.net.Network -import android.net.NetworkCapabilities import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED import android.os.Bundle import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import dagger.hilt.android.AndroidEntryPoint +import com.ohdodok.catchytape.core.ui.R.string as uiString @AndroidEntryPoint class MainActivity : AppCompatActivity() { @@ -29,8 +28,8 @@ class MainActivity : AppCompatActivity() { val capabilities = connectivityManager.getNetworkCapabilities(activeNetwork) val isNetworkAvailable = capabilities?.hasCapability(NET_CAPABILITY_VALIDATED) ?: false - if(!isNetworkAvailable) { - Toast.makeText(this, "네트워크 연결을 확인해 주세요.", Toast.LENGTH_LONG).show() + if (!isNetworkAvailable) { + Toast.makeText(this, getString(uiString.check_network), Toast.LENGTH_LONG).show() } } } \ No newline at end of file diff --git a/android/core/ui/src/main/res/values/strings.xml b/android/core/ui/src/main/res/values/strings.xml index f03fc8d..725656c 100644 --- a/android/core/ui/src/main/res/values/strings.xml +++ b/android/core/ui/src/main/res/values/strings.xml @@ -1,5 +1,7 @@ + 네트워크 연결을 확인해 주세요. + 검색 재생목록 From 4dfc6e257d7fef91ca4d36a4b24310f47b5d8bfd Mon Sep 17 00:00:00 2001 From: youlalala Date: Wed, 22 Nov 2023 20:05:51 +0900 Subject: [PATCH 096/165] =?UTF-8?q?feat=20:=20empty=20=EC=83=81=ED=83=9C?= =?UTF-8?q?=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/upload/BindingAdapter.kt | 19 +++------ .../feature/upload/UploadViewModel.kt | 40 +++++++++---------- .../src/main/res/layout/fragment_upload.xml | 7 +++- 3 files changed, 32 insertions(+), 34 deletions(-) diff --git a/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/BindingAdapter.kt b/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/BindingAdapter.kt index bc8da08..b3dc905 100644 --- a/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/BindingAdapter.kt +++ b/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/BindingAdapter.kt @@ -1,11 +1,9 @@ package com.ohdodok.catchytape.feature.upload import android.R -import android.graphics.drawable.Drawable import android.widget.ArrayAdapter import android.widget.AutoCompleteTextView import android.widget.Button -import android.widget.ImageButton import android.widget.ImageView import androidx.core.view.isVisible import androidx.databinding.BindingAdapter @@ -20,28 +18,23 @@ fun AutoCompleteTextView.setAdapter(list: List) { @BindingAdapter("visible") fun LinearProgressIndicator.setVisible(uiState: UploadUiState) { - isVisible = uiState.audioState is InputState.Loading || uiState.imageState is InputState.Loading + isVisible = uiState.audioState is UploadInputState.Loading || uiState.imageState is UploadInputState.Loading } @BindingAdapter("completeBtnEnable") fun Button.setCompleteBtnEnable(uiState: UploadUiState) { isEnabled = - uiState.audioState is InputState.Success - && uiState.imageState is InputState.Success - && uiState.titleState is InputState.Success - && uiState.genreState is InputState.Success + uiState.audioState is UploadInputState.Success + && uiState.imageState is UploadInputState.Success + && uiState.titleState is UploadInputState.Success + && uiState.genreState is UploadInputState.Success } @BindingAdapter("uploadedThumbnail") fun ImageView.bindUrl(uiState: UploadUiState) { - if (uiState.imageState is InputState.Success) { + if (uiState.imageState is UploadInputState.Success) { Glide.with(this) .load(uiState.imageState.value) .into(this) } -} - -@BindingAdapter("visible") -fun ImageView.setVisible(uiState: UploadUiState) { - isVisible = uiState.imageState is InputState.Empty } \ No newline at end of file diff --git a/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadViewModel.kt b/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadViewModel.kt index 8ddda13..7053bc8 100644 --- a/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadViewModel.kt +++ b/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadViewModel.kt @@ -45,11 +45,11 @@ class UploadViewModel @Inject constructor( private fun observeTitle() { musicTitle.onEach { if (it.isEmpty()) { - _uploadUiState.value = uploadUiState.value.copy(titleState = InputState.Empty) + _uploadUiState.value = uploadUiState.value.copy(titleState = null) return@onEach } else { _uploadUiState.value = - uploadUiState.value.copy(titleState = InputState.Success(value = it)) + uploadUiState.value.copy(titleState = UploadInputState.Success(value = it)) } }.launchIn(viewModelScope) } @@ -57,11 +57,11 @@ class UploadViewModel @Inject constructor( private fun observeGenre() { musicGenre.onEach { if (it.isEmpty()) { - _uploadUiState.value = uploadUiState.value.copy(genreState = InputState.Empty) + _uploadUiState.value = uploadUiState.value.copy(genreState = null) return@onEach } else { _uploadUiState.value = - uploadUiState.value.copy(genreState = InputState.Success(value = it)) + uploadUiState.value.copy(genreState = UploadInputState.Success(value = it)) } }.launchIn(viewModelScope) } @@ -69,14 +69,15 @@ class UploadViewModel @Inject constructor( fun uploadImage(imageUri: Uri) { imageUri.path?.let { path -> uploadFileUseCase.getImgUrl(File(path)).onStart { - _uploadUiState.value = uploadUiState.value.copy(imageState = InputState.Loading) + _uploadUiState.value = + uploadUiState.value.copy(imageState = UploadInputState.Loading) }.catch { // TODO : 에러 처리 _uploadUiState.value = - uploadUiState.value.copy(imageState = InputState.Error) + uploadUiState.value.copy(imageState = UploadInputState.Error) }.onEach { url -> _uploadUiState.value = - uploadUiState.value.copy(imageState = InputState.Success(value = url)) + uploadUiState.value.copy(imageState = UploadInputState.Success(value = url)) }.launchIn(viewModelScope) } } @@ -84,31 +85,30 @@ class UploadViewModel @Inject constructor( fun uploadAudio(audioUri: Uri) { audioUri.path?.let { path -> uploadFileUseCase.getAudioUrl(File(path)).onStart { - _uploadUiState.value = uploadUiState.value.copy(audioState = InputState.Loading) + _uploadUiState.value = + uploadUiState.value.copy(audioState = UploadInputState.Loading) }.catch { // TODO : 에러 처리 _uploadUiState.value = - uploadUiState.value.copy(audioState = InputState.Error) + uploadUiState.value.copy(audioState = UploadInputState.Error) }.onEach { _uploadUiState.value = - uploadUiState.value.copy(audioState = InputState.Success(value = it)) + uploadUiState.value.copy(audioState = UploadInputState.Success(value = it)) }.launchIn(viewModelScope) } } } data class UploadUiState( - val audioState: InputState = InputState.Empty, - val imageState: InputState = InputState.Empty, - val titleState: InputState = InputState.Empty, - val genreState: InputState = InputState.Empty + val audioState: UploadInputState? = null, + val imageState: UploadInputState? = null, + val titleState: UploadInputState? = null, + val genreState: UploadInputState? = null ) -sealed class InputState { - data object Empty : InputState() - - data object Loading : InputState() - data class Success(val value: String) : InputState() - data object Error: InputState() +sealed class UploadInputState { + data object Loading : UploadInputState() + data class Success(val value: String) : UploadInputState() + data object Error : UploadInputState() } diff --git a/android/feature/upload/src/main/res/layout/fragment_upload.xml b/android/feature/upload/src/main/res/layout/fragment_upload.xml index 6d84b39..518be9c 100644 --- a/android/feature/upload/src/main/res/layout/fragment_upload.xml +++ b/android/feature/upload/src/main/res/layout/fragment_upload.xml @@ -3,6 +3,11 @@ xmlns:app="http://schemas.android.com/apk/res-auto"> + + + @@ -74,7 +79,7 @@ android:layout_gravity="center" android:importantForAccessibility="no" android:src="@drawable/ic_camera" - app:visible="@{viewModel.uploadUiState}" /> + app:visibility="@{viewModel.uploadUiState.imageState == null ? view.VISIBLE : view.GONE}" /> From f0a1d14b711e962fbf090bc9369b6babf5c507be Mon Sep 17 00:00:00 2001 From: Seokyung Lim Date: Wed, 22 Nov 2023 20:16:40 +0900 Subject: [PATCH 097/165] =?UTF-8?q?chore=20:=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=ED=95=A0=20=EA=B2=BD=EB=A1=9C=20=EC=A7=80?= =?UTF-8?q?=EC=A0=95=ED=95=B4=EC=A3=BC=EB=8A=94=20=ED=95=B8=EB=93=A4?= =?UTF-8?q?=EB=9F=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/src/constants.ts | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/server/src/constants.ts b/server/src/constants.ts index f1361a5..48bfe21 100644 --- a/server/src/constants.ts +++ b/server/src/constants.ts @@ -1,6 +1,6 @@ export const fileSize: Record = { - MUSIC_FILE_LIMIT_SIZE: 1024 * 1024 * 50, - IMAGE_FILE_LIMIT_SIZE: 1024 * 1024 * 5, + MUSIC_SIZE: 1024 * 1024 * 50, + IMAGE_SIZE: 1024 * 1024 * 5, }; export enum Genres { @@ -14,10 +14,12 @@ export enum Genres { 'etc' = 'etc', } -export const fileExt = ['jpeg', 'jpg', 'png', 'mp3']; +export const keyFlags = ['user', 'music', 'cover']; -export const pathPattern = [ - /^image\/user\/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/, - /^image\/cover\/\d+$/, - /^music\/\d+$/, -]; +export const keyHandler: { + [key: string]: (uuid: string) => string; +} = { + user: (uuid) => `image/user/${uuid}/user.png`, + music: (uuid) => `music/${uuid}/music.mp3`, + cover: (uuid) => `image/cover/${uuid}/cover.png`, +}; From b90774e1fa40f1b66f123eceba62c1d703fb5b3e Mon Sep 17 00:00:00 2001 From: Seokyung Lim Date: Wed, 22 Nov 2023 20:18:43 +0900 Subject: [PATCH 098/165] =?UTF-8?q?fix=20:=20=EC=97=85=EB=A1=9C=EB=93=9C?= =?UTF-8?q?=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 기존에 musicId를 경로에 넣는 로직은 말이 안되어 클라이언트에게 uuid(musicId)를 넘겨줌 * /upload/uuid 를 추가해 uuid를 반환 --- server/package-lock.json | 7 +++++ server/package.json | 1 + server/src/upload/upload.controller.ts | 40 ++++++++++++++++++++------ server/src/upload/upload.module.ts | 3 +- server/src/upload/upload.service.ts | 39 ++++++++----------------- 5 files changed, 53 insertions(+), 37 deletions(-) diff --git a/server/package-lock.json b/server/package-lock.json index d5e2dad..0daa11e 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -40,6 +40,7 @@ "@types/passport": "^1.0.15", "@types/passport-google-oauth20": "^2.0.14", "@types/supertest": "^2.0.12", + "@types/uuid": "^9.0.7", "@typescript-eslint/eslint-plugin": "^6.0.0", "@typescript-eslint/parser": "^6.0.0", "eslint": "^8.42.0", @@ -2233,6 +2234,12 @@ "@types/superagent": "*" } }, + "node_modules/@types/uuid": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.7.tgz", + "integrity": "sha512-WUtIVRUZ9i5dYXefDEAI7sh9/O7jGvHg7Df/5O/gtH3Yabe5odI3UWopVR1qbPXQtvOxWu3mM4XxlYeZtMWF4g==", + "dev": true + }, "node_modules/@types/validator": { "version": "13.11.6", "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.11.6.tgz", diff --git a/server/package.json b/server/package.json index 876a3e3..a738267 100644 --- a/server/package.json +++ b/server/package.json @@ -51,6 +51,7 @@ "@types/passport": "^1.0.15", "@types/passport-google-oauth20": "^2.0.14", "@types/supertest": "^2.0.12", + "@types/uuid": "^9.0.7", "@typescript-eslint/eslint-plugin": "^6.0.0", "@typescript-eslint/parser": "^6.0.0", "eslint": "^8.42.0", diff --git a/server/src/upload/upload.controller.ts b/server/src/upload/upload.controller.ts index b7ee50a..c8c3a95 100644 --- a/server/src/upload/upload.controller.ts +++ b/server/src/upload/upload.controller.ts @@ -1,24 +1,46 @@ -import { Controller, Get, Query, HttpCode } from '@nestjs/common'; +import { + Controller, + Get, + Req, + Query, + HttpCode, + UseGuards, + HttpException, +} from '@nestjs/common'; import { UploadService } from './upload.service'; import { HTTP_STATUS_CODE } from 'src/httpStatusCode.enum'; +import { AuthGuard } from '@nestjs/passport'; +import { v4 } from 'uuid'; @Controller('upload') export class UploadController { constructor(private uploadService: UploadService) {} + @Get('uuid') + @UseGuards(AuthGuard()) + @HttpCode(HTTP_STATUS_CODE.SUCCESS) + getMusicUUID(): { uuid: string } { + try { + return { uuid: v4() }; + } catch (err) { + console.log(err); + throw new HttpException('SERVER ERROR', HTTP_STATUS_CODE.SERVER_ERROR); + } + } + @Get() + @UseGuards(AuthGuard()) @HttpCode(HTTP_STATUS_CODE.SUCCESS) async getSignedURL( - @Query('path') path: string, - @Query('fileName') fileName: string, - @Query('ext') ext: string, + @Query('type') type: string, + @Query('uuid') uuid: string, + @Req() req, ): Promise<{ signedUrl: string }> { try { - const signedUrl = await this.uploadService.getSignedURL( - path, - fileName, - ext, - ); + const userId = req.user.user_id; + const id = type === 'user' ? userId : uuid; + + const signedUrl = await this.uploadService.getSignedURL(type, id); return { signedUrl }; } catch (error) { diff --git a/server/src/upload/upload.module.ts b/server/src/upload/upload.module.ts index 2a777c0..d6cc864 100644 --- a/server/src/upload/upload.module.ts +++ b/server/src/upload/upload.module.ts @@ -3,9 +3,10 @@ import { UploadController } from './upload.controller'; import { UploadService } from './upload.service'; import { NcloudConfigService } from 'src/config/ncloud.config'; import { HttpModule } from '@nestjs/axios'; +import { AuthModule } from 'src/auth/auth.module'; @Module({ - imports: [HttpModule], + imports: [HttpModule, AuthModule], controllers: [UploadController], providers: [UploadService, NcloudConfigService], }) diff --git a/server/src/upload/upload.service.ts b/server/src/upload/upload.service.ts index 3d250a0..c875039 100644 --- a/server/src/upload/upload.service.ts +++ b/server/src/upload/upload.service.ts @@ -2,8 +2,7 @@ import { HttpException, Injectable } from '@nestjs/common'; import { HTTP_STATUS_CODE } from 'src/httpStatusCode.enum'; import { NcloudConfigService } from './../config/ncloud.config'; import { S3 } from 'aws-sdk'; -import { fileExt } from './../constants'; -import { pathPattern } from './../constants'; +import { fileSize, keyFlags, keyHandler } from './../constants'; @Injectable() export class UploadService { @@ -12,39 +11,25 @@ export class UploadService { this.objectStorage = nCloudConfigService.createObjectStorageOption(); } - private isValidPath(path: string, fileName: string) { - for (let i = 0; i < pathPattern.length; i++) { - if (pathPattern[i].test(path)) { - if (i < 2) return true; - if (i == 2 && fileName.length <= 50) return true; - } - } + private isValidFlag(flag: string): boolean { + if (keyFlags.includes(flag)) return true; return false; } - private isValidExt(ext: string) { - if (fileExt.includes(ext)) return true; - - return false; - } - - async getSignedURL( - keyPath: string, - fileName: string, - ext: string, - ): Promise { + async getSignedURL(flag: string, uuid: string): Promise { try { - if (!this.isValidPath(keyPath, fileName) || !this.isValidExt(ext)) - throw new HttpException('BAD REQUEST', HTTP_STATUS_CODE.BAD_REQUEST); + if (!this.isValidFlag(flag)) + throw new HttpException('BAD_REQUEST', HTTP_STATUS_CODE.BAD_REQUEST); + + const keyPath = keyHandler[flag](uuid); - const url = await this.objectStorage.getSignedUrlPromise('putObject', { + return await this.objectStorage.getSignedUrlPromise('putObject', { Bucket: 'catchy-tape-bucket2', - Key: `${keyPath}/${fileName}.${ext}`, - Expires: 60, + Key: `${keyPath}`, + Expires: 600, + ACL: 'public-read', }); - - return url; } catch (error) { if (error instanceof HttpException) throw error; From 280b0c1d1833a2f490943d373333bb91447474a8 Mon Sep 17 00:00:00 2001 From: hyungun Date: Wed, 22 Nov 2023 20:21:09 +0900 Subject: [PATCH 099/165] =?UTF-8?q?refactor=20:=20playlist=20entity=20?= =?UTF-8?q?=EB=B3=80=EC=88=98=EB=AA=85,=20=ED=83=80=EC=9E=85=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * id 의 타입 string -> number * playlistId -> playlist_id --- server/src/entity/playlist.entity.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/entity/playlist.entity.ts b/server/src/entity/playlist.entity.ts index e0ddeca..aabc850 100644 --- a/server/src/entity/playlist.entity.ts +++ b/server/src/entity/playlist.entity.ts @@ -14,7 +14,7 @@ import { Music_Playlist } from './music_playlist.entity'; @Entity({ name: 'playlist' }) export class Playlist extends BaseEntity { @PrimaryGeneratedColumn() - playlistId: string; + playlist_Id: number; @Column() playlist_title: string; From b6dd0962bc8debe1b43d92e152ded578b49dda40 Mon Sep 17 00:00:00 2001 From: hyungun Date: Wed, 22 Nov 2023 20:22:38 +0900 Subject: [PATCH 100/165] =?UTF-8?q?feat=20:=20=ED=94=8C=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EC=9D=8C=EC=95=85=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * POST /playlists/{playlist_id} 로 요청 * 해당 플레이리스트가 있는지, 음악이 있는지 확인 -> 없으면 400 에러 * 추가가 됐으면 200 응답 --- server/src/playlist/playlist.controller.ts | 24 +++++++- server/src/playlist/playlist.module.ts | 4 +- server/src/playlist/playlist.service.ts | 64 +++++++++++++++++++++- 3 files changed, 87 insertions(+), 5 deletions(-) diff --git a/server/src/playlist/playlist.controller.ts b/server/src/playlist/playlist.controller.ts index 658508a..d2220ca 100644 --- a/server/src/playlist/playlist.controller.ts +++ b/server/src/playlist/playlist.controller.ts @@ -2,6 +2,7 @@ import { Body, Controller, HttpCode, + Param, Post, Req, UseGuards, @@ -26,7 +27,28 @@ export class PlaylistController { @Body() playlistCreateDto: PlaylistCreateDto, ): Promise<{ playlist_id: number }> { const userId: string = req.user.user_id; - const playlistId: number = await this.playlistService.createPlaylist(userId, playlistCreateDto); + const playlistId: number = await this.playlistService.createPlaylist( + userId, + playlistCreateDto, + ); return { playlist_id: playlistId }; } + + @Post(':playlistId') + @UseGuards(AuthGuard()) + @HttpCode(HTTP_STATUS_CODE.SUCCESS) + async addMusicToPlaylist( + @Req() req, + @Param('playlistId') playlistId: number, + @Body('musicId') musicId: number, + ): Promise<{ music_playlist_id: number }> { + const userId: string = req.user.user_id; + const music_playlist_id: number = + await this.playlistService.addMusicToPlaylist( + userId, + playlistId, + musicId, + ); + return { music_playlist_id: music_playlist_id }; + } } diff --git a/server/src/playlist/playlist.module.ts b/server/src/playlist/playlist.module.ts index efa7d25..3e4d88a 100644 --- a/server/src/playlist/playlist.module.ts +++ b/server/src/playlist/playlist.module.ts @@ -4,9 +4,11 @@ import { PlaylistController } from './playlist.controller'; import { Playlist } from 'src/entity/playlist.entity'; import { TypeOrmModule } from '@nestjs/typeorm'; import { AuthModule } from 'src/auth/auth.module'; +import { Music_Playlist } from 'src/entity/music_playlist.entity'; +import { Music } from 'src/entity/music.entity'; @Module({ - imports: [TypeOrmModule.forFeature([Playlist]), AuthModule], + imports: [TypeOrmModule.forFeature([Playlist, Music_Playlist, Music]), AuthModule], controllers: [PlaylistController], providers: [PlaylistService], }) diff --git a/server/src/playlist/playlist.service.ts b/server/src/playlist/playlist.service.ts index 7b35932..b49a0a1 100644 --- a/server/src/playlist/playlist.service.ts +++ b/server/src/playlist/playlist.service.ts @@ -1,7 +1,10 @@ -import { Injectable } from '@nestjs/common'; +import { HttpException, Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { PlaylistCreateDto } from 'src/dto/playlistCreate.dto'; +import { Music } from 'src/entity/music.entity'; +import { Music_Playlist } from 'src/entity/music_playlist.entity'; import { Playlist } from 'src/entity/playlist.entity'; +import { HTTP_STATUS_CODE } from 'src/httpStatusCode.enum'; import { Repository } from 'typeorm'; @Injectable() @@ -9,9 +12,16 @@ export class PlaylistService { constructor( @InjectRepository(Playlist) private playlistRepository: Repository, + @InjectRepository(Music_Playlist) + private music_playlistRepository: Repository, + @InjectRepository(Music) + private MusicRepository: Repository, ) {} - async createPlaylist(userId: string, playlistCreateDto: PlaylistCreateDto): Promise { + async createPlaylist( + userId: string, + playlistCreateDto: PlaylistCreateDto, + ): Promise { const title: string = playlistCreateDto.title; const newPlaylist: Playlist = this.playlistRepository.create({ playlist_title: title, @@ -21,7 +31,55 @@ export class PlaylistService { }); const result: Playlist = await this.playlistRepository.save(newPlaylist); - const playlistId: number = parseInt(result.playlistId); + const playlistId: number = result.playlist_Id; return playlistId; } + + async addMusicToPlaylist( + userId: string, + playlistId: number, + musicId: number, + ): Promise { + // 사용자 플리가 있는지 확인 + if (!(await this.isExistPlaylistOnUser(playlistId, userId))) { + throw new HttpException( + 'NOT_EXIST_PLAYLIST_ON_USER', + HTTP_STATUS_CODE.BAD_REQUEST, + ); + } + // 음악 있는지 확인 + if (!(await this.isExistMusic(musicId))) { + throw new HttpException('NOT_EXIST_MUSIC', HTTP_STATUS_CODE.BAD_REQUEST); + } + + // 관계테이블에 추가 + const new_music_playlist: Music_Playlist = + this.music_playlistRepository.create({ + music: { musicId: musicId }, + playlist: { playlist_Id: playlistId }, + }); + + const result: Music_Playlist = + await this.music_playlistRepository.save(new_music_playlist); + return result.music_playlist_id; + } + + async isExistPlaylistOnUser( + playlistId: number, + userId: string, + ): Promise { + const playlistCount: number = await this.playlistRepository.countBy({ + playlist_Id: playlistId, + user: { user_id: userId }, + }); + return playlistCount !== 0; + } + + async isExistMusic(musicId: number): Promise { + const musicCount: number = await this.MusicRepository.countBy({ + musicId: musicId, + }); + + return musicCount !== 0; + } } From 5b062c3cd76d021613a5938222453cac7ef6d3b9 Mon Sep 17 00:00:00 2001 From: algosketch Date: Wed, 22 Nov 2023 20:22:41 +0900 Subject: [PATCH 101/165] =?UTF-8?q?fix=20:=20=EC=8B=9C=EC=9E=91=20?= =?UTF-8?q?=ED=99=94=EB=A9=B4=EC=9D=84=20=EB=8B=A4=EC=8B=9C=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=ED=99=94=EB=A9=B4=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EB=8F=8C=EB=A0=A4=20=EB=86=93=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- android/app/src/main/AndroidManifest.xml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 8b77eed..11d1934 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -15,19 +15,17 @@ + + + + - - - - - - From dcfd56029cd43f2dda964caf89ec3771930a6b2c Mon Sep 17 00:00:00 2001 From: hyungun Date: Wed, 22 Nov 2023 20:58:07 +0900 Subject: [PATCH 102/165] =?UTF-8?q?feat=20:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EC=A6=89=EC=8B=9C=20=EC=B5=9C=EA=B7=BC=20=EC=9E=AC?= =?UTF-8?q?=EC=83=9D=20=EB=AA=A9=EB=A1=9D=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * PlaylistService 를 주입하여 signup 을 한 후에 최근 재생 목록 플레이리스트를 바로 생성하도록 구현 --- server/src/auth/auth.module.ts | 8 ++++++-- server/src/auth/auth.service.ts | 5 +++++ server/src/playlist/playlist.module.ts | 6 +++++- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/server/src/auth/auth.module.ts b/server/src/auth/auth.module.ts index 37a18b2..1159b2c 100644 --- a/server/src/auth/auth.module.ts +++ b/server/src/auth/auth.module.ts @@ -7,6 +7,10 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { User } from 'src/entity/user.entity'; import { AuthController } from './auth.controller'; import { AuthService } from './auth.service'; +import { PlaylistService } from 'src/playlist/playlist.service'; +import { Playlist } from 'src/entity/playlist.entity'; +import { Music } from 'src/entity/music.entity'; +import { Music_Playlist } from 'src/entity/music_playlist.entity'; @Module({ imports: [ @@ -19,9 +23,9 @@ import { AuthService } from './auth.service'; }), inject: [ConfigService], }), - TypeOrmModule.forFeature([User]), + TypeOrmModule.forFeature([User, Playlist, Music, Music_Playlist]), ], - providers: [JwtStrategy, AuthService], + providers: [JwtStrategy, AuthService, PlaylistService], exports: [JwtStrategy, PassportModule], controllers: [AuthController], }) diff --git a/server/src/auth/auth.service.ts b/server/src/auth/auth.service.ts index c43b711..31b6da2 100644 --- a/server/src/auth/auth.service.ts +++ b/server/src/auth/auth.service.ts @@ -4,6 +4,7 @@ import { InjectRepository } from '@nestjs/typeorm'; import { UserCreateDto } from 'src/dto/userCreate.dto'; import { User } from 'src/entity/user.entity'; import { HTTP_STATUS_CODE } from 'src/httpStatusCode.enum'; +import { PlaylistService } from 'src/playlist/playlist.service'; import { Repository } from 'typeorm'; import { v4 as uuid } from 'uuid'; @@ -12,6 +13,7 @@ export class AuthService { constructor( @InjectRepository(User) private userRepository: Repository, private jwtService: JwtService, + private readonly playlistService: PlaylistService, ) {} async login(email: string): Promise<{ accessToken: string }> { @@ -49,6 +51,9 @@ export class AuthService { }); await this.userRepository.save(newUser); + this.playlistService.createPlaylist(newUser.user_id, { + title: '최근 재생 목록', + }); return this.login(email); } throw new HttpException('WRONG_TOKEN', HTTP_STATUS_CODE.WRONG_TOKEN); diff --git a/server/src/playlist/playlist.module.ts b/server/src/playlist/playlist.module.ts index 3e4d88a..b458076 100644 --- a/server/src/playlist/playlist.module.ts +++ b/server/src/playlist/playlist.module.ts @@ -8,8 +8,12 @@ import { Music_Playlist } from 'src/entity/music_playlist.entity'; import { Music } from 'src/entity/music.entity'; @Module({ - imports: [TypeOrmModule.forFeature([Playlist, Music_Playlist, Music]), AuthModule], + imports: [ + TypeOrmModule.forFeature([Playlist, Music_Playlist, Music]), + AuthModule, + ], controllers: [PlaylistController], providers: [PlaylistService], + exports: [PlaylistService], }) export class PlaylistModule {} From 3af03408736830c3c1f317ecd1e035d6b2f7a2b9 Mon Sep 17 00:00:00 2001 From: youlalala Date: Wed, 22 Nov 2023 21:11:32 +0900 Subject: [PATCH 103/165] =?UTF-8?q?feat=20:=20file=20=EC=83=81=ED=83=9C=20?= =?UTF-8?q?=EA=B4=80=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/domain/usecase/UploadMusicUseCase.kt | 13 ++ .../catchytape/core/ui/BindingAdapter.kt | 10 +- .../catchytape/core/ui/BindingAdapters.kt | 12 -- .../feature/upload/BindingAdapter.kt | 28 ----- .../feature/upload/UploadViewModel.kt | 115 +++++++++--------- .../src/main/res/layout/fragment_upload.xml | 10 +- 6 files changed, 85 insertions(+), 103 deletions(-) create mode 100644 android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/UploadMusicUseCase.kt delete mode 100644 android/core/ui/src/main/java/com/ohdodok/catchytape/core/ui/BindingAdapters.kt diff --git a/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/UploadMusicUseCase.kt b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/UploadMusicUseCase.kt new file mode 100644 index 0000000..c6a94dd --- /dev/null +++ b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/UploadMusicUseCase.kt @@ -0,0 +1,13 @@ +package com.ohdodok.catchytape.core.domain.usecase + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import javax.inject.Inject + +class UploadMusicUseCase @Inject constructor() { + + operator fun invoke(imgUrl: String, audioUrl: String, title: String, genre: String): Flow = flow { + // todo : 서버에 업로드 + emit(Unit) + } +} \ No newline at end of file diff --git a/android/core/ui/src/main/java/com/ohdodok/catchytape/core/ui/BindingAdapter.kt b/android/core/ui/src/main/java/com/ohdodok/catchytape/core/ui/BindingAdapter.kt index 8306400..54821fa 100644 --- a/android/core/ui/src/main/java/com/ohdodok/catchytape/core/ui/BindingAdapter.kt +++ b/android/core/ui/src/main/java/com/ohdodok/catchytape/core/ui/BindingAdapter.kt @@ -1,13 +1,21 @@ package com.ohdodok.catchytape.core.ui +import android.widget.ImageView import androidx.databinding.BindingAdapter import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView - +import com.bumptech.glide.Glide @BindingAdapter("submitList") fun RecyclerView.bindItems(items: List) { val adapter = this.adapter ?: return val listAdapter: ListAdapter = adapter as ListAdapter listAdapter.submitList(items) +} + +@BindingAdapter("imgUrl") +fun ImageView.bindImg(url: String) { + Glide.with(this.context) + .load(url) + .into(this) } \ No newline at end of file diff --git a/android/core/ui/src/main/java/com/ohdodok/catchytape/core/ui/BindingAdapters.kt b/android/core/ui/src/main/java/com/ohdodok/catchytape/core/ui/BindingAdapters.kt deleted file mode 100644 index fe02a20..0000000 --- a/android/core/ui/src/main/java/com/ohdodok/catchytape/core/ui/BindingAdapters.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.ohdodok.catchytape.core.ui - -import android.widget.ImageView -import androidx.databinding.BindingAdapter -import com.bumptech.glide.Glide - -@BindingAdapter("imageUrl") -fun ImageView.bindImg(url: String) { - Glide.with(this.context) - .load(url) - .into(this) -} \ No newline at end of file diff --git a/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/BindingAdapter.kt b/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/BindingAdapter.kt index b3dc905..8489ec3 100644 --- a/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/BindingAdapter.kt +++ b/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/BindingAdapter.kt @@ -3,38 +3,10 @@ package com.ohdodok.catchytape.feature.upload import android.R import android.widget.ArrayAdapter import android.widget.AutoCompleteTextView -import android.widget.Button -import android.widget.ImageView -import androidx.core.view.isVisible import androidx.databinding.BindingAdapter -import com.bumptech.glide.Glide -import com.google.android.material.progressindicator.LinearProgressIndicator @BindingAdapter("list") fun AutoCompleteTextView.setAdapter(list: List) { val adapter = ArrayAdapter(this.context, R.layout.simple_list_item_1, list) setAdapter(adapter) -} - -@BindingAdapter("visible") -fun LinearProgressIndicator.setVisible(uiState: UploadUiState) { - isVisible = uiState.audioState is UploadInputState.Loading || uiState.imageState is UploadInputState.Loading -} - -@BindingAdapter("completeBtnEnable") -fun Button.setCompleteBtnEnable(uiState: UploadUiState) { - isEnabled = - uiState.audioState is UploadInputState.Success - && uiState.imageState is UploadInputState.Success - && uiState.titleState is UploadInputState.Success - && uiState.genreState is UploadInputState.Success -} - -@BindingAdapter("uploadedThumbnail") -fun ImageView.bindUrl(uiState: UploadUiState) { - if (uiState.imageState is UploadInputState.Success) { - Glide.with(this) - .load(uiState.imageState.value) - .into(this) - } } \ No newline at end of file diff --git a/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadViewModel.kt b/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadViewModel.kt index 7053bc8..7e4848f 100644 --- a/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadViewModel.kt +++ b/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadViewModel.kt @@ -8,32 +8,56 @@ import javax.inject.Inject import androidx.lifecycle.viewModelScope import com.ohdodok.catchytape.core.domain.usecase.UploadFileUseCase import com.ohdodok.catchytape.core.domain.usecase.GetMusicGenresUseCase +import com.ohdodok.catchytape.core.domain.usecase.UploadMusicUseCase import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.flow.stateIn @HiltViewModel class UploadViewModel @Inject constructor( private val getMusicGenresUseCase: GetMusicGenresUseCase, - private val uploadFileUseCase: UploadFileUseCase + private val uploadFileUseCase: UploadFileUseCase, + private val uploadMusicUseCase: UploadMusicUseCase ) : ViewModel() { val musicTitle = MutableStateFlow("") val musicGenre = MutableStateFlow("") + private val _imageState: MutableStateFlow = MutableStateFlow(UploadedFileState()) + val imageState = _imageState.asStateFlow() + + private val _audioState: MutableStateFlow = MutableStateFlow(UploadedFileState()) + val audioState = _audioState.asStateFlow() + + val isLoading: StateFlow = combine(imageState, audioState) { imageState, audioState -> + imageState.isLoading || audioState.isLoading + }.stateIn( + scope = viewModelScope, + started = SharingStarted.Eagerly, + initialValue = false + ) + + val isUploadEnable: StateFlow = + combine(musicTitle, musicGenre, imageState, audioState + ) { title, genre, imageState, audioState -> + title.isNotBlank() + && genre.isNotBlank() + && imageState.url.isNotBlank() + && audioState.url.isNotBlank() + }.stateIn(viewModelScope, SharingStarted.Eagerly, false) + private val _musicGenres: MutableStateFlow> = MutableStateFlow(emptyList()) val musicGenres = _musicGenres.asStateFlow() - private val _uploadUiState: MutableStateFlow = MutableStateFlow(UploadUiState()) - val uploadUiState = _uploadUiState.asStateFlow() - init { fetchGenres() - observeTitle() - observeGenre() } private fun fetchGenres() { @@ -42,73 +66,50 @@ class UploadViewModel @Inject constructor( }.launchIn(viewModelScope) } - private fun observeTitle() { - musicTitle.onEach { - if (it.isEmpty()) { - _uploadUiState.value = uploadUiState.value.copy(titleState = null) - return@onEach - } else { - _uploadUiState.value = - uploadUiState.value.copy(titleState = UploadInputState.Success(value = it)) - } - }.launchIn(viewModelScope) - } - - private fun observeGenre() { - musicGenre.onEach { - if (it.isEmpty()) { - _uploadUiState.value = uploadUiState.value.copy(genreState = null) - return@onEach - } else { - _uploadUiState.value = - uploadUiState.value.copy(genreState = UploadInputState.Success(value = it)) - } - }.launchIn(viewModelScope) - } - fun uploadImage(imageUri: Uri) { imageUri.path?.let { path -> - uploadFileUseCase.getImgUrl(File(path)).onStart { - _uploadUiState.value = - uploadUiState.value.copy(imageState = UploadInputState.Loading) + uploadFileUseCase.getImgUrl(File(path)).onEach { url -> + _imageState.value = imageState.value.copy(isLoading = false, url = url) + }.onStart { + _imageState.value = imageState.value.copy(isLoading = true) }.catch { // TODO : 에러 처리 - _uploadUiState.value = - uploadUiState.value.copy(imageState = UploadInputState.Error) - }.onEach { url -> - _uploadUiState.value = - uploadUiState.value.copy(imageState = UploadInputState.Success(value = url)) + _imageState.value = imageState.value.copy(isLoading = false) }.launchIn(viewModelScope) } } fun uploadAudio(audioUri: Uri) { audioUri.path?.let { path -> - uploadFileUseCase.getAudioUrl(File(path)).onStart { - _uploadUiState.value = - uploadUiState.value.copy(audioState = UploadInputState.Loading) + uploadFileUseCase.getAudioUrl(File(path)).onEach { + _audioState.value = audioState.value.copy(isLoading = false, url = it) + }.onStart { + _audioState.value = audioState.value.copy(isLoading = true) }.catch { // TODO : 에러 처리 - _uploadUiState.value = - uploadUiState.value.copy(audioState = UploadInputState.Error) - }.onEach { - _uploadUiState.value = - uploadUiState.value.copy(audioState = UploadInputState.Success(value = it)) + _audioState.value = audioState.value.copy(isLoading = false) + }.launchIn(viewModelScope) + } + } + + fun uploadMusic() { + if (isUploadEnable.value) { + uploadMusicUseCase( + imgUrl = imageState.value.url, + audioUrl = audioState.value.url, + title = musicTitle.value, + genre = musicGenre.value + ).onEach { + // TODO : 업로드 성공 + }.catch { + // TODO : 업로드 실패 }.launchIn(viewModelScope) } } } -data class UploadUiState( - val audioState: UploadInputState? = null, - val imageState: UploadInputState? = null, - val titleState: UploadInputState? = null, - val genreState: UploadInputState? = null +data class UploadedFileState( + val isLoading: Boolean = false, + val url: String = "" ) -sealed class UploadInputState { - data object Loading : UploadInputState() - data class Success(val value: String) : UploadInputState() - data object Error : UploadInputState() -} - diff --git a/android/feature/upload/src/main/res/layout/fragment_upload.xml b/android/feature/upload/src/main/res/layout/fragment_upload.xml index 518be9c..9c988c5 100644 --- a/android/feature/upload/src/main/res/layout/fragment_upload.xml +++ b/android/feature/upload/src/main/res/layout/fragment_upload.xml @@ -35,8 +35,8 @@ android:layout_gravity="end|center_vertical" android:layout_marginEnd="@dimen/margin_horizontal" android:background="@android:color/transparent" - android:text="@string/complete" - app:completeBtnEnable="@{viewModel.uploadUiState}" /> + android:enabled="@{viewModel.isUploadEnable}" + android:text="@string/complete" /> @@ -48,7 +48,7 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/tb_upload" - app:visible="@{viewModel.uploadUiState}" /> + android:visibility="@{viewModel.isLoading ? view.VISIBLE : view.GONE}" /> + app:imgUrl="@{viewModel.imageState.url}" /> + app:visibility="@{viewModel.imageState.url.empty ? view.VISIBLE : view.GONE}" /> From 24103742444d021f059b6650017b142799e5d574 Mon Sep 17 00:00:00 2001 From: algosketch Date: Wed, 22 Nov 2023 22:10:24 +0900 Subject: [PATCH 104/165] =?UTF-8?q?refactor=20:=20authRepositoryImpl?= =?UTF-8?q?=EA=B3=BC=20data=20store=20=EC=82=AC=EC=9D=B4=EC=97=90=20dataSo?= =?UTF-8?q?urce=20=EB=86=93=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/datasource/TokenLocalDataSource.kt | 28 +++++++++++++++++++ .../data/repository/AuthRepositoryImpl.kt | 17 ++++------- 2 files changed, 33 insertions(+), 12 deletions(-) create mode 100644 android/core/data/src/main/java/com/ohdodok/catchytape/core/data/datasource/TokenLocalDataSource.kt diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/datasource/TokenLocalDataSource.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/datasource/TokenLocalDataSource.kt new file mode 100644 index 0000000..6a728a0 --- /dev/null +++ b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/datasource/TokenLocalDataSource.kt @@ -0,0 +1,28 @@ +package com.ohdodok.catchytape.core.data.datasource + +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.stringPreferencesKey +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.map +import javax.inject.Inject + +class TokenLocalDataSource @Inject constructor( + private val dataStore: DataStore +) { + + private val idTokenKey = stringPreferencesKey("idToken") + private val accessTokenKey = stringPreferencesKey("accessToken") + + suspend fun saveAccessToken(token: String) { + dataStore.edit { preferences -> preferences[accessTokenKey] = token } + } + + suspend fun saveIdToken(token: String) { + dataStore.edit { preferences -> preferences[idTokenKey] = token } + } + + suspend fun getIdToken(): String = + dataStore.data.map { preferences -> preferences[idTokenKey] ?: "" }.first() +} \ No newline at end of file diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/AuthRepositoryImpl.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/AuthRepositoryImpl.kt index 7206729..f4de1b0 100644 --- a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/AuthRepositoryImpl.kt +++ b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/AuthRepositoryImpl.kt @@ -1,10 +1,8 @@ package com.ohdodok.catchytape.core.data.repository -import androidx.datastore.core.DataStore -import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.edit -import androidx.datastore.preferences.core.stringPreferencesKey import com.ohdodok.catchytape.core.data.api.UserApi +import com.ohdodok.catchytape.core.data.datasource.TokenLocalDataSource import com.ohdodok.catchytape.core.data.model.LoginRequest import com.ohdodok.catchytape.core.data.model.SignUpRequest import com.ohdodok.catchytape.core.domain.repository.AuthRepository @@ -12,17 +10,13 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.map -import java.lang.RuntimeException import javax.inject.Inject class AuthRepositoryImpl @Inject constructor( private val userApi: UserApi, - private val preferenceDataStore: DataStore + private val tokenDataSource: TokenLocalDataSource, ) : AuthRepository { - private val idTokenKey = stringPreferencesKey("idToken") - private val accessTokenKey = stringPreferencesKey("accessToken") - override fun loginWithGoogle(googleToken: String): Flow = flow { val response = userApi.login(LoginRequest(idToken = googleToken)) if (response.isSuccessful) { @@ -51,15 +45,14 @@ class AuthRepositoryImpl @Inject constructor( override suspend fun saveAccessToken(token: String) { - preferenceDataStore.edit { preferences -> preferences[accessTokenKey] = token } + tokenDataSource.saveAccessToken(token) } override suspend fun saveIdToken(token: String) { - preferenceDataStore.edit { preferences -> preferences[idTokenKey] = token } + tokenDataSource.saveIdToken(token) } - override suspend fun getIdToken(): String = - preferenceDataStore.data.map { preferences -> preferences[idTokenKey] ?: "" }.first() + override suspend fun getIdToken(): String = tokenDataSource.getIdToken() override fun isDuplicatedNickname(nickname: String): Flow = flow { val response = userApi.verifyDuplicatedNickname(nickname = nickname) From 9cac05d9bff03045fea78167644cd2276f7a78c7 Mon Sep 17 00:00:00 2001 From: hyungun Date: Wed, 22 Nov 2023 22:26:42 +0900 Subject: [PATCH 105/165] =?UTF-8?q?feat=20:=20test=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 테스트 통과되도록 모듈 의존관계 추가 --- server/src/auth/auth.controller.spec.ts | 21 +++++++++++++++-- server/src/auth/auth.service.spec.ts | 23 +++++++++++++++++-- .../src/playlist/playlist.controller.spec.ts | 18 --------------- server/src/playlist/playlist.service.spec.ts | 18 --------------- 4 files changed, 40 insertions(+), 40 deletions(-) delete mode 100644 server/src/playlist/playlist.controller.spec.ts delete mode 100644 server/src/playlist/playlist.service.spec.ts diff --git a/server/src/auth/auth.controller.spec.ts b/server/src/auth/auth.controller.spec.ts index bbeb643..0c0504e 100644 --- a/server/src/auth/auth.controller.spec.ts +++ b/server/src/auth/auth.controller.spec.ts @@ -5,12 +5,16 @@ import { getRepositoryToken } from '@nestjs/typeorm'; import { User } from 'src/entity/user.entity'; import { Repository } from 'typeorm'; import { JwtModule, JwtService } from '@nestjs/jwt'; +import { PlaylistService } from 'src/playlist/playlist.service'; +import { Playlist } from 'src/entity/playlist.entity'; +import { Music } from 'src/entity/music.entity'; +import { Music_Playlist } from 'src/entity/music_playlist.entity'; describe('AuthController', () => { let controller: AuthController; let service: AuthService; let jwtModule: JwtModule; - let repository; + let userRepository: Repository; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ @@ -22,13 +26,26 @@ describe('AuthController', () => { provide: getRepositoryToken(User), useClass: Repository, }, + { + provide: getRepositoryToken(Playlist), + useClass: Repository, + }, + { + provide: getRepositoryToken(Music), + useClass: Repository, + }, + { + provide: getRepositoryToken(Music_Playlist), + useClass: Repository, + }, + PlaylistService, ], }).compile(); controller = module.get(AuthController); service = module.get(AuthService); jwtModule = module.get(JwtModule); - repository = module.get(getRepositoryToken(User)); + userRepository = module.get(getRepositoryToken(User)); }); it('should be defined', () => { diff --git a/server/src/auth/auth.service.spec.ts b/server/src/auth/auth.service.spec.ts index 32496c8..26cc20e 100644 --- a/server/src/auth/auth.service.spec.ts +++ b/server/src/auth/auth.service.spec.ts @@ -4,11 +4,16 @@ import { Repository } from 'typeorm'; import { getRepositoryToken } from '@nestjs/typeorm'; import { User } from 'src/entity/user.entity'; import { JwtModule, JwtService } from '@nestjs/jwt'; +import { PlaylistService } from 'src/playlist/playlist.service'; +import { Playlist } from 'src/entity/playlist.entity'; +import { Music } from 'src/entity/music.entity'; +import { Music_Playlist } from 'src/entity/music_playlist.entity'; describe('AuthService', () => { let service: AuthService; let jwtModule: JwtModule; - let repository; + let userRepository: Repository; + let playlistService: PlaylistService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ @@ -19,12 +24,26 @@ describe('AuthService', () => { provide: getRepositoryToken(User), useClass: Repository, }, + { + provide: getRepositoryToken(Playlist), + useClass: Repository, + }, + { + provide: getRepositoryToken(Music), + useClass: Repository, + }, + { + provide: getRepositoryToken(Music_Playlist), + useClass: Repository, + }, + PlaylistService, ], }).compile(); service = module.get(AuthService); jwtModule = module.get(JwtModule); - repository = module.get(getRepositoryToken(User)); + userRepository = module.get(getRepositoryToken(User)); + playlistService = module.get(PlaylistService); }); it('should be defined', () => { diff --git a/server/src/playlist/playlist.controller.spec.ts b/server/src/playlist/playlist.controller.spec.ts deleted file mode 100644 index dbc64d1..0000000 --- a/server/src/playlist/playlist.controller.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { PlaylistController } from './playlist.controller'; - -describe('PlaylistController', () => { - let controller: PlaylistController; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - controllers: [PlaylistController], - }).compile(); - - controller = module.get(PlaylistController); - }); - - it('should be defined', () => { - expect(controller).toBeDefined(); - }); -}); diff --git a/server/src/playlist/playlist.service.spec.ts b/server/src/playlist/playlist.service.spec.ts deleted file mode 100644 index 5dc1bfb..0000000 --- a/server/src/playlist/playlist.service.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { PlaylistService } from './playlist.service'; - -describe('PlaylistService', () => { - let service: PlaylistService; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [PlaylistService], - }).compile(); - - service = module.get(PlaylistService); - }); - - it('should be defined', () => { - expect(service).toBeDefined(); - }); -}); From b2461a4d58f213f4cd4c16132016846f34cadebc Mon Sep 17 00:00:00 2001 From: hyungun Date: Wed, 22 Nov 2023 22:29:46 +0900 Subject: [PATCH 106/165] =?UTF-8?q?refactor=20:=20=EC=83=81=EC=88=98=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * "최근 재생 목록" -> 상수로 처리 --- server/src/auth/auth.service.ts | 3 ++- server/src/constants.ts | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/server/src/auth/auth.service.ts b/server/src/auth/auth.service.ts index 31b6da2..f49d14f 100644 --- a/server/src/auth/auth.service.ts +++ b/server/src/auth/auth.service.ts @@ -1,6 +1,7 @@ import { HttpException, Injectable } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; import { InjectRepository } from '@nestjs/typeorm'; +import { RECENT_PLAYLIST_NAME } from 'src/constants'; import { UserCreateDto } from 'src/dto/userCreate.dto'; import { User } from 'src/entity/user.entity'; import { HTTP_STATUS_CODE } from 'src/httpStatusCode.enum'; @@ -52,7 +53,7 @@ export class AuthService { await this.userRepository.save(newUser); this.playlistService.createPlaylist(newUser.user_id, { - title: '최근 재생 목록', + title: RECENT_PLAYLIST_NAME, }); return this.login(email); } diff --git a/server/src/constants.ts b/server/src/constants.ts index a994751..51739f8 100644 --- a/server/src/constants.ts +++ b/server/src/constants.ts @@ -13,3 +13,5 @@ export enum Genres { 'dance' = 'dance', 'etc' = 'etc', } + +export const RECENT_PLAYLIST_NAME = '최근 재생 목록'; From d577bd8ed4328da23d1a8b0d95f86044a68d1ba4 Mon Sep 17 00:00:00 2001 From: youlalala Date: Wed, 22 Nov 2023 23:03:36 +0900 Subject: [PATCH 107/165] =?UTF-8?q?feat=20:=20flow=20=EC=B2=98=EB=A6=AC=20?= =?UTF-8?q?=EC=88=9C=EC=84=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/upload/UploadViewModel.kt | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadViewModel.kt b/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadViewModel.kt index 7e4848f..8b574e2 100644 --- a/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadViewModel.kt +++ b/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadViewModel.kt @@ -16,6 +16,7 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onCompletion import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn @@ -68,12 +69,11 @@ class UploadViewModel @Inject constructor( fun uploadImage(imageUri: Uri) { imageUri.path?.let { path -> - uploadFileUseCase.getImgUrl(File(path)).onEach { url -> - _imageState.value = imageState.value.copy(isLoading = false, url = url) - }.onStart { + uploadFileUseCase.getImgUrl(File(path)).onStart { _imageState.value = imageState.value.copy(isLoading = true) - }.catch { - // TODO : 에러 처리 + }.onEach { url -> + _imageState.value = imageState.value.copy(url = url) + }.onCompletion { _imageState.value = imageState.value.copy(isLoading = false) }.launchIn(viewModelScope) } @@ -81,12 +81,11 @@ class UploadViewModel @Inject constructor( fun uploadAudio(audioUri: Uri) { audioUri.path?.let { path -> - uploadFileUseCase.getAudioUrl(File(path)).onEach { - _audioState.value = audioState.value.copy(isLoading = false, url = it) - }.onStart { + uploadFileUseCase.getAudioUrl(File(path)).onStart { _audioState.value = audioState.value.copy(isLoading = true) - }.catch { - // TODO : 에러 처리 + }.onEach { url -> + _audioState.value = audioState.value.copy(url = url) + }.onCompletion { _audioState.value = audioState.value.copy(isLoading = false) }.launchIn(viewModelScope) } From 494020bae01943ce66926c4d6e1c186c495a12ce Mon Sep 17 00:00:00 2001 From: Seokyung Lim Date: Wed, 22 Nov 2023 23:05:14 +0900 Subject: [PATCH 108/165] =?UTF-8?q?chore=20:=20axios=20=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=20HttpModule=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 일부러 빼둔 axios 모듈을 이곳에서 넣어주어 CD에서 문제가 발생. --- server/src/upload/upload.module.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/server/src/upload/upload.module.ts b/server/src/upload/upload.module.ts index d6cc864..2fed54e 100644 --- a/server/src/upload/upload.module.ts +++ b/server/src/upload/upload.module.ts @@ -2,11 +2,10 @@ import { Module } from '@nestjs/common'; import { UploadController } from './upload.controller'; import { UploadService } from './upload.service'; import { NcloudConfigService } from 'src/config/ncloud.config'; -import { HttpModule } from '@nestjs/axios'; import { AuthModule } from 'src/auth/auth.module'; @Module({ - imports: [HttpModule, AuthModule], + imports: [AuthModule], controllers: [UploadController], providers: [UploadService, NcloudConfigService], }) From e22cff0feba1fdc43d20d8ff6e4a4d1d19045c28 Mon Sep 17 00:00:00 2001 From: Seokyung Lim Date: Wed, 22 Nov 2023 23:05:52 +0900 Subject: [PATCH 109/165] =?UTF-8?q?chore=20:=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=97=85=EB=A1=9C=EB=93=9C=20=EC=8B=9C=20uuid=20=ED=8C=A8?= =?UTF-8?q?=ED=84=B4=20=EA=B2=80=EC=A6=9D=20=ED=95=A8=EC=88=98=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/src/upload/upload.service.ts | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/server/src/upload/upload.service.ts b/server/src/upload/upload.service.ts index c875039..c77d73f 100644 --- a/server/src/upload/upload.service.ts +++ b/server/src/upload/upload.service.ts @@ -2,7 +2,7 @@ import { HttpException, Injectable } from '@nestjs/common'; import { HTTP_STATUS_CODE } from 'src/httpStatusCode.enum'; import { NcloudConfigService } from './../config/ncloud.config'; import { S3 } from 'aws-sdk'; -import { fileSize, keyFlags, keyHandler } from './../constants'; +import { keyFlags, keyHandler } from './../constants'; @Injectable() export class UploadService { @@ -17,12 +17,24 @@ export class UploadService { return false; } - async getSignedURL(flag: string, uuid: string): Promise { + private isValidUUIDPattern(uuid: string): boolean { + const uuidPattern = + /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$/; + + if (uuidPattern.test(uuid)) return true; + + return false; + } + + async getSignedURL(type: string, uuid: string): Promise { try { - if (!this.isValidFlag(flag)) - throw new HttpException('BAD_REQUEST', HTTP_STATUS_CODE.BAD_REQUEST); + if (!this.isValidUUIDPattern(uuid) || !this.isValidFlag(type)) + throw new HttpException( + 'INVALID_INPUT_VALUE', + HTTP_STATUS_CODE.BAD_REQUEST, + ); - const keyPath = keyHandler[flag](uuid); + const keyPath = keyHandler[type](uuid); return await this.objectStorage.getSignedUrlPromise('putObject', { Bucket: 'catchy-tape-bucket2', From 1dfbf3b1657d76112e28b37761800f1bed12789c Mon Sep 17 00:00:00 2001 From: youlalala Date: Wed, 22 Nov 2023 23:18:47 +0900 Subject: [PATCH 110/165] =?UTF-8?q?refactor=20:=20file=20=EB=B3=80?= =?UTF-8?q?=ED=99=98=20=EC=9C=84=EC=B9=98=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/upload/UploadFragment.kt | 5 ++- .../feature/upload/UploadViewModel.kt | 45 +++++++++---------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadFragment.kt b/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadFragment.kt index 3253885..b82c3aa 100644 --- a/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadFragment.kt +++ b/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadFragment.kt @@ -12,6 +12,7 @@ import com.ohdodok.catchytape.catchytape.upload.R import com.ohdodok.catchytape.catchytape.upload.databinding.FragmentUploadBinding import com.ohdodok.catchytape.core.ui.BaseFragment import dagger.hilt.android.AndroidEntryPoint +import java.io.File @AndroidEntryPoint class UploadFragment : BaseFragment(R.layout.fragment_upload) { @@ -19,14 +20,14 @@ class UploadFragment : BaseFragment(R.layout.fragment_upl private val imagePickerLauncher = registerForActivityResult(PickVisualMedia()) { uri -> if (uri == null) return@registerForActivityResult - viewModel.uploadImage(uri) + uri.path?.let { viewModel.uploadImage(File(it)) } } private val filePickerLauncher = registerForActivityResult(ActivityResultContracts.GetContent()) { uri -> if (uri == null) return@registerForActivityResult binding.btnFile.text = getFileName(uri) - viewModel.uploadAudio(uri) + uri.path?.let { viewModel.uploadAudio(File(it)) } } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { diff --git a/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadViewModel.kt b/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadViewModel.kt index 8b574e2..e8b66ad 100644 --- a/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadViewModel.kt +++ b/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadViewModel.kt @@ -31,10 +31,12 @@ class UploadViewModel @Inject constructor( val musicTitle = MutableStateFlow("") val musicGenre = MutableStateFlow("") - private val _imageState: MutableStateFlow = MutableStateFlow(UploadedFileState()) + private val _imageState: MutableStateFlow = + MutableStateFlow(UploadedFileState()) val imageState = _imageState.asStateFlow() - private val _audioState: MutableStateFlow = MutableStateFlow(UploadedFileState()) + private val _audioState: MutableStateFlow = + MutableStateFlow(UploadedFileState()) val audioState = _audioState.asStateFlow() val isLoading: StateFlow = combine(imageState, audioState) { imageState, audioState -> @@ -46,7 +48,8 @@ class UploadViewModel @Inject constructor( ) val isUploadEnable: StateFlow = - combine(musicTitle, musicGenre, imageState, audioState + combine( + musicTitle, musicGenre, imageState, audioState ) { title, genre, imageState, audioState -> title.isNotBlank() && genre.isNotBlank() @@ -67,28 +70,24 @@ class UploadViewModel @Inject constructor( }.launchIn(viewModelScope) } - fun uploadImage(imageUri: Uri) { - imageUri.path?.let { path -> - uploadFileUseCase.getImgUrl(File(path)).onStart { - _imageState.value = imageState.value.copy(isLoading = true) - }.onEach { url -> - _imageState.value = imageState.value.copy(url = url) - }.onCompletion { - _imageState.value = imageState.value.copy(isLoading = false) - }.launchIn(viewModelScope) - } + fun uploadImage(imageFile: File) { + uploadFileUseCase.getImgUrl(imageFile).onStart { + _imageState.value = imageState.value.copy(isLoading = true) + }.onEach { url -> + _imageState.value = imageState.value.copy(url = url) + }.onCompletion { + _imageState.value = imageState.value.copy(isLoading = false) + }.launchIn(viewModelScope) } - fun uploadAudio(audioUri: Uri) { - audioUri.path?.let { path -> - uploadFileUseCase.getAudioUrl(File(path)).onStart { - _audioState.value = audioState.value.copy(isLoading = true) - }.onEach { url -> - _audioState.value = audioState.value.copy(url = url) - }.onCompletion { - _audioState.value = audioState.value.copy(isLoading = false) - }.launchIn(viewModelScope) - } + fun uploadAudio(audioFile: File) { + uploadFileUseCase.getAudioUrl(audioFile).onStart { + _audioState.value = audioState.value.copy(isLoading = true) + }.onEach { url -> + _audioState.value = audioState.value.copy(url = url) + }.onCompletion { + _audioState.value = audioState.value.copy(isLoading = false) + }.launchIn(viewModelScope) } fun uploadMusic() { From 1a84976234945e68b66091a6e1bd0262864eb11e Mon Sep 17 00:00:00 2001 From: algosketch Date: Wed, 22 Nov 2023 23:50:49 +0900 Subject: [PATCH 111/165] =?UTF-8?q?feat=20:=20auth=20interceptor=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/datasource/TokenLocalDataSource.kt | 9 ++++-- .../catchytape/core/data/di/NetworkModule.kt | 32 ++++++++++++++++--- .../core/data/di/qualifier/interceptor.kt | 6 ++++ .../catchytape/core/data/store/TokenStore.kt | 13 ++++++++ 4 files changed, 53 insertions(+), 7 deletions(-) create mode 100644 android/core/data/src/main/java/com/ohdodok/catchytape/core/data/di/qualifier/interceptor.kt create mode 100644 android/core/data/src/main/java/com/ohdodok/catchytape/core/data/store/TokenStore.kt diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/datasource/TokenLocalDataSource.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/datasource/TokenLocalDataSource.kt index 6a728a0..346a728 100644 --- a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/datasource/TokenLocalDataSource.kt +++ b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/datasource/TokenLocalDataSource.kt @@ -15,6 +15,12 @@ class TokenLocalDataSource @Inject constructor( private val idTokenKey = stringPreferencesKey("idToken") private val accessTokenKey = stringPreferencesKey("accessToken") + suspend fun getAccessToken(): String = + dataStore.data.map { preferences -> preferences[accessTokenKey] ?: "" }.first() + + suspend fun getIdToken(): String = + dataStore.data.map { preferences -> preferences[idTokenKey] ?: "" }.first() + suspend fun saveAccessToken(token: String) { dataStore.edit { preferences -> preferences[accessTokenKey] = token } } @@ -22,7 +28,4 @@ class TokenLocalDataSource @Inject constructor( suspend fun saveIdToken(token: String) { dataStore.edit { preferences -> preferences[idTokenKey] = token } } - - suspend fun getIdToken(): String = - dataStore.data.map { preferences -> preferences[idTokenKey] ?: "" }.first() } \ No newline at end of file diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/di/NetworkModule.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/di/NetworkModule.kt index 3f759e7..2a555c2 100644 --- a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/di/NetworkModule.kt +++ b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/di/NetworkModule.kt @@ -2,11 +2,14 @@ package com.ohdodok.catchytape.core.data.di import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory import com.ohdodok.catchytape.core.data.BuildConfig +import com.ohdodok.catchytape.core.data.di.qualifier.AuthInterceptor +import com.ohdodok.catchytape.core.data.store.TokenStore import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import kotlinx.serialization.json.Json +import okhttp3.Interceptor import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor @@ -14,20 +17,41 @@ import retrofit2.Retrofit import timber.log.Timber import javax.inject.Singleton - @Module @InstallIn(SingletonComponent::class) object NetworkModule { + @AuthInterceptor + @Singleton + @Provides + fun provideAuthInterceptor(tokenStore: TokenStore): Interceptor { + + return Interceptor { chain -> + val newRequest = chain.request().newBuilder() + .addHeader("Authorization", tokenStore.token) + .build() + + chain.proceed(newRequest) + } + } + @Singleton @Provides - fun provideOkHttpClient(): OkHttpClient { + fun provideLoggingInterceptor(): HttpLoggingInterceptor { val logger = HttpLoggingInterceptor.Logger { message -> Timber.tag("okHttp").d(message) } - val httpInterceptor = HttpLoggingInterceptor(logger) + return HttpLoggingInterceptor(logger) .setLevel(HttpLoggingInterceptor.Level.BODY) + } + @Singleton + @Provides + fun provideOkHttpClient( + loggingInterceptor: HttpLoggingInterceptor, + @AuthInterceptor authInterceptor: Interceptor, + ): OkHttpClient { return OkHttpClient.Builder() - .addInterceptor(httpInterceptor) + .addInterceptor(loggingInterceptor) + .addInterceptor(authInterceptor) .build() } diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/di/qualifier/interceptor.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/di/qualifier/interceptor.kt new file mode 100644 index 0000000..52fffef --- /dev/null +++ b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/di/qualifier/interceptor.kt @@ -0,0 +1,6 @@ +package com.ohdodok.catchytape.core.data.di.qualifier + +import javax.inject.Qualifier + +@Qualifier +annotation class AuthInterceptor diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/store/TokenStore.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/store/TokenStore.kt new file mode 100644 index 0000000..172a271 --- /dev/null +++ b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/store/TokenStore.kt @@ -0,0 +1,13 @@ +package com.ohdodok.catchytape.core.data.store + +import javax.inject.Inject + +class TokenStore @Inject constructor() { + + var token: String = "" + private set + + fun updateToken(newToken: String) { + token = newToken + } +} \ No newline at end of file From 11d3faae3d08d9ce6099982b23756769ef7132f3 Mon Sep 17 00:00:00 2001 From: algosketch Date: Thu, 23 Nov 2023 00:54:23 +0900 Subject: [PATCH 112/165] =?UTF-8?q?feat=20:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= =?UTF-8?q?=EC=9D=84=20=EC=8B=9C=EB=8F=84=ED=95=98=EB=A9=B4=20=EB=A9=94?= =?UTF-8?q?=EB=AA=A8=EB=A6=AC=EC=97=90=20token=20=EC=A0=80=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/repository/AuthRepositoryImpl.kt | 27 ++++++++++--------- .../core/domain/repository/AuthRepository.kt | 4 +-- ...seCase.kt => AutomaticallyLoginUseCase.kt} | 4 +-- .../feature/login/LoginViewModel.kt | 17 +++--------- 4 files changed, 22 insertions(+), 30 deletions(-) rename android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/{GetIdTokenUseCase.kt => AutomaticallyLoginUseCase.kt} (59%) diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/AuthRepositoryImpl.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/AuthRepositoryImpl.kt index f4de1b0..197e04d 100644 --- a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/AuthRepositoryImpl.kt +++ b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/AuthRepositoryImpl.kt @@ -1,27 +1,26 @@ package com.ohdodok.catchytape.core.data.repository -import androidx.datastore.preferences.core.edit import com.ohdodok.catchytape.core.data.api.UserApi import com.ohdodok.catchytape.core.data.datasource.TokenLocalDataSource import com.ohdodok.catchytape.core.data.model.LoginRequest import com.ohdodok.catchytape.core.data.model.SignUpRequest +import com.ohdodok.catchytape.core.data.store.TokenStore import com.ohdodok.catchytape.core.domain.repository.AuthRepository import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.flow.map import javax.inject.Inject class AuthRepositoryImpl @Inject constructor( private val userApi: UserApi, private val tokenDataSource: TokenLocalDataSource, + private val tokenStore: TokenStore, ) : AuthRepository { override fun loginWithGoogle(googleToken: String): Flow = flow { val response = userApi.login(LoginRequest(idToken = googleToken)) if (response.isSuccessful) { response.body()?.let { loginResponse -> - saveIdToken(googleToken) + tokenStore.updateToken(loginResponse.accessToken) emit(loginResponse.accessToken) } } else if (response.code() == 401) { @@ -34,7 +33,7 @@ class AuthRepositoryImpl @Inject constructor( val response = userApi.signUp(SignUpRequest(idToken = googleToken, nickname = nickname)) if (response.isSuccessful) { response.body()?.let { loginResponse -> - saveIdToken(googleToken) + tokenStore.updateToken(loginResponse.accessToken) emit(loginResponse.accessToken) } } else { @@ -43,17 +42,10 @@ class AuthRepositoryImpl @Inject constructor( } } - override suspend fun saveAccessToken(token: String) { tokenDataSource.saveAccessToken(token) } - override suspend fun saveIdToken(token: String) { - tokenDataSource.saveIdToken(token) - } - - override suspend fun getIdToken(): String = tokenDataSource.getIdToken() - override fun isDuplicatedNickname(nickname: String): Flow = flow { val response = userApi.verifyDuplicatedNickname(nickname = nickname) @@ -63,4 +55,15 @@ class AuthRepositoryImpl @Inject constructor( else -> throw RuntimeException("네트워크 에러") // fixme : 예외 처리 로직이 정해지면 수정 } } + + override suspend fun tryLoginAutomatically(): Boolean { + val accessToken = tokenDataSource.getAccessToken() + + return if(accessToken.isNotBlank()) { + tokenStore.updateToken(accessToken) + true + } else { + false + } + } } \ No newline at end of file diff --git a/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/repository/AuthRepository.kt b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/repository/AuthRepository.kt index 4189825..24a75d4 100644 --- a/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/repository/AuthRepository.kt +++ b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/repository/AuthRepository.kt @@ -10,9 +10,7 @@ interface AuthRepository { suspend fun saveAccessToken(token: String) - suspend fun saveIdToken(token: String) - - suspend fun getIdToken(): String + suspend fun tryLoginAutomatically(): Boolean fun isDuplicatedNickname(nickname: String): Flow diff --git a/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/GetIdTokenUseCase.kt b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/AutomaticallyLoginUseCase.kt similarity index 59% rename from android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/GetIdTokenUseCase.kt rename to android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/AutomaticallyLoginUseCase.kt index 83db5a2..27ec79c 100644 --- a/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/GetIdTokenUseCase.kt +++ b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/AutomaticallyLoginUseCase.kt @@ -3,8 +3,8 @@ package com.ohdodok.catchytape.core.domain.usecase import com.ohdodok.catchytape.core.domain.repository.AuthRepository import javax.inject.Inject -class GetIdTokenUseCase @Inject constructor( +class AutomaticallyLoginUseCase @Inject constructor( private val authRepository: AuthRepository ) { - suspend operator fun invoke() = authRepository.getIdToken() + suspend operator fun invoke(): Boolean = authRepository.tryLoginAutomatically() } \ No newline at end of file diff --git a/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/LoginViewModel.kt b/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/LoginViewModel.kt index fb311a2..67267d1 100644 --- a/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/LoginViewModel.kt +++ b/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/LoginViewModel.kt @@ -2,10 +2,9 @@ package com.ohdodok.catchytape.feature.login import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.ohdodok.catchytape.core.domain.usecase.GetIdTokenUseCase +import com.ohdodok.catchytape.core.domain.usecase.AutomaticallyLoginUseCase import com.ohdodok.catchytape.core.domain.usecase.LoginUseCase import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.catch @@ -17,8 +16,7 @@ import javax.inject.Inject @HiltViewModel class LoginViewModel @Inject constructor( private val loginUseCase: LoginUseCase, - private val tokenUseCase: GetIdTokenUseCase - + private val automaticallyLoginUseCase: AutomaticallyLoginUseCase ) : ViewModel() { private val _events = MutableSharedFlow() @@ -27,7 +25,6 @@ class LoginViewModel @Inject constructor( var isAutoLoginFinished: Boolean = false private set - fun login(token: String, isAutoLogin: Boolean = false) { loginUseCase(token) .catch { @@ -39,18 +36,12 @@ class LoginViewModel @Inject constructor( }.launchIn(viewModelScope) } - fun automaticallyLogin() { viewModelScope.launch { - val idToken = tokenUseCase() - if (idToken.isNotEmpty()) { - login(idToken, true) - } - delay(1000) - isAutoLoginFinished = true + val isLoggedIn = automaticallyLoginUseCase() + if (isLoggedIn) _events.emit(LoginEvent.NavigateToHome) } } - } sealed interface LoginEvent { From 70dfa3490a84bbbf51d8f7e1905417b30d161116 Mon Sep 17 00:00:00 2001 From: algosketch Date: Thu, 23 Nov 2023 01:01:01 +0900 Subject: [PATCH 113/165] =?UTF-8?q?feat=20:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=EC=9D=84=20=EC=99=84=EB=A3=8C=ED=95=98=EB=A9=B4=20?= =?UTF-8?q?=ED=99=88=20=ED=99=94=EB=A9=B4=EC=9C=BC=EB=A1=9C=20=EC=9D=B4?= =?UTF-8?q?=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/ohdodok/catchytape/feature/login/LoginFragment.kt | 4 +--- .../com/ohdodok/catchytape/feature/login/NicknameFragment.kt | 5 +++++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/LoginFragment.kt b/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/LoginFragment.kt index 72c7f4e..66f72a0 100644 --- a/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/LoginFragment.kt +++ b/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/LoginFragment.kt @@ -21,8 +21,6 @@ class LoginFragment : BaseFragment(R.layout.fragment_login private val viewModel: LoginViewModel by activityViewModels() - private val loginActivity by lazy { activity as LoginActivity } - private val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN) .requestIdToken(BuildConfig.GOOGLE_CLIENT_ID) .requestEmail() @@ -58,7 +56,7 @@ class LoginFragment : BaseFragment(R.layout.fragment_login val intent = Intent() intent.component = ComponentName("com.ohdodok.catchytape", "com.ohdodok.catchytape.MainActivity") startActivity(intent) - loginActivity.finish() + activity?.finish() } is LoginEvent.NavigateToNickName -> { diff --git a/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/NicknameFragment.kt b/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/NicknameFragment.kt index e2fc9ce..bfc54ab 100644 --- a/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/NicknameFragment.kt +++ b/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/NicknameFragment.kt @@ -1,5 +1,7 @@ package com.ohdodok.catchytape.feature.login +import android.content.ComponentName +import android.content.Intent import android.os.Bundle import android.view.View import android.widget.TextView @@ -41,6 +43,9 @@ class NicknameFragment : BaseFragment(R.layout.fragment viewModel.events.collect { event -> when (event) { is NicknameEvent.NavigateToHome -> { + val intent = Intent() + intent.component = ComponentName("com.ohdodok.catchytape", "com.ohdodok.catchytape.MainActivity") + startActivity(intent) activity?.finish() } } From f4f4cf4f18a0c1a40cc40bcadbe3cea4d364c909 Mon Sep 17 00:00:00 2001 From: algosketch Date: Thu, 23 Nov 2023 01:31:46 +0900 Subject: [PATCH 114/165] =?UTF-8?q?fix=20:=20=EB=A6=AC=ED=8C=A9=ED=84=B0?= =?UTF-8?q?=EB=A7=81=20=EA=B3=BC=EC=A0=95=EC=97=90=EC=84=9C=20=EC=83=9D?= =?UTF-8?q?=EA=B8=B4=20=EB=AC=B4=ED=95=9C=20=EC=8A=A4=ED=94=8C=EB=9E=98?= =?UTF-8?q?=EC=8B=9C=20=EB=B2=84=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/ohdodok/catchytape/feature/login/LoginViewModel.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/LoginViewModel.kt b/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/LoginViewModel.kt index 67267d1..c065a53 100644 --- a/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/LoginViewModel.kt +++ b/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/LoginViewModel.kt @@ -40,6 +40,7 @@ class LoginViewModel @Inject constructor( viewModelScope.launch { val isLoggedIn = automaticallyLoginUseCase() if (isLoggedIn) _events.emit(LoginEvent.NavigateToHome) + isAutoLoginFinished = true } } } From 876b0c4b200a951305180e1fcdad7a44f19c7763 Mon Sep 17 00:00:00 2001 From: algosketch Date: Thu, 23 Nov 2023 01:46:59 +0900 Subject: [PATCH 115/165] =?UTF-8?q?fix=20:=20token=EC=97=90=20bearer=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/ohdodok/catchytape/core/data/di/NetworkModule.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/di/NetworkModule.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/di/NetworkModule.kt index 2a555c2..eaeaaa5 100644 --- a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/di/NetworkModule.kt +++ b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/di/NetworkModule.kt @@ -28,7 +28,7 @@ object NetworkModule { return Interceptor { chain -> val newRequest = chain.request().newBuilder() - .addHeader("Authorization", tokenStore.token) + .addHeader("Authorization", "Bearer ${tokenStore.token}") .build() chain.proceed(newRequest) From a4da1fabcad4bb7707b7a758f279e774ece0150b Mon Sep 17 00:00:00 2001 From: algosketch Date: Thu, 23 Nov 2023 01:54:24 +0900 Subject: [PATCH 116/165] =?UTF-8?q?feat=20:=20=EC=9C=A0=ED=9A=A8=20token?= =?UTF-8?q?=20=EA=B2=80=EC=82=AC=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/ohdodok/catchytape/core/data/api/UserApi.kt | 6 ++++++ .../core/data/repository/AuthRepositoryImpl.kt | 9 +++------ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/api/UserApi.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/api/UserApi.kt index 7038dd0..659df72 100644 --- a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/api/UserApi.kt +++ b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/api/UserApi.kt @@ -7,6 +7,7 @@ import com.ohdodok.catchytape.core.data.model.SignUpRequest import retrofit2.Response import retrofit2.http.Body import retrofit2.http.GET +import retrofit2.http.Header import retrofit2.http.POST import retrofit2.http.Path @@ -26,4 +27,9 @@ interface UserApi { suspend fun verifyDuplicatedNickname( @Path("nickname") nickname: String, ): Response + + @GET("users/verify") + suspend fun verify( + @Header("Authorization") accessToken: String, + ): Response } \ No newline at end of file diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/AuthRepositoryImpl.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/AuthRepositoryImpl.kt index 197e04d..11cdd8e 100644 --- a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/AuthRepositoryImpl.kt +++ b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/AuthRepositoryImpl.kt @@ -59,11 +59,8 @@ class AuthRepositoryImpl @Inject constructor( override suspend fun tryLoginAutomatically(): Boolean { val accessToken = tokenDataSource.getAccessToken() - return if(accessToken.isNotBlank()) { - tokenStore.updateToken(accessToken) - true - } else { - false - } + if(accessToken.isBlank()) return false + + return userApi.verify("Bearer $accessToken").isSuccessful } } \ No newline at end of file From 1909be741a95c21db43121fa760512fe0ac380b3 Mon Sep 17 00:00:00 2001 From: algosketch Date: Thu, 23 Nov 2023 01:58:50 +0900 Subject: [PATCH 117/165] =?UTF-8?q?refactor=20:=20token=20data=20source=20?= =?UTF-8?q?=EB=8B=A4=EC=8B=9C=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/datasource/TokenLocalDataSource.kt | 31 ------------------- .../data/repository/AuthRepositoryImpl.kt | 18 ++++++++--- 2 files changed, 13 insertions(+), 36 deletions(-) delete mode 100644 android/core/data/src/main/java/com/ohdodok/catchytape/core/data/datasource/TokenLocalDataSource.kt diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/datasource/TokenLocalDataSource.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/datasource/TokenLocalDataSource.kt deleted file mode 100644 index 346a728..0000000 --- a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/datasource/TokenLocalDataSource.kt +++ /dev/null @@ -1,31 +0,0 @@ -package com.ohdodok.catchytape.core.data.datasource - -import androidx.datastore.core.DataStore -import androidx.datastore.preferences.core.Preferences -import androidx.datastore.preferences.core.edit -import androidx.datastore.preferences.core.stringPreferencesKey -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.map -import javax.inject.Inject - -class TokenLocalDataSource @Inject constructor( - private val dataStore: DataStore -) { - - private val idTokenKey = stringPreferencesKey("idToken") - private val accessTokenKey = stringPreferencesKey("accessToken") - - suspend fun getAccessToken(): String = - dataStore.data.map { preferences -> preferences[accessTokenKey] ?: "" }.first() - - suspend fun getIdToken(): String = - dataStore.data.map { preferences -> preferences[idTokenKey] ?: "" }.first() - - suspend fun saveAccessToken(token: String) { - dataStore.edit { preferences -> preferences[accessTokenKey] = token } - } - - suspend fun saveIdToken(token: String) { - dataStore.edit { preferences -> preferences[idTokenKey] = token } - } -} \ No newline at end of file diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/AuthRepositoryImpl.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/AuthRepositoryImpl.kt index 11cdd8e..2af6158 100644 --- a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/AuthRepositoryImpl.kt +++ b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/AuthRepositoryImpl.kt @@ -1,21 +1,28 @@ package com.ohdodok.catchytape.core.data.repository +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.stringPreferencesKey import com.ohdodok.catchytape.core.data.api.UserApi -import com.ohdodok.catchytape.core.data.datasource.TokenLocalDataSource import com.ohdodok.catchytape.core.data.model.LoginRequest import com.ohdodok.catchytape.core.data.model.SignUpRequest import com.ohdodok.catchytape.core.data.store.TokenStore import com.ohdodok.catchytape.core.domain.repository.AuthRepository import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.map import javax.inject.Inject class AuthRepositoryImpl @Inject constructor( private val userApi: UserApi, - private val tokenDataSource: TokenLocalDataSource, private val tokenStore: TokenStore, + private val dataStore: DataStore ) : AuthRepository { + private val accessTokenKey = stringPreferencesKey("accessToken") + override fun loginWithGoogle(googleToken: String): Flow = flow { val response = userApi.login(LoginRequest(idToken = googleToken)) if (response.isSuccessful) { @@ -43,7 +50,7 @@ class AuthRepositoryImpl @Inject constructor( } override suspend fun saveAccessToken(token: String) { - tokenDataSource.saveAccessToken(token) + dataStore.edit { preferences -> preferences[accessTokenKey] = token } } override fun isDuplicatedNickname(nickname: String): Flow = flow { @@ -57,9 +64,10 @@ class AuthRepositoryImpl @Inject constructor( } override suspend fun tryLoginAutomatically(): Boolean { - val accessToken = tokenDataSource.getAccessToken() + val accessToken = + dataStore.data.map { preferences -> preferences[accessTokenKey] ?: "" }.first() - if(accessToken.isBlank()) return false + if (accessToken.isBlank()) return false return userApi.verify("Bearer $accessToken").isSuccessful } From 7cb24449cc9ecd2a26dd503c42e94bb031dd3f03 Mon Sep 17 00:00:00 2001 From: hyungun Date: Thu, 23 Nov 2023 02:10:58 +0900 Subject: [PATCH 118/165] =?UTF-8?q?feat=20:=20=ED=94=8C=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EB=AA=A9=EB=A1=9D=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * GET /playlists 로 사용자가 가진 플레이리스트 조회 * json 형태로 응답 --- server/src/playlist/playlist.controller.ts | 12 ++++++++++++ server/src/playlist/playlist.service.ts | 14 ++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/server/src/playlist/playlist.controller.ts b/server/src/playlist/playlist.controller.ts index d2220ca..5f176f1 100644 --- a/server/src/playlist/playlist.controller.ts +++ b/server/src/playlist/playlist.controller.ts @@ -1,6 +1,7 @@ import { Body, Controller, + Get, HttpCode, Param, Post, @@ -13,6 +14,7 @@ import { PlaylistService } from './playlist.service'; import { AuthGuard } from '@nestjs/passport'; import { HTTP_STATUS_CODE } from 'src/httpStatusCode.enum'; import { PlaylistCreateDto } from 'src/dto/playlistCreate.dto'; +import { Playlist } from 'src/entity/playlist.entity'; @Controller('playlists') export class PlaylistController { @@ -51,4 +53,14 @@ export class PlaylistController { ); return { music_playlist_id: music_playlist_id }; } + + @Get() + @UseGuards(AuthGuard()) + @HttpCode(HTTP_STATUS_CODE.SUCCESS) + async getUserPlaylists(@Req() req) { + const userId: string = req.user.user_id; + const playlists: Playlist[] = + await this.playlistService.getUserPlaylists(userId); + return playlists; + } } diff --git a/server/src/playlist/playlist.service.ts b/server/src/playlist/playlist.service.ts index b49a0a1..c46c956 100644 --- a/server/src/playlist/playlist.service.ts +++ b/server/src/playlist/playlist.service.ts @@ -82,4 +82,18 @@ export class PlaylistService { return musicCount !== 0; } + + async getUserPlaylists(userId: string): Promise { + const playlists: Playlist[] = await this.playlistRepository.find({ + select: { playlist_Id: true, playlist_title: true }, + where: { + user: { user_id: userId }, + }, + order: { + updated_at: 'ASC', + }, + }); + + return playlists; + } } From 0b65866d7126682a87622b707c70f6e7ef36f66e Mon Sep 17 00:00:00 2001 From: hyungun Date: Thu, 23 Nov 2023 02:28:39 +0900 Subject: [PATCH 119/165] =?UTF-8?q?feat=20:=20=ED=94=8C=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EC=9D=8C=EC=95=85=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 음악을 추가했을 때 플레이리스트의 update_at 필드를 갱신하도록 구현 --- server/src/playlist/playlist.service.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/server/src/playlist/playlist.service.ts b/server/src/playlist/playlist.service.ts index c46c956..e1b4421 100644 --- a/server/src/playlist/playlist.service.ts +++ b/server/src/playlist/playlist.service.ts @@ -61,6 +61,7 @@ export class PlaylistService { const result: Music_Playlist = await this.music_playlistRepository.save(new_music_playlist); + this.setUpdatedAtNow(playlistId); return result.music_playlist_id; } @@ -83,6 +84,14 @@ export class PlaylistService { return musicCount !== 0; } + async setUpdatedAtNow(playlistId: number): Promise { + const targetPlaylist: Playlist = await this.playlistRepository.findOne({ + where: { playlist_Id: playlistId }, + }); + targetPlaylist.updated_at = new Date(); + this.playlistRepository.save(targetPlaylist); + } + async getUserPlaylists(userId: string): Promise { const playlists: Playlist[] = await this.playlistRepository.find({ select: { playlist_Id: true, playlist_title: true }, From 5c0a5f69445a55a38bf741848d2aeffc982a4fb4 Mon Sep 17 00:00:00 2001 From: hyungun Date: Thu, 23 Nov 2023 02:50:03 +0900 Subject: [PATCH 120/165] =?UTF-8?q?refactor=20:=20=EC=9D=8C=EC=95=85=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20API=20=EC=98=88=EC=99=B8=EC=B2=98=EB=A6=AC?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 플레이리스트에 이미 추가된 음악이라면 400 응답 --- server/src/playlist/playlist.service.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/server/src/playlist/playlist.service.ts b/server/src/playlist/playlist.service.ts index e1b4421..8101c75 100644 --- a/server/src/playlist/playlist.service.ts +++ b/server/src/playlist/playlist.service.ts @@ -52,6 +52,11 @@ export class PlaylistService { throw new HttpException('NOT_EXIST_MUSIC', HTTP_STATUS_CODE.BAD_REQUEST); } + // 이미 추가된 음악인지 확인 + if (await this.isAlreadyAdded(playlistId, musicId)) { + throw new HttpException('ALREADY_ADDED', HTTP_STATUS_CODE.BAD_REQUEST); + } + // 관계테이블에 추가 const new_music_playlist: Music_Playlist = this.music_playlistRepository.create({ @@ -65,6 +70,14 @@ export class PlaylistService { return result.music_playlist_id; } + async isAlreadyAdded(playlistId: number, musicId: number): Promise { + const count: number = await this.music_playlistRepository.countBy({ + music: { musicId: musicId }, + playlist: { playlist_Id: playlistId }, + }); + return count !== 0; + } + async isExistPlaylistOnUser( playlistId: number, userId: string, From c176318f8888a86bf45302f76e2f8973b00dc42a Mon Sep 17 00:00:00 2001 From: hyungun Date: Thu, 23 Nov 2023 04:18:39 +0900 Subject: [PATCH 121/165] =?UTF-8?q?feat=20:=20=ED=94=8C=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EC=9D=8C=EC=95=85=20=EB=AA=A9?= =?UTF-8?q?=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 플레이리스트에 있는 음악들의 정보를 응답 --- server/src/playlist/playlist.controller.ts | 14 +++++++- server/src/playlist/playlist.service.ts | 41 +++++++++++++++++++++- 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/server/src/playlist/playlist.controller.ts b/server/src/playlist/playlist.controller.ts index 5f176f1..e2e28eb 100644 --- a/server/src/playlist/playlist.controller.ts +++ b/server/src/playlist/playlist.controller.ts @@ -15,6 +15,7 @@ import { AuthGuard } from '@nestjs/passport'; import { HTTP_STATUS_CODE } from 'src/httpStatusCode.enum'; import { PlaylistCreateDto } from 'src/dto/playlistCreate.dto'; import { Playlist } from 'src/entity/playlist.entity'; +import { Music } from 'src/entity/music.entity'; @Controller('playlists') export class PlaylistController { @@ -57,10 +58,21 @@ export class PlaylistController { @Get() @UseGuards(AuthGuard()) @HttpCode(HTTP_STATUS_CODE.SUCCESS) - async getUserPlaylists(@Req() req) { + async getUserPlaylists(@Req() req): Promise { const userId: string = req.user.user_id; const playlists: Playlist[] = await this.playlistService.getUserPlaylists(userId); return playlists; } + + @Get(':playlistId') + @UseGuards(AuthGuard()) + @HttpCode(HTTP_STATUS_CODE.SUCCESS) + async getPlaylistMusics( + @Req() req, + @Param('playlistId') playlistId: number, + ): Promise { + const userId: string = req.user.user_id; + return await this.playlistService.getPlaylistMusics(userId, playlistId); + } } diff --git a/server/src/playlist/playlist.service.ts b/server/src/playlist/playlist.service.ts index 8101c75..379bd54 100644 --- a/server/src/playlist/playlist.service.ts +++ b/server/src/playlist/playlist.service.ts @@ -112,10 +112,49 @@ export class PlaylistService { user: { user_id: userId }, }, order: { - updated_at: 'ASC', + updated_at: 'DESC', }, }); return playlists; } + + async getPlaylistMusics( + userId: string, + playlistId: number, + ): Promise { + if (!(await this.isExistPlaylistOnUser(playlistId, userId))) { + throw new HttpException( + 'NOT_EXIST_PLAYLIST_ON_USER', + HTTP_STATUS_CODE.BAD_REQUEST, + ); + } + + const musics: Music[] = await this.music_playlistRepository + .find({ + relations: { + music: { user: true }, + }, + where: { + playlist: { playlist_Id: playlistId }, + }, + select: { + music: { + musicId: true, + title: true, + cover: true, + musicFile: true, + genre: true, + user: { user_id: true, nickname: true }, + }, + music_playlist_id: false, + }, + order: { + music_playlist_id: 'DESC', + }, + }) + .then((a: Music_Playlist[]) => a.map((b) => b.music)); + + return musics; + } } From d21ce84def630129fa7b3e8440295e424fffe333 Mon Sep 17 00:00:00 2001 From: hyungun Date: Thu, 23 Nov 2023 04:34:20 +0900 Subject: [PATCH 122/165] =?UTF-8?q?refactor=20:=20Active=20Record=20?= =?UTF-8?q?=ED=8C=A8=ED=84=B4=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 긴 로직에 대해서 Active Record 패턴 적용 * service 에서 가독성 증가 --- server/src/entity/music_playlist.entity.ts | 41 +++++++++++++++++++--- server/src/entity/playlist.entity.ts | 12 +++++++ server/src/playlist/playlist.service.ts | 40 ++------------------- 3 files changed, 50 insertions(+), 43 deletions(-) diff --git a/server/src/entity/music_playlist.entity.ts b/server/src/entity/music_playlist.entity.ts index a323aec..b61f019 100644 --- a/server/src/entity/music_playlist.entity.ts +++ b/server/src/entity/music_playlist.entity.ts @@ -1,4 +1,10 @@ -import { BaseEntity, Entity, JoinColumn, ManyToOne, PrimaryGeneratedColumn } from 'typeorm'; +import { + BaseEntity, + Entity, + JoinColumn, + ManyToOne, + PrimaryGeneratedColumn, +} from 'typeorm'; import { Music } from './music.entity'; import { Playlist } from './playlist.entity'; @@ -8,10 +14,35 @@ export class Music_Playlist extends BaseEntity { music_playlist_id: number; @ManyToOne(() => Music, (music) => music.music_playlist) - @JoinColumn({name: 'music_id'}) - music: Music + @JoinColumn({ name: 'music_id' }) + music: Music; @ManyToOne(() => Playlist, (playlist) => playlist.music_playlist) - @JoinColumn({name: 'playlist_id'}) - playlist: Playlist + @JoinColumn({ name: 'playlist_id' }) + playlist: Playlist; + + static async getMusicListByPlaylistId(playlistId: number): Promise { + return this.find({ + relations: { + music: { user: true }, + }, + where: { + playlist: { playlist_Id: playlistId }, + }, + select: { + music: { + musicId: true, + title: true, + cover: true, + musicFile: true, + genre: true, + user: { user_id: true, nickname: true }, + }, + music_playlist_id: false, + }, + order: { + music_playlist_id: 'DESC', + }, + }).then((a: Music_Playlist[]) => a.map((b) => b.music)); + } } diff --git a/server/src/entity/playlist.entity.ts b/server/src/entity/playlist.entity.ts index aabc850..d5d9391 100644 --- a/server/src/entity/playlist.entity.ts +++ b/server/src/entity/playlist.entity.ts @@ -31,4 +31,16 @@ export class Playlist extends BaseEntity { @OneToMany(() => Music_Playlist, (music_playlist) => music_playlist.playlist) music_playlist: Music_Playlist[]; + + static async getPlaylistsByUserId(userId: string): Promise { + return this.find({ + select: { playlist_Id: true, playlist_title: true }, + where: { + user: { user_id: userId }, + }, + order: { + updated_at: 'DESC', + }, + }); + } } diff --git a/server/src/playlist/playlist.service.ts b/server/src/playlist/playlist.service.ts index 379bd54..680cf99 100644 --- a/server/src/playlist/playlist.service.ts +++ b/server/src/playlist/playlist.service.ts @@ -106,17 +106,7 @@ export class PlaylistService { } async getUserPlaylists(userId: string): Promise { - const playlists: Playlist[] = await this.playlistRepository.find({ - select: { playlist_Id: true, playlist_title: true }, - where: { - user: { user_id: userId }, - }, - order: { - updated_at: 'DESC', - }, - }); - - return playlists; + return Playlist.getPlaylistsByUserId(userId); } async getPlaylistMusics( @@ -129,32 +119,6 @@ export class PlaylistService { HTTP_STATUS_CODE.BAD_REQUEST, ); } - - const musics: Music[] = await this.music_playlistRepository - .find({ - relations: { - music: { user: true }, - }, - where: { - playlist: { playlist_Id: playlistId }, - }, - select: { - music: { - musicId: true, - title: true, - cover: true, - musicFile: true, - genre: true, - user: { user_id: true, nickname: true }, - }, - music_playlist_id: false, - }, - order: { - music_playlist_id: 'DESC', - }, - }) - .then((a: Music_Playlist[]) => a.map((b) => b.music)); - - return musics; + return Music_Playlist.getMusicListByPlaylistId(playlistId); } } From a4251b6eb878c37e942961bcba39a45d1f2e5256 Mon Sep 17 00:00:00 2001 From: Yura Park <62279741+youlalala@users.noreply.github.com> Date: Thu, 23 Nov 2023 10:32:41 +0900 Subject: [PATCH 123/165] =?UTF-8?q?docs=20:=20README=20=EC=97=85=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b2345e1..d7c3755 100644 --- a/README.md +++ b/README.md @@ -25,13 +25,14 @@ ### 🤖 Android | Category | TechStack | 기록 | | ------------- | ------------- | ------------- | -| Architecture | Clean Architecture, Multi Module, MVVM | [프로젝트 구조](https://tral-lalala.tistory.com/126) +| Architecture | Clean Architecture, Multi Module, MVVM | [프로젝트 구조](https://tral-lalala.tistory.com/126)⎮[build-logic](https://algosketch.tistory.com/179) | DI | Hilt | | Network | Retrofit, Kotlin Serialization | [역/직렬화 라이브러리 비교](https://github.com/boostcampwm2023/and04-catchy-tape/wiki/%EC%97%AD-%EC%A7%81%EB%A0%AC%ED%99%94-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-%EB%B9%84%EA%B5%90) | Asynchronous | Coroutines, Flow | Jetpack | DataBinding, Navigation | -| CI/CD | Github Actions |[PR에 대한 단위 테스트 자동화](https://algosketch.tistory.com/178)⎮[Github Release 자동화](https://tral-lalala.tistory.com/127)⎮[Firebase App 배포 자동화](https://tral-lalala.tistory.com/128) +| CI/CD | Github Actions |[PR 단위 테스트 자동화](https://algosketch.tistory.com/178)⎮[Github Release 자동화](https://tral-lalala.tistory.com/127)⎮[Firebase App 배포 자동화](https://tral-lalala.tistory.com/128) | Test | Kotest +
그 외 기록 From d2deb7134336c74bd8f7a2b3377617bc047002dd Mon Sep 17 00:00:00 2001 From: hyungun Date: Thu, 23 Nov 2023 10:46:38 +0900 Subject: [PATCH 124/165] =?UTF-8?q?refactor=20:=20playlist.service=20?= =?UTF-8?q?=EC=98=88=EC=99=B8=EC=B2=98=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 서버 DB 조회 에러 처리 추가 --- server/src/playlist/playlist.service.ts | 112 +++++++++++++++--------- 1 file changed, 72 insertions(+), 40 deletions(-) diff --git a/server/src/playlist/playlist.service.ts b/server/src/playlist/playlist.service.ts index 680cf99..54f77a9 100644 --- a/server/src/playlist/playlist.service.ts +++ b/server/src/playlist/playlist.service.ts @@ -22,17 +22,21 @@ export class PlaylistService { userId: string, playlistCreateDto: PlaylistCreateDto, ): Promise { - const title: string = playlistCreateDto.title; - const newPlaylist: Playlist = this.playlistRepository.create({ - playlist_title: title, - created_at: new Date(), - updated_at: new Date(), - user: { user_id: userId }, - }); + try { + const title: string = playlistCreateDto.title; + const newPlaylist: Playlist = this.playlistRepository.create({ + playlist_title: title, + created_at: new Date(), + updated_at: new Date(), + user: { user_id: userId }, + }); - const result: Playlist = await this.playlistRepository.save(newPlaylist); - const playlistId: number = result.playlist_Id; - return playlistId; + const result: Playlist = await this.playlistRepository.save(newPlaylist); + const playlistId: number = result.playlist_Id; + return playlistId; + } catch { + throw new HttpException('SERVER_ERROR', HTTP_STATUS_CODE.SERVER_ERROR); + } } async addMusicToPlaylist( @@ -58,55 +62,79 @@ export class PlaylistService { } // 관계테이블에 추가 - const new_music_playlist: Music_Playlist = - this.music_playlistRepository.create({ - music: { musicId: musicId }, - playlist: { playlist_Id: playlistId }, - }); + try { + const new_music_playlist: Music_Playlist = + this.music_playlistRepository.create({ + music: { musicId: musicId }, + playlist: { playlist_Id: playlistId }, + }); - const result: Music_Playlist = - await this.music_playlistRepository.save(new_music_playlist); - this.setUpdatedAtNow(playlistId); - return result.music_playlist_id; + const result: Music_Playlist = + await this.music_playlistRepository.save(new_music_playlist); + this.setUpdatedAtNow(playlistId); + return result.music_playlist_id; + } catch { + throw new HttpException('SERVER_ERROR', HTTP_STATUS_CODE.SERVER_ERROR); + } } async isAlreadyAdded(playlistId: number, musicId: number): Promise { - const count: number = await this.music_playlistRepository.countBy({ - music: { musicId: musicId }, - playlist: { playlist_Id: playlistId }, - }); - return count !== 0; + try { + const count: number = await this.music_playlistRepository.countBy({ + music: { musicId: musicId }, + playlist: { playlist_Id: playlistId }, + }); + return count !== 0; + } catch { + throw new HttpException('SERVER_ERROR', HTTP_STATUS_CODE.SERVER_ERROR); + } } async isExistPlaylistOnUser( playlistId: number, userId: string, ): Promise { - const playlistCount: number = await this.playlistRepository.countBy({ - playlist_Id: playlistId, - user: { user_id: userId }, - }); - return playlistCount !== 0; + try { + const playlistCount: number = await this.playlistRepository.countBy({ + playlist_Id: playlistId, + user: { user_id: userId }, + }); + return playlistCount !== 0; + } catch { + throw new HttpException('SERVER_ERROR', HTTP_STATUS_CODE.SERVER_ERROR); + } } async isExistMusic(musicId: number): Promise { - const musicCount: number = await this.MusicRepository.countBy({ - musicId: musicId, - }); + try { + const musicCount: number = await this.MusicRepository.countBy({ + musicId: musicId, + }); - return musicCount !== 0; + return musicCount !== 0; + } catch { + throw new HttpException('SERVER_ERROR', HTTP_STATUS_CODE.SERVER_ERROR); + } } async setUpdatedAtNow(playlistId: number): Promise { - const targetPlaylist: Playlist = await this.playlistRepository.findOne({ - where: { playlist_Id: playlistId }, - }); - targetPlaylist.updated_at = new Date(); - this.playlistRepository.save(targetPlaylist); + try { + const targetPlaylist: Playlist = await this.playlistRepository.findOne({ + where: { playlist_Id: playlistId }, + }); + targetPlaylist.updated_at = new Date(); + this.playlistRepository.save(targetPlaylist); + } catch { + throw new HttpException('SERVER_ERROR', HTTP_STATUS_CODE.SERVER_ERROR); + } } async getUserPlaylists(userId: string): Promise { - return Playlist.getPlaylistsByUserId(userId); + try { + return Playlist.getPlaylistsByUserId(userId); + } catch { + throw new HttpException('SERVER_ERROR', HTTP_STATUS_CODE.SERVER_ERROR); + } } async getPlaylistMusics( @@ -119,6 +147,10 @@ export class PlaylistService { HTTP_STATUS_CODE.BAD_REQUEST, ); } - return Music_Playlist.getMusicListByPlaylistId(playlistId); + try { + return Music_Playlist.getMusicListByPlaylistId(playlistId); + } catch { + throw new HttpException('SERVER_ERROR', HTTP_STATUS_CODE.SERVER_ERROR); + } } } From 3b336d83bc470bdc5063788960028bcfa06ec1e5 Mon Sep 17 00:00:00 2001 From: algosketch Date: Thu, 23 Nov 2023 10:58:00 +0900 Subject: [PATCH 125/165] =?UTF-8?q?refactor=20:=20interceptor=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/data/di/qualifier/{interceptor.kt => AuthInterceptor.kt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename android/core/data/src/main/java/com/ohdodok/catchytape/core/data/di/qualifier/{interceptor.kt => AuthInterceptor.kt} (100%) diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/di/qualifier/interceptor.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/di/qualifier/AuthInterceptor.kt similarity index 100% rename from android/core/data/src/main/java/com/ohdodok/catchytape/core/data/di/qualifier/interceptor.kt rename to android/core/data/src/main/java/com/ohdodok/catchytape/core/data/di/qualifier/AuthInterceptor.kt From 8bb7c821739bb909877957ed5fae59410b2fb283 Mon Sep 17 00:00:00 2001 From: algosketch Date: Thu, 23 Nov 2023 11:09:02 +0900 Subject: [PATCH 126/165] =?UTF-8?q?fix=20:=20token=20data=20source?= =?UTF-8?q?=EB=A5=BC=20=EC=9D=B4=EC=9A=A9=ED=95=B4=20=ED=86=A0=ED=81=B0=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=20=EB=B0=8F=20=EB=B6=88=EB=9F=AC=EC=98=A4?= =?UTF-8?q?=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/datasource/TokenLocalDataSource.kt | 23 +++++++++++++++++++ .../catchytape/core/data/di/NetworkModule.kt | 8 +++++-- .../data/repository/AuthRepositoryImpl.kt | 20 ++++------------ 3 files changed, 33 insertions(+), 18 deletions(-) create mode 100644 android/core/data/src/main/java/com/ohdodok/catchytape/core/data/datasource/TokenLocalDataSource.kt diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/datasource/TokenLocalDataSource.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/datasource/TokenLocalDataSource.kt new file mode 100644 index 0000000..2bc0e49 --- /dev/null +++ b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/datasource/TokenLocalDataSource.kt @@ -0,0 +1,23 @@ +package com.ohdodok.catchytape.core.data.datasource + +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.stringPreferencesKey +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.map +import javax.inject.Inject + +class TokenLocalDataSource @Inject constructor( + private val dataStore: DataStore +) { + + private val accessTokenKey = stringPreferencesKey("accessToken") + + suspend fun getAccessToken(): String = + dataStore.data.map { preferences -> preferences[accessTokenKey] ?: "" }.first() + + suspend fun saveAccessToken(token: String) { + dataStore.edit { preferences -> preferences[accessTokenKey] = token } + } +} \ No newline at end of file diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/di/NetworkModule.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/di/NetworkModule.kt index eaeaaa5..686b9d7 100644 --- a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/di/NetworkModule.kt +++ b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/di/NetworkModule.kt @@ -2,12 +2,14 @@ package com.ohdodok.catchytape.core.data.di import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory import com.ohdodok.catchytape.core.data.BuildConfig +import com.ohdodok.catchytape.core.data.datasource.TokenLocalDataSource import com.ohdodok.catchytape.core.data.di.qualifier.AuthInterceptor import com.ohdodok.catchytape.core.data.store.TokenStore import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent +import kotlinx.coroutines.runBlocking import kotlinx.serialization.json.Json import okhttp3.Interceptor import okhttp3.MediaType.Companion.toMediaType @@ -24,11 +26,13 @@ object NetworkModule { @AuthInterceptor @Singleton @Provides - fun provideAuthInterceptor(tokenStore: TokenStore): Interceptor { + fun provideAuthInterceptor(tokenDataSource: TokenLocalDataSource): Interceptor { + + val accessToken = runBlocking { tokenDataSource.getAccessToken() } return Interceptor { chain -> val newRequest = chain.request().newBuilder() - .addHeader("Authorization", "Bearer ${tokenStore.token}") + .addHeader("Authorization", "Bearer $accessToken") .build() chain.proceed(newRequest) diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/AuthRepositoryImpl.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/AuthRepositoryImpl.kt index 2af6158..6885e01 100644 --- a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/AuthRepositoryImpl.kt +++ b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/AuthRepositoryImpl.kt @@ -1,33 +1,23 @@ package com.ohdodok.catchytape.core.data.repository -import androidx.datastore.core.DataStore -import androidx.datastore.preferences.core.Preferences -import androidx.datastore.preferences.core.edit -import androidx.datastore.preferences.core.stringPreferencesKey import com.ohdodok.catchytape.core.data.api.UserApi +import com.ohdodok.catchytape.core.data.datasource.TokenLocalDataSource import com.ohdodok.catchytape.core.data.model.LoginRequest import com.ohdodok.catchytape.core.data.model.SignUpRequest -import com.ohdodok.catchytape.core.data.store.TokenStore import com.ohdodok.catchytape.core.domain.repository.AuthRepository import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.flow.map import javax.inject.Inject class AuthRepositoryImpl @Inject constructor( private val userApi: UserApi, - private val tokenStore: TokenStore, - private val dataStore: DataStore + private val tokenDataSource: TokenLocalDataSource, ) : AuthRepository { - private val accessTokenKey = stringPreferencesKey("accessToken") - override fun loginWithGoogle(googleToken: String): Flow = flow { val response = userApi.login(LoginRequest(idToken = googleToken)) if (response.isSuccessful) { response.body()?.let { loginResponse -> - tokenStore.updateToken(loginResponse.accessToken) emit(loginResponse.accessToken) } } else if (response.code() == 401) { @@ -40,7 +30,6 @@ class AuthRepositoryImpl @Inject constructor( val response = userApi.signUp(SignUpRequest(idToken = googleToken, nickname = nickname)) if (response.isSuccessful) { response.body()?.let { loginResponse -> - tokenStore.updateToken(loginResponse.accessToken) emit(loginResponse.accessToken) } } else { @@ -50,7 +39,7 @@ class AuthRepositoryImpl @Inject constructor( } override suspend fun saveAccessToken(token: String) { - dataStore.edit { preferences -> preferences[accessTokenKey] = token } + tokenDataSource.saveAccessToken(token) } override fun isDuplicatedNickname(nickname: String): Flow = flow { @@ -64,8 +53,7 @@ class AuthRepositoryImpl @Inject constructor( } override suspend fun tryLoginAutomatically(): Boolean { - val accessToken = - dataStore.data.map { preferences -> preferences[accessTokenKey] ?: "" }.first() + val accessToken = tokenDataSource.getAccessToken() if (accessToken.isBlank()) return false From cea7ab657b29fd138b70d621984c5aa44141df57 Mon Sep 17 00:00:00 2001 From: youlalala Date: Thu, 23 Nov 2023 11:43:13 +0900 Subject: [PATCH 127/165] =?UTF-8?q?feat=20:=20=EC=97=85=EB=A1=9C=EB=93=9C?= =?UTF-8?q?=20=EC=84=9C=EB=B2=84=20=ED=86=B5=EC=8B=A0=20=EB=A1=9C=EC=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../catchytape/core/data/api/UploadApi.kt | 32 +++++++---- .../core/data/di/RepositoryModule.kt | 5 ++ .../core/data/model/UuidResponse.kt | 5 ++ .../core/data/repository/UrlRepositoryImpl.kt | 54 +++++++++++++++++++ .../core/domain/repository/UrlRepository.kt | 14 +++++ .../core/domain/usecase/UploadFileUseCase.kt | 19 +++---- 6 files changed, 109 insertions(+), 20 deletions(-) create mode 100644 android/core/data/src/main/java/com/ohdodok/catchytape/core/data/model/UuidResponse.kt create mode 100644 android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/UrlRepositoryImpl.kt create mode 100644 android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/repository/UrlRepository.kt diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/api/UploadApi.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/api/UploadApi.kt index 902ee7f..90e5613 100644 --- a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/api/UploadApi.kt +++ b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/api/UploadApi.kt @@ -1,20 +1,34 @@ package com.ohdodok.catchytape.core.data.api import com.ohdodok.catchytape.core.data.model.UrlResponse +import com.ohdodok.catchytape.core.data.model.UuidResponse +import kotlinx.serialization.SerialName +import okhttp3.MultipartBody import retrofit2.Response -import retrofit2.http.Headers -import retrofit2.http.POST +import retrofit2.http.GET +import retrofit2.http.Multipart +import retrofit2.http.Part +import retrofit2.http.Query interface UploadApi { - @POST("upload/music") - @Headers("Content-Type: audio/mpeg") - suspend fun uploadMusic( - ): Response + @GET("upload/uuid") + suspend fun getUuid( + ): Response - @POST("upload/image") - @Headers("Content-Type: image/png") - suspend fun uploadImage( + @GET("upload") + @Multipart + suspend fun getUrl( + @Part file: MultipartBody.Part, + @Query("uuid") uuid: String, + @Query("type") type: FileType, ): Response +} + +enum class FileType { + @SerialName("music") + MUSIC, + @SerialName("cover") + COVER } \ No newline at end of file diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/di/RepositoryModule.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/di/RepositoryModule.kt index 0b3179a..b58ac30 100644 --- a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/di/RepositoryModule.kt +++ b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/di/RepositoryModule.kt @@ -2,8 +2,10 @@ package com.ohdodok.catchytape.core.data.di import com.ohdodok.catchytape.core.data.repository.AuthRepositoryImpl import com.ohdodok.catchytape.core.data.repository.MusicRepositoryImpl +import com.ohdodok.catchytape.core.data.repository.UrlRepositoryImpl import com.ohdodok.catchytape.core.domain.repository.AuthRepository import com.ohdodok.catchytape.core.domain.repository.MusicRepository +import com.ohdodok.catchytape.core.domain.repository.UrlRepository import dagger.Binds import dagger.Module import dagger.hilt.InstallIn @@ -18,4 +20,7 @@ interface RepositoryModule { @Binds fun bindMusicRepository(musicRepositoryImpl: MusicRepositoryImpl): MusicRepository + + @Binds + fun bindUrlRepository(urlRepositoryImpl: UrlRepositoryImpl): UrlRepository } \ No newline at end of file diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/model/UuidResponse.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/model/UuidResponse.kt new file mode 100644 index 0000000..43a48c1 --- /dev/null +++ b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/model/UuidResponse.kt @@ -0,0 +1,5 @@ +package com.ohdodok.catchytape.core.data.model + +data class UuidResponse ( + val uuid: String +) \ No newline at end of file diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/UrlRepositoryImpl.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/UrlRepositoryImpl.kt new file mode 100644 index 0000000..d9e0e79 --- /dev/null +++ b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/UrlRepositoryImpl.kt @@ -0,0 +1,54 @@ +package com.ohdodok.catchytape.core.data.repository + +import com.ohdodok.catchytape.core.data.api.FileType +import com.ohdodok.catchytape.core.data.api.UploadApi +import com.ohdodok.catchytape.core.domain.repository.UrlRepository +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.MultipartBody +import okhttp3.RequestBody.Companion.asRequestBody +import java.io.File +import javax.inject.Inject + +class UrlRepositoryImpl @Inject constructor( + private val uploadApi: UploadApi +) : UrlRepository { + + override fun getUuid(): Flow = flow { + val response = uploadApi.getUuid() + if (response.isSuccessful) { + response.body()?.let { uuidResponse -> emit(uuidResponse.uuid) } + } else { + // TODO : 네트워크 에러 로직 + throw Exception("uuid 생성 실패") + } + } + + override fun getImageUrl(uuid: String, file: File): Flow = flow { + val response = + uploadApi.getUrl(uuid = uuid, type = FileType.COVER, file = file.toMultipart()) + if (response.isSuccessful) { + response.body()?.let { urlResponse -> emit(urlResponse.url) } + } else { + // TODO : 네트워크 에러 로직 + throw Exception("이미지 업로드 실패") + } + } + + override fun getAudioUrl(uuid: String, file: File): Flow = flow { + val response = + uploadApi.getUrl(uuid = uuid, type = FileType.MUSIC, file = file.toMultipart()) + if (response.isSuccessful) { + response.body()?.let { urlResponse -> emit(urlResponse.url) } + } else { + // TODO : 네트워크 에러 로직 + throw Exception("음악 업로드 실패") + } + } + + private fun File.toMultipart(): MultipartBody.Part { + val fileBody = this.asRequestBody("multipart/form-data".toMediaTypeOrNull()) + return MultipartBody.Part.createFormData("file", this.name, fileBody) + } +} \ No newline at end of file diff --git a/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/repository/UrlRepository.kt b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/repository/UrlRepository.kt new file mode 100644 index 0000000..baf6728 --- /dev/null +++ b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/repository/UrlRepository.kt @@ -0,0 +1,14 @@ +package com.ohdodok.catchytape.core.domain.repository + +import kotlinx.coroutines.flow.Flow +import java.io.File + +interface UrlRepository { + + fun getUuid(): Flow + + fun getImageUrl(uuid: String, file: File): Flow + + fun getAudioUrl(uuid: String, file: File): Flow + +} \ No newline at end of file diff --git a/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/UploadFileUseCase.kt b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/UploadFileUseCase.kt index b2a7d7f..ecc741b 100644 --- a/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/UploadFileUseCase.kt +++ b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/UploadFileUseCase.kt @@ -1,24 +1,21 @@ package com.ohdodok.catchytape.core.domain.usecase -import kotlinx.coroutines.delay +import com.ohdodok.catchytape.core.domain.repository.UrlRepository import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.single import java.io.File import javax.inject.Inject class UploadFileUseCase @Inject constructor( - + private val urlRepository: UrlRepository ) { - fun getImgUrl(file: File): Flow = flow { - // todo : 서버 기다리는 중.. - delay(1000) - emit("https://kr.object.ncloudstorage.com/catchy-tape-bucket2/image/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202023-11-21%20180100.png") + fun getImgUrl(file: File): Flow = urlRepository.getUuid().map { uuid -> + urlRepository.getImageUrl(uuid, file).single() } - fun getAudioUrl(file: File): Flow = flow { - // todo : 서버 기다리는 중.. - delay(1000) - emit("https://kr.object.ncloudstorage.com/catchy-tape-bucket2/music/2/%EC%9D%B4%EB%85%B8%EB%9E%98.mp3") + fun getAudioUrl(file: File): Flow = urlRepository.getUuid().map { uuid -> + urlRepository.getAudioUrl(uuid, file).single() } } \ No newline at end of file From 4d8cbdc5902ed4425af43a6bc8ed270660f3a5da Mon Sep 17 00:00:00 2001 From: youlalala Date: Thu, 23 Nov 2023 11:53:06 +0900 Subject: [PATCH 128/165] =?UTF-8?q?feat=20:=20=EC=9D=8C=EC=95=85=20?= =?UTF-8?q?=EC=97=85=EB=A1=9C=EB=93=9C=20=EC=84=9C=EB=B2=84=20=ED=86=B5?= =?UTF-8?q?=EC=8B=A0=20=EB=A1=9C=EC=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/data/model/MusicRequest.kt | 3 ++- .../data/repository/MusicRepositoryImpl.kt | 22 +++++++++++++++++++ .../core/domain/repository/MusicRepository.kt | 2 ++ .../core/domain/usecase/UploadMusicUseCase.kt | 21 +++++++++++++----- .../feature/upload/UploadViewModel.kt | 2 +- 5 files changed, 42 insertions(+), 8 deletions(-) diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/model/MusicRequest.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/model/MusicRequest.kt index ed724fe..671f6b2 100644 --- a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/model/MusicRequest.kt +++ b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/model/MusicRequest.kt @@ -3,5 +3,6 @@ package com.ohdodok.catchytape.core.data.model data class MusicRequest ( val title: String, val cover: String, - val file: String + val file: String, + val genre: String ) \ No newline at end of file diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/MusicRepositoryImpl.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/MusicRepositoryImpl.kt index a60a139..1aad7c1 100644 --- a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/MusicRepositoryImpl.kt +++ b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/MusicRepositoryImpl.kt @@ -1,6 +1,7 @@ package com.ohdodok.catchytape.core.data.repository import com.ohdodok.catchytape.core.data.api.MusicApi +import com.ohdodok.catchytape.core.data.model.MusicRequest import com.ohdodok.catchytape.core.domain.repository.MusicRepository import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow @@ -18,4 +19,25 @@ class MusicRepositoryImpl @Inject constructor( else -> throw RuntimeException("네트워크 에러") } } + + override fun postMusic( + title: String, + imageUrl: String, + audioUrl: String, + genre: String + ): Flow = flow { + val response = musicApi.postMusic( + MusicRequest( + title = title, + cover = imageUrl, + file = audioUrl, + genre = genre + ) + ) + when (response.code()) { + // TODO : 네트워크 에러 로직 처리 + in 200..299 -> emit(response.body() ?: Unit) + else -> throw RuntimeException("네트워크 에러") + } + } } \ No newline at end of file diff --git a/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/repository/MusicRepository.kt b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/repository/MusicRepository.kt index 2dbacd5..9f3b67c 100644 --- a/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/repository/MusicRepository.kt +++ b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/repository/MusicRepository.kt @@ -5,4 +5,6 @@ import kotlinx.coroutines.flow.Flow interface MusicRepository { fun getGenres(): Flow> + + fun postMusic(title: String, imageUrl: String, audioUrl: String, genre: String): Flow } \ No newline at end of file diff --git a/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/UploadMusicUseCase.kt b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/UploadMusicUseCase.kt index c6a94dd..4490ae6 100644 --- a/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/UploadMusicUseCase.kt +++ b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/UploadMusicUseCase.kt @@ -1,13 +1,22 @@ package com.ohdodok.catchytape.core.domain.usecase +import com.ohdodok.catchytape.core.domain.repository.MusicRepository import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flow import javax.inject.Inject -class UploadMusicUseCase @Inject constructor() { +class UploadMusicUseCase @Inject constructor( + private val musicRepository: MusicRepository +) { - operator fun invoke(imgUrl: String, audioUrl: String, title: String, genre: String): Flow = flow { - // todo : 서버에 업로드 - emit(Unit) - } + operator fun invoke( + imageUrl: String, + audioUrl: String, + title: String, + genre: String + ): Flow = musicRepository.postMusic( + title = title, + genre = genre, + imageUrl = imageUrl, + audioUrl = audioUrl + ) } \ No newline at end of file diff --git a/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadViewModel.kt b/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadViewModel.kt index e8b66ad..47e2070 100644 --- a/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadViewModel.kt +++ b/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadViewModel.kt @@ -93,7 +93,7 @@ class UploadViewModel @Inject constructor( fun uploadMusic() { if (isUploadEnable.value) { uploadMusicUseCase( - imgUrl = imageState.value.url, + imageUrl = imageState.value.url, audioUrl = audioState.value.url, title = musicTitle.value, genre = musicGenre.value From 1a8cd319e4a5edc168f00dae141de8d3318be71d Mon Sep 17 00:00:00 2001 From: youlalala Date: Thu, 23 Nov 2023 11:59:09 +0900 Subject: [PATCH 129/165] =?UTF-8?q?feat=20:=20=EC=97=85=EB=A1=9C=EB=93=9C?= =?UTF-8?q?=20=EC=84=B1=EA=B3=B5=EC=8B=9C=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/upload/UploadFragment.kt | 25 ++++++++++++++++++- .../feature/upload/UploadViewModel.kt | 12 +++++++-- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadFragment.kt b/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadFragment.kt index b82c3aa..be77a0d 100644 --- a/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadFragment.kt +++ b/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadFragment.kt @@ -8,6 +8,7 @@ import androidx.activity.result.PickVisualMediaRequest import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia import androidx.fragment.app.viewModels +import androidx.navigation.fragment.findNavController import com.ohdodok.catchytape.catchytape.upload.R import com.ohdodok.catchytape.catchytape.upload.databinding.FragmentUploadBinding import com.ohdodok.catchytape.core.ui.BaseFragment @@ -33,9 +34,25 @@ class UploadFragment : BaseFragment(R.layout.fragment_upl override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) binding.viewModel = viewModel + + observeEvents() + setUpFileBtn() - setupBackStack(binding.tbUpload) + setUpCompleteBtn() setupSelectThumbnailImage() + setupBackStack(binding.tbUpload) + } + + private fun observeEvents() { + repeatOnStarted { + viewModel.events.collect { event -> + when (event) { + is UploadEventState.NavigateToBack -> { + findNavController().popBackStack() + } + } + } + } } private fun setUpFileBtn() { @@ -44,6 +61,12 @@ class UploadFragment : BaseFragment(R.layout.fragment_upl } } + private fun setUpCompleteBtn() { + binding.btnComplete.setOnClickListener { + viewModel.uploadMusic() + } + } + private fun setupSelectThumbnailImage() { binding.cvUploadThumbnail.setOnClickListener { imagePickerLauncher.launch(PickVisualMediaRequest(PickVisualMedia.ImageOnly)) diff --git a/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadViewModel.kt b/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadViewModel.kt index 47e2070..154eb47 100644 --- a/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadViewModel.kt +++ b/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadViewModel.kt @@ -1,6 +1,5 @@ package com.ohdodok.catchytape.feature.upload -import android.net.Uri import androidx.lifecycle.ViewModel import dagger.hilt.android.lifecycle.HiltViewModel import java.io.File @@ -9,9 +8,11 @@ import androidx.lifecycle.viewModelScope import com.ohdodok.catchytape.core.domain.usecase.UploadFileUseCase import com.ohdodok.catchytape.core.domain.usecase.GetMusicGenresUseCase import com.ohdodok.catchytape.core.domain.usecase.UploadMusicUseCase +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.combine @@ -28,6 +29,9 @@ class UploadViewModel @Inject constructor( private val uploadMusicUseCase: UploadMusicUseCase ) : ViewModel() { + private val _events = MutableSharedFlow() + val events = _events.asSharedFlow() + val musicTitle = MutableStateFlow("") val musicGenre = MutableStateFlow("") @@ -98,7 +102,7 @@ class UploadViewModel @Inject constructor( title = musicTitle.value, genre = musicGenre.value ).onEach { - // TODO : 업로드 성공 + _events.emit(UploadEventState.NavigateToBack) }.catch { // TODO : 업로드 실패 }.launchIn(viewModelScope) @@ -111,3 +115,7 @@ data class UploadedFileState( val url: String = "" ) +sealed interface UploadEventState { + data object NavigateToBack : UploadEventState +} + From 75868500031ccb1ce114aa976380b8275a4f21bc Mon Sep 17 00:00:00 2001 From: algosketch Date: Thu, 23 Nov 2023 14:10:18 +0900 Subject: [PATCH 130/165] =?UTF-8?q?refactor=20:=20=ED=86=A0=ED=81=B0=20?= =?UTF-8?q?=EC=8A=A4=ED=86=A0=EC=96=B4=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../catchytape/core/data/di/NetworkModule.kt | 1 - .../catchytape/core/data/store/TokenStore.kt | 13 ------------- 2 files changed, 14 deletions(-) delete mode 100644 android/core/data/src/main/java/com/ohdodok/catchytape/core/data/store/TokenStore.kt diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/di/NetworkModule.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/di/NetworkModule.kt index 686b9d7..1fe3996 100644 --- a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/di/NetworkModule.kt +++ b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/di/NetworkModule.kt @@ -4,7 +4,6 @@ import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFact import com.ohdodok.catchytape.core.data.BuildConfig import com.ohdodok.catchytape.core.data.datasource.TokenLocalDataSource import com.ohdodok.catchytape.core.data.di.qualifier.AuthInterceptor -import com.ohdodok.catchytape.core.data.store.TokenStore import dagger.Module import dagger.Provides import dagger.hilt.InstallIn diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/store/TokenStore.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/store/TokenStore.kt deleted file mode 100644 index 172a271..0000000 --- a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/store/TokenStore.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.ohdodok.catchytape.core.data.store - -import javax.inject.Inject - -class TokenStore @Inject constructor() { - - var token: String = "" - private set - - fun updateToken(newToken: String) { - token = newToken - } -} \ No newline at end of file From 0c5e84700cb95dc164db8c732bb85d367ed9e143 Mon Sep 17 00:00:00 2001 From: youlalala Date: Thu, 23 Nov 2023 11:43:13 +0900 Subject: [PATCH 131/165] =?UTF-8?q?feat=20:=20=EC=97=85=EB=A1=9C=EB=93=9C?= =?UTF-8?q?=20=EC=84=9C=EB=B2=84=20=ED=86=B5=EC=8B=A0=20=EB=A1=9C=EC=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../catchytape/core/data/api/UploadApi.kt | 32 +++++++---- .../core/data/di/RepositoryModule.kt | 5 ++ .../core/data/model/UuidResponse.kt | 5 ++ .../core/data/repository/UrlRepositoryImpl.kt | 54 +++++++++++++++++++ .../core/domain/repository/UrlRepository.kt | 14 +++++ .../core/domain/usecase/UploadFileUseCase.kt | 19 +++---- 6 files changed, 109 insertions(+), 20 deletions(-) create mode 100644 android/core/data/src/main/java/com/ohdodok/catchytape/core/data/model/UuidResponse.kt create mode 100644 android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/UrlRepositoryImpl.kt create mode 100644 android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/repository/UrlRepository.kt diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/api/UploadApi.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/api/UploadApi.kt index 902ee7f..90e5613 100644 --- a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/api/UploadApi.kt +++ b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/api/UploadApi.kt @@ -1,20 +1,34 @@ package com.ohdodok.catchytape.core.data.api import com.ohdodok.catchytape.core.data.model.UrlResponse +import com.ohdodok.catchytape.core.data.model.UuidResponse +import kotlinx.serialization.SerialName +import okhttp3.MultipartBody import retrofit2.Response -import retrofit2.http.Headers -import retrofit2.http.POST +import retrofit2.http.GET +import retrofit2.http.Multipart +import retrofit2.http.Part +import retrofit2.http.Query interface UploadApi { - @POST("upload/music") - @Headers("Content-Type: audio/mpeg") - suspend fun uploadMusic( - ): Response + @GET("upload/uuid") + suspend fun getUuid( + ): Response - @POST("upload/image") - @Headers("Content-Type: image/png") - suspend fun uploadImage( + @GET("upload") + @Multipart + suspend fun getUrl( + @Part file: MultipartBody.Part, + @Query("uuid") uuid: String, + @Query("type") type: FileType, ): Response +} + +enum class FileType { + @SerialName("music") + MUSIC, + @SerialName("cover") + COVER } \ No newline at end of file diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/di/RepositoryModule.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/di/RepositoryModule.kt index 0b3179a..b58ac30 100644 --- a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/di/RepositoryModule.kt +++ b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/di/RepositoryModule.kt @@ -2,8 +2,10 @@ package com.ohdodok.catchytape.core.data.di import com.ohdodok.catchytape.core.data.repository.AuthRepositoryImpl import com.ohdodok.catchytape.core.data.repository.MusicRepositoryImpl +import com.ohdodok.catchytape.core.data.repository.UrlRepositoryImpl import com.ohdodok.catchytape.core.domain.repository.AuthRepository import com.ohdodok.catchytape.core.domain.repository.MusicRepository +import com.ohdodok.catchytape.core.domain.repository.UrlRepository import dagger.Binds import dagger.Module import dagger.hilt.InstallIn @@ -18,4 +20,7 @@ interface RepositoryModule { @Binds fun bindMusicRepository(musicRepositoryImpl: MusicRepositoryImpl): MusicRepository + + @Binds + fun bindUrlRepository(urlRepositoryImpl: UrlRepositoryImpl): UrlRepository } \ No newline at end of file diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/model/UuidResponse.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/model/UuidResponse.kt new file mode 100644 index 0000000..43a48c1 --- /dev/null +++ b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/model/UuidResponse.kt @@ -0,0 +1,5 @@ +package com.ohdodok.catchytape.core.data.model + +data class UuidResponse ( + val uuid: String +) \ No newline at end of file diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/UrlRepositoryImpl.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/UrlRepositoryImpl.kt new file mode 100644 index 0000000..d9e0e79 --- /dev/null +++ b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/UrlRepositoryImpl.kt @@ -0,0 +1,54 @@ +package com.ohdodok.catchytape.core.data.repository + +import com.ohdodok.catchytape.core.data.api.FileType +import com.ohdodok.catchytape.core.data.api.UploadApi +import com.ohdodok.catchytape.core.domain.repository.UrlRepository +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.MultipartBody +import okhttp3.RequestBody.Companion.asRequestBody +import java.io.File +import javax.inject.Inject + +class UrlRepositoryImpl @Inject constructor( + private val uploadApi: UploadApi +) : UrlRepository { + + override fun getUuid(): Flow = flow { + val response = uploadApi.getUuid() + if (response.isSuccessful) { + response.body()?.let { uuidResponse -> emit(uuidResponse.uuid) } + } else { + // TODO : 네트워크 에러 로직 + throw Exception("uuid 생성 실패") + } + } + + override fun getImageUrl(uuid: String, file: File): Flow = flow { + val response = + uploadApi.getUrl(uuid = uuid, type = FileType.COVER, file = file.toMultipart()) + if (response.isSuccessful) { + response.body()?.let { urlResponse -> emit(urlResponse.url) } + } else { + // TODO : 네트워크 에러 로직 + throw Exception("이미지 업로드 실패") + } + } + + override fun getAudioUrl(uuid: String, file: File): Flow = flow { + val response = + uploadApi.getUrl(uuid = uuid, type = FileType.MUSIC, file = file.toMultipart()) + if (response.isSuccessful) { + response.body()?.let { urlResponse -> emit(urlResponse.url) } + } else { + // TODO : 네트워크 에러 로직 + throw Exception("음악 업로드 실패") + } + } + + private fun File.toMultipart(): MultipartBody.Part { + val fileBody = this.asRequestBody("multipart/form-data".toMediaTypeOrNull()) + return MultipartBody.Part.createFormData("file", this.name, fileBody) + } +} \ No newline at end of file diff --git a/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/repository/UrlRepository.kt b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/repository/UrlRepository.kt new file mode 100644 index 0000000..baf6728 --- /dev/null +++ b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/repository/UrlRepository.kt @@ -0,0 +1,14 @@ +package com.ohdodok.catchytape.core.domain.repository + +import kotlinx.coroutines.flow.Flow +import java.io.File + +interface UrlRepository { + + fun getUuid(): Flow + + fun getImageUrl(uuid: String, file: File): Flow + + fun getAudioUrl(uuid: String, file: File): Flow + +} \ No newline at end of file diff --git a/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/UploadFileUseCase.kt b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/UploadFileUseCase.kt index b2a7d7f..ecc741b 100644 --- a/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/UploadFileUseCase.kt +++ b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/UploadFileUseCase.kt @@ -1,24 +1,21 @@ package com.ohdodok.catchytape.core.domain.usecase -import kotlinx.coroutines.delay +import com.ohdodok.catchytape.core.domain.repository.UrlRepository import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.single import java.io.File import javax.inject.Inject class UploadFileUseCase @Inject constructor( - + private val urlRepository: UrlRepository ) { - fun getImgUrl(file: File): Flow = flow { - // todo : 서버 기다리는 중.. - delay(1000) - emit("https://kr.object.ncloudstorage.com/catchy-tape-bucket2/image/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202023-11-21%20180100.png") + fun getImgUrl(file: File): Flow = urlRepository.getUuid().map { uuid -> + urlRepository.getImageUrl(uuid, file).single() } - fun getAudioUrl(file: File): Flow = flow { - // todo : 서버 기다리는 중.. - delay(1000) - emit("https://kr.object.ncloudstorage.com/catchy-tape-bucket2/music/2/%EC%9D%B4%EB%85%B8%EB%9E%98.mp3") + fun getAudioUrl(file: File): Flow = urlRepository.getUuid().map { uuid -> + urlRepository.getAudioUrl(uuid, file).single() } } \ No newline at end of file From 23b9839f07f29848881e36f9046b344a7c495aa4 Mon Sep 17 00:00:00 2001 From: youlalala Date: Thu, 23 Nov 2023 11:53:06 +0900 Subject: [PATCH 132/165] =?UTF-8?q?feat=20:=20=EC=9D=8C=EC=95=85=20?= =?UTF-8?q?=EC=97=85=EB=A1=9C=EB=93=9C=20=EC=84=9C=EB=B2=84=20=ED=86=B5?= =?UTF-8?q?=EC=8B=A0=20=EB=A1=9C=EC=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/data/model/MusicRequest.kt | 3 ++- .../data/repository/MusicRepositoryImpl.kt | 22 +++++++++++++++++++ .../core/domain/repository/MusicRepository.kt | 2 ++ .../core/domain/usecase/UploadMusicUseCase.kt | 21 +++++++++++++----- .../feature/upload/UploadViewModel.kt | 2 +- 5 files changed, 42 insertions(+), 8 deletions(-) diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/model/MusicRequest.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/model/MusicRequest.kt index ed724fe..671f6b2 100644 --- a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/model/MusicRequest.kt +++ b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/model/MusicRequest.kt @@ -3,5 +3,6 @@ package com.ohdodok.catchytape.core.data.model data class MusicRequest ( val title: String, val cover: String, - val file: String + val file: String, + val genre: String ) \ No newline at end of file diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/MusicRepositoryImpl.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/MusicRepositoryImpl.kt index a60a139..1aad7c1 100644 --- a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/MusicRepositoryImpl.kt +++ b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/MusicRepositoryImpl.kt @@ -1,6 +1,7 @@ package com.ohdodok.catchytape.core.data.repository import com.ohdodok.catchytape.core.data.api.MusicApi +import com.ohdodok.catchytape.core.data.model.MusicRequest import com.ohdodok.catchytape.core.domain.repository.MusicRepository import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow @@ -18,4 +19,25 @@ class MusicRepositoryImpl @Inject constructor( else -> throw RuntimeException("네트워크 에러") } } + + override fun postMusic( + title: String, + imageUrl: String, + audioUrl: String, + genre: String + ): Flow = flow { + val response = musicApi.postMusic( + MusicRequest( + title = title, + cover = imageUrl, + file = audioUrl, + genre = genre + ) + ) + when (response.code()) { + // TODO : 네트워크 에러 로직 처리 + in 200..299 -> emit(response.body() ?: Unit) + else -> throw RuntimeException("네트워크 에러") + } + } } \ No newline at end of file diff --git a/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/repository/MusicRepository.kt b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/repository/MusicRepository.kt index 2dbacd5..9f3b67c 100644 --- a/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/repository/MusicRepository.kt +++ b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/repository/MusicRepository.kt @@ -5,4 +5,6 @@ import kotlinx.coroutines.flow.Flow interface MusicRepository { fun getGenres(): Flow> + + fun postMusic(title: String, imageUrl: String, audioUrl: String, genre: String): Flow } \ No newline at end of file diff --git a/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/UploadMusicUseCase.kt b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/UploadMusicUseCase.kt index c6a94dd..4490ae6 100644 --- a/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/UploadMusicUseCase.kt +++ b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/UploadMusicUseCase.kt @@ -1,13 +1,22 @@ package com.ohdodok.catchytape.core.domain.usecase +import com.ohdodok.catchytape.core.domain.repository.MusicRepository import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flow import javax.inject.Inject -class UploadMusicUseCase @Inject constructor() { +class UploadMusicUseCase @Inject constructor( + private val musicRepository: MusicRepository +) { - operator fun invoke(imgUrl: String, audioUrl: String, title: String, genre: String): Flow = flow { - // todo : 서버에 업로드 - emit(Unit) - } + operator fun invoke( + imageUrl: String, + audioUrl: String, + title: String, + genre: String + ): Flow = musicRepository.postMusic( + title = title, + genre = genre, + imageUrl = imageUrl, + audioUrl = audioUrl + ) } \ No newline at end of file diff --git a/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadViewModel.kt b/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadViewModel.kt index e8b66ad..47e2070 100644 --- a/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadViewModel.kt +++ b/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadViewModel.kt @@ -93,7 +93,7 @@ class UploadViewModel @Inject constructor( fun uploadMusic() { if (isUploadEnable.value) { uploadMusicUseCase( - imgUrl = imageState.value.url, + imageUrl = imageState.value.url, audioUrl = audioState.value.url, title = musicTitle.value, genre = musicGenre.value From 18858760ea4e941aff32ac2d0d313a968b81da63 Mon Sep 17 00:00:00 2001 From: youlalala Date: Thu, 23 Nov 2023 11:59:09 +0900 Subject: [PATCH 133/165] =?UTF-8?q?feat=20:=20=EC=97=85=EB=A1=9C=EB=93=9C?= =?UTF-8?q?=20=EC=84=B1=EA=B3=B5=EC=8B=9C=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/upload/UploadFragment.kt | 25 ++++++++++++++++++- .../feature/upload/UploadViewModel.kt | 12 +++++++-- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadFragment.kt b/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadFragment.kt index b82c3aa..be77a0d 100644 --- a/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadFragment.kt +++ b/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadFragment.kt @@ -8,6 +8,7 @@ import androidx.activity.result.PickVisualMediaRequest import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia import androidx.fragment.app.viewModels +import androidx.navigation.fragment.findNavController import com.ohdodok.catchytape.catchytape.upload.R import com.ohdodok.catchytape.catchytape.upload.databinding.FragmentUploadBinding import com.ohdodok.catchytape.core.ui.BaseFragment @@ -33,9 +34,25 @@ class UploadFragment : BaseFragment(R.layout.fragment_upl override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) binding.viewModel = viewModel + + observeEvents() + setUpFileBtn() - setupBackStack(binding.tbUpload) + setUpCompleteBtn() setupSelectThumbnailImage() + setupBackStack(binding.tbUpload) + } + + private fun observeEvents() { + repeatOnStarted { + viewModel.events.collect { event -> + when (event) { + is UploadEventState.NavigateToBack -> { + findNavController().popBackStack() + } + } + } + } } private fun setUpFileBtn() { @@ -44,6 +61,12 @@ class UploadFragment : BaseFragment(R.layout.fragment_upl } } + private fun setUpCompleteBtn() { + binding.btnComplete.setOnClickListener { + viewModel.uploadMusic() + } + } + private fun setupSelectThumbnailImage() { binding.cvUploadThumbnail.setOnClickListener { imagePickerLauncher.launch(PickVisualMediaRequest(PickVisualMedia.ImageOnly)) diff --git a/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadViewModel.kt b/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadViewModel.kt index 47e2070..154eb47 100644 --- a/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadViewModel.kt +++ b/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadViewModel.kt @@ -1,6 +1,5 @@ package com.ohdodok.catchytape.feature.upload -import android.net.Uri import androidx.lifecycle.ViewModel import dagger.hilt.android.lifecycle.HiltViewModel import java.io.File @@ -9,9 +8,11 @@ import androidx.lifecycle.viewModelScope import com.ohdodok.catchytape.core.domain.usecase.UploadFileUseCase import com.ohdodok.catchytape.core.domain.usecase.GetMusicGenresUseCase import com.ohdodok.catchytape.core.domain.usecase.UploadMusicUseCase +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.combine @@ -28,6 +29,9 @@ class UploadViewModel @Inject constructor( private val uploadMusicUseCase: UploadMusicUseCase ) : ViewModel() { + private val _events = MutableSharedFlow() + val events = _events.asSharedFlow() + val musicTitle = MutableStateFlow("") val musicGenre = MutableStateFlow("") @@ -98,7 +102,7 @@ class UploadViewModel @Inject constructor( title = musicTitle.value, genre = musicGenre.value ).onEach { - // TODO : 업로드 성공 + _events.emit(UploadEventState.NavigateToBack) }.catch { // TODO : 업로드 실패 }.launchIn(viewModelScope) @@ -111,3 +115,7 @@ data class UploadedFileState( val url: String = "" ) +sealed interface UploadEventState { + data object NavigateToBack : UploadEventState +} + From c5f6947f6d0ec5613e5bb8a027dbcb404fc54827 Mon Sep 17 00:00:00 2001 From: Seokyung Lim Date: Thu, 23 Nov 2023 15:21:21 +0900 Subject: [PATCH 134/165] =?UTF-8?q?chore=20:=20user=20=EB=AA=A8=EB=93=88?= =?UTF-8?q?=EC=97=90=20=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/src/user/user.module.ts | 11 +++++++++-- server/src/user/user.service.ts | 3 ++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/server/src/user/user.module.ts b/server/src/user/user.module.ts index 6570616..3e27e0e 100644 --- a/server/src/user/user.module.ts +++ b/server/src/user/user.module.ts @@ -4,10 +4,17 @@ import { UserService } from './user.service'; import { TypeOrmModule } from '@nestjs/typeorm'; import { User } from 'src/entity/user.entity'; import { AuthModule } from 'src/auth/auth.module'; +import { PlaylistService } from 'src/playlist/playlist.service'; +import { Playlist } from 'src/entity/playlist.entity'; +import { Music_Playlist } from 'src/entity/music_playlist.entity'; +import { Music } from 'src/entity/music.entity'; @Module({ - imports: [TypeOrmModule.forFeature([User]), AuthModule], + imports: [ + TypeOrmModule.forFeature([User, Playlist, Music_Playlist, Music]), + AuthModule, + ], controllers: [UserController], - providers: [UserService], + providers: [UserService, PlaylistService], }) export class UserModule {} diff --git a/server/src/user/user.service.ts b/server/src/user/user.service.ts index badb6ee..647b3d7 100644 --- a/server/src/user/user.service.ts +++ b/server/src/user/user.service.ts @@ -3,12 +3,13 @@ import { HTTP_STATUS_CODE } from 'src/httpStatusCode.enum'; import { User } from 'src/entity/user.entity'; import { Repository } from 'typeorm'; import { InjectRepository } from '@nestjs/typeorm'; +import { PlaylistService } from 'src/playlist/playlist.service'; @Injectable() export class UserService { - //TODO: custom repository로 변경하기 constructor( @InjectRepository(User) private userRepository: Repository, + private playlistService: PlaylistService, ) {} async isDuplicatedUserEmail(userNickname: string): Promise { From e71f2b40eada050a763b52f549d58ba1e00e6d5a Mon Sep 17 00:00:00 2001 From: Seokyung Lim Date: Thu, 23 Nov 2023 15:27:22 +0900 Subject: [PATCH 135/165] =?UTF-8?q?feat=20:=20=EC=82=AC=EC=9A=A9=EC=9E=90?= =?UTF-8?q?=EA=B0=80=20=EC=B5=9C=EA=B7=BC=20=EC=9E=AC=EC=83=9D=ED=95=9C=20?= =?UTF-8?q?=EC=9D=8C=EC=95=85=2010=EA=B0=9C=20=EA=B0=80=EC=A0=B8=EC=98=A4?= =?UTF-8?q?=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Music_Playlist 엔티티에서 정보 가져옴 * User 모듈에 의존성 추가하여 PlaylistService에서 출력된 정보를 가져옴 --- server/src/entity/music_playlist.entity.ts | 32 ++++++++++++++++++++++ server/src/playlist/playlist.service.ts | 8 ++++++ server/src/user/user.controller.ts | 19 +++++++++++++ server/src/user/user.service.ts | 9 ++++++ 4 files changed, 68 insertions(+) diff --git a/server/src/entity/music_playlist.entity.ts b/server/src/entity/music_playlist.entity.ts index b61f019..d4155a4 100644 --- a/server/src/entity/music_playlist.entity.ts +++ b/server/src/entity/music_playlist.entity.ts @@ -45,4 +45,36 @@ export class Music_Playlist extends BaseEntity { }, }).then((a: Music_Playlist[]) => a.map((b) => b.music)); } + + static async getRecentPlayedMusicByUserId(userId: string): Promise { + return await this.find({ + relations: { + music: true, + }, + where: { + playlist: { + playlist_title: '최근 재생 목록', + }, + music: { + user: { + user_id: userId, + }, + }, + }, + select: { + music_playlist_id: false, + music: { + musicId: true, + title: true, + musicFile: true, + cover: true, + genre: true, + }, + }, + order: { + music_playlist_id: 'DESC', + }, + take: 10, + }).then((a: Music_Playlist[]) => a.map((b) => b.music)); + } } diff --git a/server/src/playlist/playlist.service.ts b/server/src/playlist/playlist.service.ts index 54f77a9..7648935 100644 --- a/server/src/playlist/playlist.service.ts +++ b/server/src/playlist/playlist.service.ts @@ -153,4 +153,12 @@ export class PlaylistService { throw new HttpException('SERVER_ERROR', HTTP_STATUS_CODE.SERVER_ERROR); } } + + async getRecentMusicsByUserId(userId: string) { + try { + return Music_Playlist.getRecentPlayedMusicByUserId(userId); + } catch { + throw new HttpException('SERVER ERROR', HTTP_STATUS_CODE.SERVER_ERROR); + } + } } diff --git a/server/src/user/user.controller.ts b/server/src/user/user.controller.ts index f44b993..5c3256d 100644 --- a/server/src/user/user.controller.ts +++ b/server/src/user/user.controller.ts @@ -1,12 +1,16 @@ import { Controller, Get, + Req, HttpCode, HttpException, Param, + UseGuards, } from '@nestjs/common'; import { UserService } from './user.service'; import { HTTP_STATUS_CODE } from 'src/httpStatusCode.enum'; +import { AuthGuard } from '@nestjs/passport'; +import { Music } from 'src/entity/music.entity'; @Controller('users') export class UserController { @@ -26,4 +30,19 @@ export class UserController { return { nickname: name }; } + + @Get('recent-played') + @UseGuards(AuthGuard()) + @HttpCode(HTTP_STATUS_CODE.SUCCESS) + async getUserRecentPlayedMusics(@Req() req): Promise { + try { + const userId = req.user.userId; + const userMusicData = + await this.userService.getRecentPlayedMusicByUserId(userId); + + return userMusicData; + } catch (err) { + throw err; + } + } } diff --git a/server/src/user/user.service.ts b/server/src/user/user.service.ts index 647b3d7..b392af7 100644 --- a/server/src/user/user.service.ts +++ b/server/src/user/user.service.ts @@ -1,6 +1,7 @@ import { HttpException, Injectable } from '@nestjs/common'; import { HTTP_STATUS_CODE } from 'src/httpStatusCode.enum'; import { User } from 'src/entity/user.entity'; +import { Music } from 'src/entity/music.entity'; import { Repository } from 'typeorm'; import { InjectRepository } from '@nestjs/typeorm'; import { PlaylistService } from 'src/playlist/playlist.service'; @@ -27,4 +28,12 @@ export class UserService { throw new HttpException('SERVER ERROR', HTTP_STATUS_CODE.SERVER_ERROR); } } + + async getRecentPlayedMusicByUserId(userId: string): Promise { + try { + return await this.playlistService.getRecentMusicsByUserId(userId); + } catch (error) { + throw error; + } + } } From 7584b1d2205d365298478d31157ef174d2ab2e4a Mon Sep 17 00:00:00 2001 From: Seokyung Lim Date: Thu, 23 Nov 2023 17:32:59 +0900 Subject: [PATCH 136/165] =?UTF-8?q?chore=20:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/src/user/user.controller.spec.ts | 23 +++++++++++++++++++++-- server/src/user/user.service.spec.ts | 23 +++++++++++++++++++++-- 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/server/src/user/user.controller.spec.ts b/server/src/user/user.controller.spec.ts index 4856bff..275f626 100644 --- a/server/src/user/user.controller.spec.ts +++ b/server/src/user/user.controller.spec.ts @@ -4,27 +4,46 @@ import { UserService } from './user.service'; import { getRepositoryToken } from '@nestjs/typeorm'; import { User } from 'src/entity/user.entity'; import { Repository } from 'typeorm'; +import { PlaylistService } from 'src/playlist/playlist.service'; +import { Music } from 'src/entity/music.entity'; +import { Music_Playlist } from 'src/entity/music_playlist.entity'; +import { Playlist } from 'src/entity/playlist.entity'; describe('UserController', () => { let controller: UserController; let userService: UserService; - let repository; + let playlistService: PlaylistService; + let userRepository: Repository; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [UserController], providers: [ UserService, + PlaylistService, { provide: getRepositoryToken(User), useClass: Repository, }, + { + provide: getRepositoryToken(Playlist), + useClass: Repository, + }, + { + provide: getRepositoryToken(Music), + useClass: Repository, + }, + { + provide: getRepositoryToken(Music_Playlist), + useClass: Repository, + }, ], }).compile(); controller = module.get(UserController); userService = module.get(UserService); - repository = module.get(getRepositoryToken(User)); + playlistService = module.get(PlaylistService); + userRepository = module.get(getRepositoryToken(User)); }); it('should be defined', () => { diff --git a/server/src/user/user.service.spec.ts b/server/src/user/user.service.spec.ts index 5653523..93b2135 100644 --- a/server/src/user/user.service.spec.ts +++ b/server/src/user/user.service.spec.ts @@ -3,24 +3,43 @@ import { UserService } from './user.service'; import { getRepositoryToken } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { User } from '../entity/user.entity'; +import { Playlist } from 'src/entity/playlist.entity'; +import { Music } from 'src/entity/music.entity'; +import { Music_Playlist } from 'src/entity/music_playlist.entity'; +import { PlaylistService } from 'src/playlist/playlist.service'; describe('UserService', () => { let service: UserService; - let repository; + let playlistService: PlaylistService; + let userRepository: Repository; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ UserService, + PlaylistService, { provide: getRepositoryToken(User), useClass: Repository, }, + { + provide: getRepositoryToken(Playlist), + useClass: Repository, + }, + { + provide: getRepositoryToken(Music), + useClass: Repository, + }, + { + provide: getRepositoryToken(Music_Playlist), + useClass: Repository, + }, ], }).compile(); service = module.get(UserService); - repository = module.get(getRepositoryToken(User)) + playlistService = module.get(PlaylistService); + userRepository = module.get(getRepositoryToken(User)); }); it('should be defined', () => { From 15b4d8e5cc72a323ee87ec27920f069a4603a5fe Mon Sep 17 00:00:00 2001 From: Seokyung Lim Date: Thu, 23 Nov 2023 17:50:20 +0900 Subject: [PATCH 137/165] =?UTF-8?q?chore=20:=20=EB=8D=B0=EB=AA=A8=20?= =?UTF-8?q?=EC=9C=84=ED=95=9C=20=EC=97=85=EB=A1=9C=EB=93=9C=20API=20?= =?UTF-8?q?=EC=9E=84=EC=8B=9C=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/src/upload/upload.controller.ts | 42 ++++++++++++++++++++++++++ server/src/upload/upload.service.ts | 35 +++++++++++++++++++++ 2 files changed, 77 insertions(+) diff --git a/server/src/upload/upload.controller.ts b/server/src/upload/upload.controller.ts index c8c3a95..1c89312 100644 --- a/server/src/upload/upload.controller.ts +++ b/server/src/upload/upload.controller.ts @@ -1,4 +1,5 @@ import { + Post, Controller, Get, Req, @@ -6,11 +7,18 @@ import { HttpCode, UseGuards, HttpException, + UseInterceptors, + UploadedFile, + ParseFilePipe, + MaxFileSizeValidator, + FileTypeValidator, } from '@nestjs/common'; import { UploadService } from './upload.service'; import { HTTP_STATUS_CODE } from 'src/httpStatusCode.enum'; import { AuthGuard } from '@nestjs/passport'; import { v4 } from 'uuid'; +import { fileSize } from '../constants'; +import { FileInterceptor } from '@nestjs/platform-express'; @Controller('upload') export class UploadController { @@ -47,4 +55,38 @@ export class UploadController { throw error; } } + + @Post('/music') + @UseInterceptors(FileInterceptor('file')) + async uploadMusic( + @UploadedFile( + new ParseFilePipe({ + validators: [ + // new MaxFileSizeValidator({ maxSize: fileSize.MUSIC_FILE_LIMIT_SIZE }), + new FileTypeValidator({ fileType: 'audio/mpeg' }), + ], + }), + ) + file: Express.Multer.File, + ) { + const { url } = await this.uploadService.uploadMusic(file); + return { url }; + } + + @Post('/image') + @UseInterceptors(FileInterceptor('file')) + async uploadImage( + @UploadedFile( + new ParseFilePipe({ + validators: [ + // new MaxFileSizeValidator({ maxSize: fileSize.IMAGE_FILE_LIMIT_SIZE }), + new FileTypeValidator({ fileType: 'image/png' }), + ], + }), + ) + file: Express.Multer.File, + ) { + const { url } = await this.uploadService.uploadImage(file); + return { url }; + } } diff --git a/server/src/upload/upload.service.ts b/server/src/upload/upload.service.ts index c77d73f..c49901b 100644 --- a/server/src/upload/upload.service.ts +++ b/server/src/upload/upload.service.ts @@ -3,6 +3,7 @@ import { HTTP_STATUS_CODE } from 'src/httpStatusCode.enum'; import { NcloudConfigService } from './../config/ncloud.config'; import { S3 } from 'aws-sdk'; import { keyFlags, keyHandler } from './../constants'; +import { Readable } from 'stream'; @Injectable() export class UploadService { @@ -48,4 +49,38 @@ export class UploadService { throw new HttpException('SERVER ERROR', HTTP_STATUS_CODE.SERVER_ERROR); } } + + async uploadMusic(file: Express.Multer.File): Promise<{ url: string }> { + try { + const uploadResult = await this.objectStorage + .upload({ + Bucket: 'catchy-tape-bucket2', + Key: `music/example/${file.originalname}`, + Body: Readable.from(file.buffer), + ACL: 'public-read', + }) + .promise(); + + return { url: uploadResult.Location }; + } catch { + throw new HttpException('SERVER ERROR', HTTP_STATUS_CODE.SERVER_ERROR); + } + } + + async uploadImage(file: Express.Multer.File): Promise<{ url: string }> { + try { + const uploadResult = await this.objectStorage + .upload({ + Bucket: 'catchy-tape-bucket2', + Key: `image/example/${file.originalname}`, + Body: Readable.from(file.buffer), + ACL: 'public-read', + }) + .promise(); + + return { url: uploadResult.Location }; + } catch { + throw new HttpException('SERVER ERROR', HTTP_STATUS_CODE.SERVER_ERROR); + } + } } From ba42683d397854e854280257edb1d54ac654d5f6 Mon Sep 17 00:00:00 2001 From: Seokyung Lim Date: Thu, 23 Nov 2023 18:11:44 +0900 Subject: [PATCH 138/165] =?UTF-8?q?chore=20:=20=EB=AC=B4=EC=9D=98=EB=AF=B8?= =?UTF-8?q?=ED=95=9C=20try-catch=EB=AC=B8=20=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/src/user/user.controller.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/server/src/user/user.controller.ts b/server/src/user/user.controller.ts index 5c3256d..a720e77 100644 --- a/server/src/user/user.controller.ts +++ b/server/src/user/user.controller.ts @@ -35,14 +35,10 @@ export class UserController { @UseGuards(AuthGuard()) @HttpCode(HTTP_STATUS_CODE.SUCCESS) async getUserRecentPlayedMusics(@Req() req): Promise { - try { - const userId = req.user.userId; - const userMusicData = - await this.userService.getRecentPlayedMusicByUserId(userId); + const userId = req.user.userId; + const userMusicData = + await this.userService.getRecentPlayedMusicByUserId(userId); - return userMusicData; - } catch (err) { - throw err; - } + return userMusicData; } } From f4edebf754c0ba7b051e7329cf9a8b2c20f3766c Mon Sep 17 00:00:00 2001 From: Seokyung Lim Date: Thu, 23 Nov 2023 18:14:12 +0900 Subject: [PATCH 139/165] =?UTF-8?q?chore=20:=20=EB=AC=B4=EC=9D=98=EB=AF=B8?= =?UTF-8?q?=ED=95=9C=20try-catch=EB=AC=B8=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/src/user/user.service.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/server/src/user/user.service.ts b/server/src/user/user.service.ts index b392af7..686bca0 100644 --- a/server/src/user/user.service.ts +++ b/server/src/user/user.service.ts @@ -30,10 +30,6 @@ export class UserService { } async getRecentPlayedMusicByUserId(userId: string): Promise { - try { - return await this.playlistService.getRecentMusicsByUserId(userId); - } catch (error) { - throw error; - } + return await this.playlistService.getRecentMusicsByUserId(userId); } } From ed77e0ffc71a2a949b138178734d4df3f021a9ac Mon Sep 17 00:00:00 2001 From: hyungun Date: Thu, 23 Nov 2023 15:27:36 +0900 Subject: [PATCH 140/165] =?UTF-8?q?feat=20:=20=EC=BB=A4=EC=8A=A4=ED=85=80?= =?UTF-8?q?=20exception=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 세부적인 errorCode 반환을 위한 CatchyException 클래스 생성 * error code 를 정의하는 ERROR_CODE enum 생성 --- server/src/config/catchyException.ts | 9 +++++++++ server/src/config/errorCode.enum.ts | 7 +++++++ 2 files changed, 16 insertions(+) create mode 100644 server/src/config/catchyException.ts create mode 100644 server/src/config/errorCode.enum.ts diff --git a/server/src/config/catchyException.ts b/server/src/config/catchyException.ts new file mode 100644 index 0000000..40a665f --- /dev/null +++ b/server/src/config/catchyException.ts @@ -0,0 +1,9 @@ +import { HttpException } from '@nestjs/common'; +import { HTTP_STATUS_CODE } from 'src/httpStatusCode.enum'; +import { ERROR_CODE } from './errorCode.enum'; + +export class CatchyException extends HttpException { + constructor(message: string, statuscode: HTTP_STATUS_CODE, errorCode: ERROR_CODE) { + super({message, errorCode}, statuscode); + } +} diff --git a/server/src/config/errorCode.enum.ts b/server/src/config/errorCode.enum.ts new file mode 100644 index 0000000..130f692 --- /dev/null +++ b/server/src/config/errorCode.enum.ts @@ -0,0 +1,7 @@ +export enum ERROR_CODE { + 'NOT_DUPLICATED_NICKNAME' = 1000, + 'DUPLICATED_NICKNAME' = 1001, + 'WRONG_TOKEN' = 10000, + 'SERVER_ERROR' = 10000, + 'BAD_REQUEST' = 10000, +} From 381cd7b52e2d6add882af8b15b94969906884793 Mon Sep 17 00:00:00 2001 From: hyungun Date: Thu, 23 Nov 2023 16:07:07 +0900 Subject: [PATCH 141/165] =?UTF-8?q?feat=20:=20playlist=20exception=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 모두 CathcyException 으로 변경 --- server/src/config/errorCode.enum.ts | 6 +- server/src/playlist/playlist.controller.ts | 12 ++++ server/src/playlist/playlist.service.ts | 68 ++++++++++++++++++---- 3 files changed, 73 insertions(+), 13 deletions(-) diff --git a/server/src/config/errorCode.enum.ts b/server/src/config/errorCode.enum.ts index 130f692..fff9642 100644 --- a/server/src/config/errorCode.enum.ts +++ b/server/src/config/errorCode.enum.ts @@ -1,7 +1,11 @@ export enum ERROR_CODE { 'NOT_DUPLICATED_NICKNAME' = 1000, 'DUPLICATED_NICKNAME' = 1001, + 'SERVER_ERROR' = 5000, + 'NOT_EXIST_PLAYLIST_ON_USER' = 4001, + 'NOT_EXIST_MUSIC' = 4002, + 'ALREADY_ADDED' = 4003, + 'WRONG_TOKEN' = 10000, - 'SERVER_ERROR' = 10000, 'BAD_REQUEST' = 10000, } diff --git a/server/src/playlist/playlist.controller.ts b/server/src/playlist/playlist.controller.ts index e2e28eb..5e4c330 100644 --- a/server/src/playlist/playlist.controller.ts +++ b/server/src/playlist/playlist.controller.ts @@ -4,6 +4,7 @@ import { Get, HttpCode, Param, + Patch, Post, Req, UseGuards, @@ -16,6 +17,8 @@ import { HTTP_STATUS_CODE } from 'src/httpStatusCode.enum'; import { PlaylistCreateDto } from 'src/dto/playlistCreate.dto'; import { Playlist } from 'src/entity/playlist.entity'; import { Music } from 'src/entity/music.entity'; +import { CatchyException } from 'src/config/catchyException'; +import { ERROR_CODE } from 'src/config/errorCode.enum'; @Controller('playlists') export class PlaylistController { @@ -75,4 +78,13 @@ export class PlaylistController { const userId: string = req.user.user_id; return await this.playlistService.getPlaylistMusics(userId, playlistId); } + + @Patch('test') + test() { + throw new CatchyException( + 'testtest', + HTTP_STATUS_CODE.SERVER_ERROR, + ERROR_CODE.BAD_REQUEST, + ); + } } diff --git a/server/src/playlist/playlist.service.ts b/server/src/playlist/playlist.service.ts index 7648935..5213e79 100644 --- a/server/src/playlist/playlist.service.ts +++ b/server/src/playlist/playlist.service.ts @@ -1,5 +1,7 @@ import { HttpException, Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; +import { CatchyException } from 'src/config/catchyException'; +import { ERROR_CODE } from 'src/config/errorCode.enum'; import { PlaylistCreateDto } from 'src/dto/playlistCreate.dto'; import { Music } from 'src/entity/music.entity'; import { Music_Playlist } from 'src/entity/music_playlist.entity'; @@ -35,7 +37,11 @@ export class PlaylistService { const playlistId: number = result.playlist_Id; return playlistId; } catch { - throw new HttpException('SERVER_ERROR', HTTP_STATUS_CODE.SERVER_ERROR); + throw new CatchyException( + 'SERVER_ERROR', + HTTP_STATUS_CODE.SERVER_ERROR, + ERROR_CODE.SERVICE_ERROR, + ); } } @@ -46,19 +52,28 @@ export class PlaylistService { ): Promise { // 사용자 플리가 있는지 확인 if (!(await this.isExistPlaylistOnUser(playlistId, userId))) { - throw new HttpException( + throw new CatchyException( 'NOT_EXIST_PLAYLIST_ON_USER', HTTP_STATUS_CODE.BAD_REQUEST, + ERROR_CODE.NOT_EXIST_PLAYLIST_ON_USER, ); } // 음악 있는지 확인 if (!(await this.isExistMusic(musicId))) { - throw new HttpException('NOT_EXIST_MUSIC', HTTP_STATUS_CODE.BAD_REQUEST); + throw new CatchyException( + 'NOT_EXIST_MUSIC', + HTTP_STATUS_CODE.BAD_REQUEST, + ERROR_CODE.NOT_EXIST_MUSIC, + ); } // 이미 추가된 음악인지 확인 if (await this.isAlreadyAdded(playlistId, musicId)) { - throw new HttpException('ALREADY_ADDED', HTTP_STATUS_CODE.BAD_REQUEST); + throw new CatchyException( + 'ALREADY_ADDED', + HTTP_STATUS_CODE.BAD_REQUEST, + ERROR_CODE.ALREADY_ADDED, + ); } // 관계테이블에 추가 @@ -74,7 +89,11 @@ export class PlaylistService { this.setUpdatedAtNow(playlistId); return result.music_playlist_id; } catch { - throw new HttpException('SERVER_ERROR', HTTP_STATUS_CODE.SERVER_ERROR); + throw new CatchyException( + 'SERVER_ERROR', + HTTP_STATUS_CODE.SERVER_ERROR, + ERROR_CODE.SERVICE_ERROR, + ); } } @@ -86,7 +105,11 @@ export class PlaylistService { }); return count !== 0; } catch { - throw new HttpException('SERVER_ERROR', HTTP_STATUS_CODE.SERVER_ERROR); + throw new CatchyException( + 'SERVER_ERROR', + HTTP_STATUS_CODE.SERVER_ERROR, + ERROR_CODE.SERVICE_ERROR, + ); } } @@ -101,7 +124,11 @@ export class PlaylistService { }); return playlistCount !== 0; } catch { - throw new HttpException('SERVER_ERROR', HTTP_STATUS_CODE.SERVER_ERROR); + throw new CatchyException( + 'SERVER_ERROR', + HTTP_STATUS_CODE.SERVER_ERROR, + ERROR_CODE.SERVICE_ERROR, + ); } } @@ -113,7 +140,11 @@ export class PlaylistService { return musicCount !== 0; } catch { - throw new HttpException('SERVER_ERROR', HTTP_STATUS_CODE.SERVER_ERROR); + throw new CatchyException( + 'SERVER_ERROR', + HTTP_STATUS_CODE.SERVER_ERROR, + ERROR_CODE.SERVICE_ERROR, + ); } } @@ -125,7 +156,11 @@ export class PlaylistService { targetPlaylist.updated_at = new Date(); this.playlistRepository.save(targetPlaylist); } catch { - throw new HttpException('SERVER_ERROR', HTTP_STATUS_CODE.SERVER_ERROR); + throw new CatchyException( + 'SERVER_ERROR', + HTTP_STATUS_CODE.SERVER_ERROR, + ERROR_CODE.SERVICE_ERROR, + ); } } @@ -133,7 +168,11 @@ export class PlaylistService { try { return Playlist.getPlaylistsByUserId(userId); } catch { - throw new HttpException('SERVER_ERROR', HTTP_STATUS_CODE.SERVER_ERROR); + throw new CatchyException( + 'SERVER_ERROR', + HTTP_STATUS_CODE.SERVER_ERROR, + ERROR_CODE.SERVICE_ERROR, + ); } } @@ -142,15 +181,20 @@ export class PlaylistService { playlistId: number, ): Promise { if (!(await this.isExistPlaylistOnUser(playlistId, userId))) { - throw new HttpException( + throw new CatchyException( 'NOT_EXIST_PLAYLIST_ON_USER', HTTP_STATUS_CODE.BAD_REQUEST, + ERROR_CODE.NOT_EXIST_PLAYLIST_ON_USER, ); } try { return Music_Playlist.getMusicListByPlaylistId(playlistId); } catch { - throw new HttpException('SERVER_ERROR', HTTP_STATUS_CODE.SERVER_ERROR); + throw new CatchyException( + 'SERVER_ERROR', + HTTP_STATUS_CODE.SERVER_ERROR, + ERROR_CODE.SERVICE_ERROR, + ); } } From 3481513f49cbe31854496f54438bfb418e87dfab Mon Sep 17 00:00:00 2001 From: hyungun Date: Thu, 23 Nov 2023 18:24:55 +0900 Subject: [PATCH 142/165] =?UTF-8?q?feat=20:=20user=20=EB=AA=A8=EB=93=88=20?= =?UTF-8?q?exception=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * CatchyException 으로 변경 --- server/src/config/errorCode.enum.ts | 1 + server/src/playlist/playlist.service.ts | 8 ++++++-- server/src/user/user.controller.ts | 6 ++++-- server/src/user/user.service.ts | 6 ++++-- 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/server/src/config/errorCode.enum.ts b/server/src/config/errorCode.enum.ts index fff9642..13fe2b9 100644 --- a/server/src/config/errorCode.enum.ts +++ b/server/src/config/errorCode.enum.ts @@ -2,6 +2,7 @@ export enum ERROR_CODE { 'NOT_DUPLICATED_NICKNAME' = 1000, 'DUPLICATED_NICKNAME' = 1001, 'SERVER_ERROR' = 5000, + 'SERVICE_ERROR' = 5001, 'NOT_EXIST_PLAYLIST_ON_USER' = 4001, 'NOT_EXIST_MUSIC' = 4002, 'ALREADY_ADDED' = 4003, diff --git a/server/src/playlist/playlist.service.ts b/server/src/playlist/playlist.service.ts index 5213e79..f383f62 100644 --- a/server/src/playlist/playlist.service.ts +++ b/server/src/playlist/playlist.service.ts @@ -1,4 +1,4 @@ -import { HttpException, Injectable } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { CatchyException } from 'src/config/catchyException'; import { ERROR_CODE } from 'src/config/errorCode.enum'; @@ -202,7 +202,11 @@ export class PlaylistService { try { return Music_Playlist.getRecentPlayedMusicByUserId(userId); } catch { - throw new HttpException('SERVER ERROR', HTTP_STATUS_CODE.SERVER_ERROR); + throw new CatchyException( + 'SERVER ERROR', + HTTP_STATUS_CODE.SERVER_ERROR, + ERROR_CODE.SERVER_ERROR, + ); } } } diff --git a/server/src/user/user.controller.ts b/server/src/user/user.controller.ts index a720e77..13f95b9 100644 --- a/server/src/user/user.controller.ts +++ b/server/src/user/user.controller.ts @@ -3,7 +3,6 @@ import { Get, Req, HttpCode, - HttpException, Param, UseGuards, } from '@nestjs/common'; @@ -11,6 +10,8 @@ import { UserService } from './user.service'; import { HTTP_STATUS_CODE } from 'src/httpStatusCode.enum'; import { AuthGuard } from '@nestjs/passport'; import { Music } from 'src/entity/music.entity'; +import { CatchyException } from 'src/config/catchyException'; +import { ERROR_CODE } from 'src/config/errorCode.enum'; @Controller('users') export class UserController { @@ -22,9 +23,10 @@ export class UserController { @Param('name') name: string, ): Promise<{ nickname: string }> { if (await this.userService.isDuplicatedUserEmail(name)) { - throw new HttpException( + throw new CatchyException( 'DUPLICATED_NICKNAME', HTTP_STATUS_CODE.DUPLICATED_NICKNAME, + ERROR_CODE.DUPLICATED_NICKNAME ); } diff --git a/server/src/user/user.service.ts b/server/src/user/user.service.ts index 686bca0..1dc6504 100644 --- a/server/src/user/user.service.ts +++ b/server/src/user/user.service.ts @@ -1,10 +1,12 @@ -import { HttpException, Injectable } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { HTTP_STATUS_CODE } from 'src/httpStatusCode.enum'; import { User } from 'src/entity/user.entity'; import { Music } from 'src/entity/music.entity'; import { Repository } from 'typeorm'; import { InjectRepository } from '@nestjs/typeorm'; import { PlaylistService } from 'src/playlist/playlist.service'; +import { CatchyException } from 'src/config/catchyException'; +import { ERROR_CODE } from 'src/config/errorCode.enum'; @Injectable() export class UserService { @@ -25,7 +27,7 @@ export class UserService { return false; } catch { - throw new HttpException('SERVER ERROR', HTTP_STATUS_CODE.SERVER_ERROR); + throw new CatchyException('SERVER ERROR', HTTP_STATUS_CODE.SERVER_ERROR, ERROR_CODE.SERVICE_ERROR); } } From cbe23cfad1cfc1f273288f25e86027f9f6f545f4 Mon Sep 17 00:00:00 2001 From: hyungun Date: Thu, 23 Nov 2023 18:27:16 +0900 Subject: [PATCH 143/165] =?UTF-8?q?refactor=20:=20upload=20=EB=AA=A8?= =?UTF-8?q?=EB=93=88=20exception=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 모두 CatchyException 으로 변경 --- server/src/config/errorCode.enum.ts | 1 + server/src/upload/upload.controller.ts | 11 +++++++---- server/src/upload/upload.service.ts | 27 ++++++++++++++++++++------ 3 files changed, 29 insertions(+), 10 deletions(-) diff --git a/server/src/config/errorCode.enum.ts b/server/src/config/errorCode.enum.ts index 13fe2b9..b1b342f 100644 --- a/server/src/config/errorCode.enum.ts +++ b/server/src/config/errorCode.enum.ts @@ -6,6 +6,7 @@ export enum ERROR_CODE { 'NOT_EXIST_PLAYLIST_ON_USER' = 4001, 'NOT_EXIST_MUSIC' = 4002, 'ALREADY_ADDED' = 4003, + 'INVALID_INPUT_VALUE' = 4004, 'WRONG_TOKEN' = 10000, 'BAD_REQUEST' = 10000, diff --git a/server/src/upload/upload.controller.ts b/server/src/upload/upload.controller.ts index 1c89312..7dd3f39 100644 --- a/server/src/upload/upload.controller.ts +++ b/server/src/upload/upload.controller.ts @@ -6,19 +6,18 @@ import { Query, HttpCode, UseGuards, - HttpException, UseInterceptors, UploadedFile, ParseFilePipe, - MaxFileSizeValidator, FileTypeValidator, } from '@nestjs/common'; import { UploadService } from './upload.service'; import { HTTP_STATUS_CODE } from 'src/httpStatusCode.enum'; import { AuthGuard } from '@nestjs/passport'; import { v4 } from 'uuid'; -import { fileSize } from '../constants'; import { FileInterceptor } from '@nestjs/platform-express'; +import { CatchyException } from 'src/config/catchyException'; +import { ERROR_CODE } from 'src/config/errorCode.enum'; @Controller('upload') export class UploadController { @@ -32,7 +31,11 @@ export class UploadController { return { uuid: v4() }; } catch (err) { console.log(err); - throw new HttpException('SERVER ERROR', HTTP_STATUS_CODE.SERVER_ERROR); + throw new CatchyException( + 'SERVER ERROR', + HTTP_STATUS_CODE.SERVER_ERROR, + ERROR_CODE.SERVER_ERROR, + ); } } diff --git a/server/src/upload/upload.service.ts b/server/src/upload/upload.service.ts index c49901b..1f5be0f 100644 --- a/server/src/upload/upload.service.ts +++ b/server/src/upload/upload.service.ts @@ -1,9 +1,11 @@ -import { HttpException, Injectable } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { HTTP_STATUS_CODE } from 'src/httpStatusCode.enum'; import { NcloudConfigService } from './../config/ncloud.config'; import { S3 } from 'aws-sdk'; import { keyFlags, keyHandler } from './../constants'; import { Readable } from 'stream'; +import { CatchyException } from 'src/config/catchyException'; +import { ERROR_CODE } from 'src/config/errorCode.enum'; @Injectable() export class UploadService { @@ -30,9 +32,10 @@ export class UploadService { async getSignedURL(type: string, uuid: string): Promise { try { if (!this.isValidUUIDPattern(uuid) || !this.isValidFlag(type)) - throw new HttpException( + throw new CatchyException( 'INVALID_INPUT_VALUE', HTTP_STATUS_CODE.BAD_REQUEST, + ERROR_CODE.INVALID_INPUT_VALUE, ); const keyPath = keyHandler[type](uuid); @@ -44,9 +47,13 @@ export class UploadService { ACL: 'public-read', }); } catch (error) { - if (error instanceof HttpException) throw error; + if (error instanceof CatchyException) throw error; - throw new HttpException('SERVER ERROR', HTTP_STATUS_CODE.SERVER_ERROR); + throw new CatchyException( + 'SERVER ERROR', + HTTP_STATUS_CODE.SERVER_ERROR, + ERROR_CODE.SERVICE_ERROR, + ); } } @@ -63,7 +70,11 @@ export class UploadService { return { url: uploadResult.Location }; } catch { - throw new HttpException('SERVER ERROR', HTTP_STATUS_CODE.SERVER_ERROR); + throw new CatchyException( + 'SERVER ERROR', + HTTP_STATUS_CODE.SERVER_ERROR, + ERROR_CODE.SERVICE_ERROR, + ); } } @@ -80,7 +91,11 @@ export class UploadService { return { url: uploadResult.Location }; } catch { - throw new HttpException('SERVER ERROR', HTTP_STATUS_CODE.SERVER_ERROR); + throw new CatchyException( + 'SERVER ERROR', + HTTP_STATUS_CODE.SERVER_ERROR, + ERROR_CODE.SERVICE_ERROR, + ); } } } From 36851717d4fea5736c71fefd66cee441cd036f18 Mon Sep 17 00:00:00 2001 From: hyungun Date: Thu, 23 Nov 2023 16:29:11 +0900 Subject: [PATCH 144/165] =?UTF-8?q?refactor=20:=20auth=20=EB=AA=A8?= =?UTF-8?q?=EB=93=88=20Exception=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 모두 CatchyException 으로 변경 --- server/src/auth/auth.service.ts | 25 ++++++++++++++++++++----- server/src/config/errorCode.enum.ts | 6 ++++-- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/server/src/auth/auth.service.ts b/server/src/auth/auth.service.ts index f49d14f..1d58561 100644 --- a/server/src/auth/auth.service.ts +++ b/server/src/auth/auth.service.ts @@ -1,6 +1,8 @@ -import { HttpException, Injectable } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; import { InjectRepository } from '@nestjs/typeorm'; +import { CatchyException } from 'src/config/catchyException'; +import { ERROR_CODE } from 'src/config/errorCode.enum'; import { RECENT_PLAYLIST_NAME } from 'src/constants'; import { UserCreateDto } from 'src/dto/userCreate.dto'; import { User } from 'src/entity/user.entity'; @@ -28,9 +30,10 @@ export class AuthService { return { accessToken }; } else { - throw new HttpException( + throw new CatchyException( 'NOT_EXIST_USER', HTTP_STATUS_CODE['WRONG_TOKEN'], + ERROR_CODE.NOT_EXIST_USER, ); } } @@ -40,7 +43,11 @@ export class AuthService { const email: string = await this.getGoogleEmail(idToken); if (await this.isExistEmail(email)) { - throw new HttpException('EXIST_EMAIL', HTTP_STATUS_CODE.BAD_REQUEST); + throw new CatchyException( + 'ALREADY_EXIST_EMAIL', + HTTP_STATUS_CODE.BAD_REQUEST, + ERROR_CODE.ALREADY_EXIST_EMAIL, + ); } if (email) { const newUser: User = this.userRepository.create({ @@ -57,7 +64,11 @@ export class AuthService { }); return this.login(email); } - throw new HttpException('WRONG_TOKEN', HTTP_STATUS_CODE.WRONG_TOKEN); + throw new CatchyException( + 'WRONG_TOKEN', + HTTP_STATUS_CODE.WRONG_TOKEN, + ERROR_CODE.WRONG_TOKEN, + ); } async getGoogleEmail(googleIdToken: string): Promise { @@ -68,7 +79,11 @@ export class AuthService { }).then((res) => res.json()); if (!userInfo.email) { - throw new HttpException('EXPIRED_TOKEN', HTTP_STATUS_CODE.WRONG_TOKEN); + throw new CatchyException( + 'EXPIRED_TOKEN', + HTTP_STATUS_CODE.WRONG_TOKEN, + ERROR_CODE.EXPIRED_TOKEN, + ); } return userInfo.email; } diff --git a/server/src/config/errorCode.enum.ts b/server/src/config/errorCode.enum.ts index b1b342f..78a733f 100644 --- a/server/src/config/errorCode.enum.ts +++ b/server/src/config/errorCode.enum.ts @@ -7,7 +7,9 @@ export enum ERROR_CODE { 'NOT_EXIST_MUSIC' = 4002, 'ALREADY_ADDED' = 4003, 'INVALID_INPUT_VALUE' = 4004, + 'NOT_EXIST_USER' = 4005, + 'ALREADY_EXIST_EMAIL' = 4006, - 'WRONG_TOKEN' = 10000, - 'BAD_REQUEST' = 10000, + 'WRONG_TOKEN' = 4100, + 'EXPIRED_TOKEN' = 4101, } From 6cc338cf36d58ccc97e3daf67de8637c816b056a Mon Sep 17 00:00:00 2001 From: hyungun Date: Thu, 23 Nov 2023 16:43:22 +0900 Subject: [PATCH 145/165] =?UTF-8?q?refactor=20:=20music=20=EB=AA=A8?= =?UTF-8?q?=EB=93=88=20exception=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 모두 CatchyException 으로 변경 --- server/src/config/errorCode.enum.ts | 1 + server/src/music/music.controller.ts | 16 +++------------- server/src/music/music.service.ts | 21 ++++++++++++++++----- 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/server/src/config/errorCode.enum.ts b/server/src/config/errorCode.enum.ts index 78a733f..e6cf932 100644 --- a/server/src/config/errorCode.enum.ts +++ b/server/src/config/errorCode.enum.ts @@ -9,6 +9,7 @@ export enum ERROR_CODE { 'INVALID_INPUT_VALUE' = 4004, 'NOT_EXIST_USER' = 4005, 'ALREADY_EXIST_EMAIL' = 4006, + 'NOT_EXIST_GENRE' = 4007, 'WRONG_TOKEN' = 4100, 'EXPIRED_TOKEN' = 4101, diff --git a/server/src/music/music.controller.ts b/server/src/music/music.controller.ts index 96ad3c4..96ed72c 100644 --- a/server/src/music/music.controller.ts +++ b/server/src/music/music.controller.ts @@ -3,7 +3,6 @@ import { Controller, Req, HttpCode, - HttpException, Post, Get, UseGuards, @@ -29,18 +28,9 @@ export class MusicController { @Body() musicCreateDto: MusicCreateDto, @Req() req, ): Promise<{ userId: string }> { - try { - const userId = req.user.user_id; - - this.musicService.createMusic(musicCreateDto, userId); - - return { userId }; - } catch (err) { - if (err instanceof HttpException) { - throw err; - } - throw new HttpException('WRONG TOKEN', HTTP_STATUS_CODE['WRONG_TOKEN']); - } + const userId = req.user.user_id; + this.musicService.createMusic(musicCreateDto, userId); + return { userId }; } @Get('recent-uploads') diff --git a/server/src/music/music.service.ts b/server/src/music/music.service.ts index 20ec5b9..e68fd5e 100644 --- a/server/src/music/music.service.ts +++ b/server/src/music/music.service.ts @@ -1,10 +1,12 @@ -import { HttpException, Injectable } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { HTTP_STATUS_CODE } from 'src/httpStatusCode.enum'; import { Repository } from 'typeorm'; import { InjectRepository } from '@nestjs/typeorm'; import { MusicCreateDto } from 'src/dto/musicCreate.dto'; import { Music } from 'src/entity/music.entity'; import { Genres } from 'src/constants'; +import { CatchyException } from 'src/config/catchyException'; +import { ERROR_CODE } from 'src/config/errorCode.enum'; @Injectable() export class MusicService { @@ -26,9 +28,10 @@ export class MusicService { const { title, cover, file: musicFile, genre } = musicCreateDto; if (!this.isValidGenre(genre)) { - throw new HttpException( + throw new CatchyException( 'NOT_EXIST_GENRE', HTTP_STATUS_CODE.BAD_REQUEST, + ERROR_CODE.NOT_EXIST_GENRE, ); } @@ -43,11 +46,15 @@ export class MusicService { this.musicRepository.save(newMusic); } catch (err) { - if (err instanceof HttpException) { + if (err instanceof CatchyException) { throw err; } - throw new HttpException('SERVER ERROR', HTTP_STATUS_CODE.SERVER_ERROR); + throw new CatchyException( + 'SERVER ERROR', + HTTP_STATUS_CODE.SERVER_ERROR, + ERROR_CODE.SERVICE_ERROR, + ); } } @@ -62,7 +69,11 @@ export class MusicService { return musics; } catch { - throw new HttpException('SERVER ERROR', HTTP_STATUS_CODE.SERVER_ERROR); + throw new CatchyException( + 'SERVER ERROR', + HTTP_STATUS_CODE.SERVER_ERROR, + ERROR_CODE.SERVICE_ERROR, + ); } } } From 6b18f5aec679ecae3b190be0ff9d8dda341a0870 Mon Sep 17 00:00:00 2001 From: hyungun Date: Thu, 23 Nov 2023 16:52:07 +0900 Subject: [PATCH 146/165] =?UTF-8?q?fix=20:=20upload=20=EB=A1=9C=EC=A7=81?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * music 업로드가 완료된 후 musicId 를 응답하도록 수정 --- server/src/music/music.controller.ts | 9 ++++++--- server/src/music/music.service.ts | 8 ++++++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/server/src/music/music.controller.ts b/server/src/music/music.controller.ts index 96ed72c..660ad25 100644 --- a/server/src/music/music.controller.ts +++ b/server/src/music/music.controller.ts @@ -27,10 +27,13 @@ export class MusicController { async upload( @Body() musicCreateDto: MusicCreateDto, @Req() req, - ): Promise<{ userId: string }> { + ): Promise<{ musicId: number }> { const userId = req.user.user_id; - this.musicService.createMusic(musicCreateDto, userId); - return { userId }; + const savedMusicId: number = await this.musicService.createMusic( + musicCreateDto, + userId, + ); + return { musicId: savedMusicId }; } @Get('recent-uploads') diff --git a/server/src/music/music.service.ts b/server/src/music/music.service.ts index e68fd5e..6ab93c8 100644 --- a/server/src/music/music.service.ts +++ b/server/src/music/music.service.ts @@ -23,7 +23,10 @@ export class MusicService { return false; } - createMusic(musicCreateDto: MusicCreateDto, user_id: string): void { + async createMusic( + musicCreateDto: MusicCreateDto, + user_id: string, + ): Promise { try { const { title, cover, file: musicFile, genre } = musicCreateDto; @@ -44,7 +47,8 @@ export class MusicService { user: { user_id: user_id }, }); - this.musicRepository.save(newMusic); + const savedMusic: Music = await this.musicRepository.save(newMusic); + return savedMusic.musicId; } catch (err) { if (err instanceof CatchyException) { throw err; From d1e06792af74d19c8f6e06942829d736afe73350 Mon Sep 17 00:00:00 2001 From: hyungun Date: Thu, 23 Nov 2023 18:31:33 +0900 Subject: [PATCH 147/165] =?UTF-8?q?refactor=20:=20Exception=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * JwtStrategy Exception 을 CatchyException 으로 변경 --- server/src/auth/jwt.strategy.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/server/src/auth/jwt.strategy.ts b/server/src/auth/jwt.strategy.ts index 6b8c7e2..59a758b 100644 --- a/server/src/auth/jwt.strategy.ts +++ b/server/src/auth/jwt.strategy.ts @@ -1,8 +1,10 @@ -import { HttpException, Injectable } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { PassportStrategy } from '@nestjs/passport'; import { InjectRepository } from '@nestjs/typeorm'; import { Strategy, ExtractJwt } from 'passport-jwt'; +import { CatchyException } from 'src/config/catchyException'; +import { ERROR_CODE } from 'src/config/errorCode.enum'; import { User } from 'src/entity/user.entity'; import { HTTP_STATUS_CODE } from 'src/httpStatusCode.enum'; import { Repository } from 'typeorm'; @@ -27,9 +29,10 @@ export class JwtStrategy extends PassportStrategy(Strategy) { }); if (!user) { - throw new HttpException( - 'Not Exist User', + throw new CatchyException( + 'NOT_EXIST_USER', HTTP_STATUS_CODE['WRONG_TOKEN'], + ERROR_CODE.NOT_EXIST_USER, ); } From ee55ca98c1e7ca21f958ac6bf8193afa6aad44de Mon Sep 17 00:00:00 2001 From: hyungun Date: Thu, 23 Nov 2023 18:35:22 +0900 Subject: [PATCH 148/165] =?UTF-8?q?refactor=20:=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=EC=9A=A9=20=EC=BD=94=EB=93=9C=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/src/playlist/playlist.controller.ts | 9 --------- 1 file changed, 9 deletions(-) diff --git a/server/src/playlist/playlist.controller.ts b/server/src/playlist/playlist.controller.ts index 5e4c330..6c12600 100644 --- a/server/src/playlist/playlist.controller.ts +++ b/server/src/playlist/playlist.controller.ts @@ -78,13 +78,4 @@ export class PlaylistController { const userId: string = req.user.user_id; return await this.playlistService.getPlaylistMusics(userId, playlistId); } - - @Patch('test') - test() { - throw new CatchyException( - 'testtest', - HTTP_STATUS_CODE.SERVER_ERROR, - ERROR_CODE.BAD_REQUEST, - ); - } } From 8eef0c5d26101c47eb4b24fedb1c9247b2b4e127 Mon Sep 17 00:00:00 2001 From: youlalala Date: Thu, 23 Nov 2023 20:52:29 +0900 Subject: [PATCH 149/165] =?UTF-8?q?feat=20:=20=EC=9D=8C=EC=95=85=20?= =?UTF-8?q?=EC=97=85=EB=A1=9C=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../catchytape/core/data/api/UploadApi.kt | 32 ++++++++++++------- .../core/data/model/MusicRequest.kt | 3 ++ .../catchytape/core/data/model/UrlResponse.kt | 8 +++++ .../core/data/model/UuidResponse.kt | 3 ++ .../core/data/repository/UrlRepositoryImpl.kt | 15 ++++----- .../core/domain/repository/UrlRepository.kt | 4 +-- .../core/domain/usecase/UploadFileUseCase.kt | 17 +++++----- .../feature/upload/UploadFragment.kt | 25 +++++++++++++-- 8 files changed, 73 insertions(+), 34 deletions(-) diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/api/UploadApi.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/api/UploadApi.kt index 90e5613..8695079 100644 --- a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/api/UploadApi.kt +++ b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/api/UploadApi.kt @@ -1,14 +1,19 @@ package com.ohdodok.catchytape.core.data.api +import com.ohdodok.catchytape.core.data.model.PreSignedUrlResponse import com.ohdodok.catchytape.core.data.model.UrlResponse import com.ohdodok.catchytape.core.data.model.UuidResponse -import kotlinx.serialization.SerialName import okhttp3.MultipartBody import retrofit2.Response +import retrofit2.http.FormUrlEncoded import retrofit2.http.GET +import retrofit2.http.Header import retrofit2.http.Multipart +import retrofit2.http.POST +import retrofit2.http.PUT import retrofit2.http.Part import retrofit2.http.Query +import retrofit2.http.Url interface UploadApi { @@ -17,18 +22,21 @@ interface UploadApi { ): Response @GET("upload") - @Multipart - suspend fun getUrl( - @Part file: MultipartBody.Part, + suspend fun getPreSignedUrl( @Query("uuid") uuid: String, - @Query("type") type: FileType, + @Query("type") type: String, + ): Response + + @Multipart + @POST("upload/music") + suspend fun postMusic( + @Part part: MultipartBody.Part, ): Response -} + @Multipart + @POST("upload/image") + suspend fun postImage( + @Part part: MultipartBody.Part, + ): Response -enum class FileType { - @SerialName("music") - MUSIC, - @SerialName("cover") - COVER -} \ No newline at end of file +} diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/model/MusicRequest.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/model/MusicRequest.kt index 671f6b2..2dab5f8 100644 --- a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/model/MusicRequest.kt +++ b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/model/MusicRequest.kt @@ -1,5 +1,8 @@ package com.ohdodok.catchytape.core.data.model +import kotlinx.serialization.Serializable + +@Serializable data class MusicRequest ( val title: String, val cover: String, diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/model/UrlResponse.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/model/UrlResponse.kt index c359d14..a816196 100644 --- a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/model/UrlResponse.kt +++ b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/model/UrlResponse.kt @@ -1,5 +1,13 @@ package com.ohdodok.catchytape.core.data.model +import kotlinx.serialization.Serializable + +@Serializable +data class PreSignedUrlResponse( + val signedUrl: String +) + +@Serializable data class UrlResponse( val url: String ) \ No newline at end of file diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/model/UuidResponse.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/model/UuidResponse.kt index 43a48c1..e760d56 100644 --- a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/model/UuidResponse.kt +++ b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/model/UuidResponse.kt @@ -1,5 +1,8 @@ package com.ohdodok.catchytape.core.data.model +import kotlinx.serialization.Serializable + +@Serializable data class UuidResponse ( val uuid: String ) \ No newline at end of file diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/UrlRepositoryImpl.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/UrlRepositoryImpl.kt index d9e0e79..3650d38 100644 --- a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/UrlRepositoryImpl.kt +++ b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/UrlRepositoryImpl.kt @@ -1,6 +1,5 @@ package com.ohdodok.catchytape.core.data.repository -import com.ohdodok.catchytape.core.data.api.FileType import com.ohdodok.catchytape.core.data.api.UploadApi import com.ohdodok.catchytape.core.domain.repository.UrlRepository import kotlinx.coroutines.flow.Flow @@ -25,9 +24,8 @@ class UrlRepositoryImpl @Inject constructor( } } - override fun getImageUrl(uuid: String, file: File): Flow = flow { - val response = - uploadApi.getUrl(uuid = uuid, type = FileType.COVER, file = file.toMultipart()) + override fun getImageUrl(file: File): Flow = flow { + val response = uploadApi.postImage(file.toMultipart("image/png")) if (response.isSuccessful) { response.body()?.let { urlResponse -> emit(urlResponse.url) } } else { @@ -36,9 +34,8 @@ class UrlRepositoryImpl @Inject constructor( } } - override fun getAudioUrl(uuid: String, file: File): Flow = flow { - val response = - uploadApi.getUrl(uuid = uuid, type = FileType.MUSIC, file = file.toMultipart()) + override fun getAudioUrl(file: File): Flow = flow { + val response = uploadApi.postMusic(file.toMultipart("audio/mpeg")) if (response.isSuccessful) { response.body()?.let { urlResponse -> emit(urlResponse.url) } } else { @@ -47,8 +44,8 @@ class UrlRepositoryImpl @Inject constructor( } } - private fun File.toMultipart(): MultipartBody.Part { - val fileBody = this.asRequestBody("multipart/form-data".toMediaTypeOrNull()) + private fun File.toMultipart(contentType: String): MultipartBody.Part { + val fileBody = this.asRequestBody(contentType.toMediaTypeOrNull()) return MultipartBody.Part.createFormData("file", this.name, fileBody) } } \ No newline at end of file diff --git a/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/repository/UrlRepository.kt b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/repository/UrlRepository.kt index baf6728..82f2f07 100644 --- a/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/repository/UrlRepository.kt +++ b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/repository/UrlRepository.kt @@ -7,8 +7,8 @@ interface UrlRepository { fun getUuid(): Flow - fun getImageUrl(uuid: String, file: File): Flow + fun getImageUrl(file: File): Flow - fun getAudioUrl(uuid: String, file: File): Flow + fun getAudioUrl(file: File): Flow } \ No newline at end of file diff --git a/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/UploadFileUseCase.kt b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/UploadFileUseCase.kt index ecc741b..9a3cfa3 100644 --- a/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/UploadFileUseCase.kt +++ b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/UploadFileUseCase.kt @@ -2,8 +2,6 @@ package com.ohdodok.catchytape.core.domain.usecase import com.ohdodok.catchytape.core.domain.repository.UrlRepository import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.single import java.io.File import javax.inject.Inject @@ -11,11 +9,14 @@ class UploadFileUseCase @Inject constructor( private val urlRepository: UrlRepository ) { - fun getImgUrl(file: File): Flow = urlRepository.getUuid().map { uuid -> - urlRepository.getImageUrl(uuid, file).single() - } + fun getImgUrl(file: File): Flow = urlRepository.getImageUrl(file) + + fun getAudioUrl(file: File): Flow = urlRepository.getAudioUrl(file) + + + // TODO : 나중에 쓸 부분임 +// fun getAudioUrl(file: File): Flow = urlRepository.getUuid().map { uuid -> +// urlRepository.getAudioUrl(uuid, file).single() +// } - fun getAudioUrl(file: File): Flow = urlRepository.getUuid().map { uuid -> - urlRepository.getAudioUrl(uuid, file).single() - } } \ No newline at end of file diff --git a/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadFragment.kt b/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadFragment.kt index be77a0d..bf3ac25 100644 --- a/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadFragment.kt +++ b/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadFragment.kt @@ -1,7 +1,12 @@ package com.ohdodok.catchytape.feature.upload +import android.content.ContentValues import android.net.Uri +import android.os.Build import android.os.Bundle +import android.os.Environment +import android.provider.DocumentsContract +import android.provider.MediaStore import android.provider.OpenableColumns import android.view.View import androidx.activity.result.PickVisualMediaRequest @@ -13,7 +18,9 @@ import com.ohdodok.catchytape.catchytape.upload.R import com.ohdodok.catchytape.catchytape.upload.databinding.FragmentUploadBinding import com.ohdodok.catchytape.core.ui.BaseFragment import dagger.hilt.android.AndroidEntryPoint +import timber.log.Timber import java.io.File +import java.io.FileOutputStream @AndroidEntryPoint class UploadFragment : BaseFragment(R.layout.fragment_upload) { @@ -21,14 +28,14 @@ class UploadFragment : BaseFragment(R.layout.fragment_upl private val imagePickerLauncher = registerForActivityResult(PickVisualMedia()) { uri -> if (uri == null) return@registerForActivityResult - uri.path?.let { viewModel.uploadImage(File(it)) } + uri.toPath()?.let { path -> viewModel.uploadImage(File(path)) } } private val filePickerLauncher = registerForActivityResult(ActivityResultContracts.GetContent()) { uri -> if (uri == null) return@registerForActivityResult + uri.toPath()?.let { path -> viewModel.uploadAudio(File(path)) } binding.btnFile.text = getFileName(uri) - uri.path?.let { viewModel.uploadAudio(File(it)) } } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -82,4 +89,16 @@ class UploadFragment : BaseFragment(R.layout.fragment_upl } return null } -} \ No newline at end of file + + private fun Uri.toPath(): String? { + val file = getFileName(this)?.let { File(requireContext().filesDir, it) } + val inputStream = requireContext().contentResolver.openInputStream(this) + val outputStream = FileOutputStream(file) + inputStream.use { input -> + outputStream.use { output -> + input?.copyTo(output) + } + } + return file?.absolutePath + } +} From f3561331901a5e7c1ba4b86dd7dd20f4c235d48c Mon Sep 17 00:00:00 2001 From: youlalala Date: Thu, 23 Nov 2023 21:07:18 +0900 Subject: [PATCH 150/165] =?UTF-8?q?chore=20:=20=ED=95=84=EC=9A=94=20?= =?UTF-8?q?=EC=97=86=EB=8A=94=20import=20=EB=AC=B8=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/ohdodok/catchytape/core/data/api/UploadApi.kt | 4 ---- .../com/ohdodok/catchytape/feature/upload/UploadFragment.kt | 6 ------ 2 files changed, 10 deletions(-) diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/api/UploadApi.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/api/UploadApi.kt index 8695079..88a47f4 100644 --- a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/api/UploadApi.kt +++ b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/api/UploadApi.kt @@ -5,15 +5,11 @@ import com.ohdodok.catchytape.core.data.model.UrlResponse import com.ohdodok.catchytape.core.data.model.UuidResponse import okhttp3.MultipartBody import retrofit2.Response -import retrofit2.http.FormUrlEncoded import retrofit2.http.GET -import retrofit2.http.Header import retrofit2.http.Multipart import retrofit2.http.POST -import retrofit2.http.PUT import retrofit2.http.Part import retrofit2.http.Query -import retrofit2.http.Url interface UploadApi { diff --git a/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadFragment.kt b/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadFragment.kt index bf3ac25..8654816 100644 --- a/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadFragment.kt +++ b/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadFragment.kt @@ -1,12 +1,7 @@ package com.ohdodok.catchytape.feature.upload -import android.content.ContentValues import android.net.Uri -import android.os.Build import android.os.Bundle -import android.os.Environment -import android.provider.DocumentsContract -import android.provider.MediaStore import android.provider.OpenableColumns import android.view.View import androidx.activity.result.PickVisualMediaRequest @@ -18,7 +13,6 @@ import com.ohdodok.catchytape.catchytape.upload.R import com.ohdodok.catchytape.catchytape.upload.databinding.FragmentUploadBinding import com.ohdodok.catchytape.core.ui.BaseFragment import dagger.hilt.android.AndroidEntryPoint -import timber.log.Timber import java.io.File import java.io.FileOutputStream From cd9d4b2e3a1107e4898223f35002ac1c8bf5e7e4 Mon Sep 17 00:00:00 2001 From: youlalala Date: Thu, 23 Nov 2023 21:42:14 +0900 Subject: [PATCH 151/165] =?UTF-8?q?feat=20:=20=EC=B5=9C=EA=B7=BC=20?= =?UTF-8?q?=EC=97=85=EB=A1=9C=EB=93=9C=ED=95=9C=20=EB=85=B8=EB=9E=98=20?= =?UTF-8?q?=EC=84=9C=EB=B2=84=EC=97=90=EC=84=9C=20=EA=B0=80=EC=A0=B8?= =?UTF-8?q?=EC=98=A4=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../catchytape/core/data/api/MusicApi.kt | 4 + .../catchytape/core/data/di/NetworkModule.kt | 3 +- .../core/data/model/MusicResponse.kt | 12 ++ .../core/data/model/MusicsResponse.kt | 8 + .../data/repository/MusicRepositoryImpl.kt | 20 +++ .../catchytape/core/domain/model/Music.kt | 2 +- .../core/domain/repository/MusicRepository.kt | 3 + .../domain/usecase/GetRecentUploadedMusic.kt | 12 ++ .../main/res/layout/item_music_horizontal.xml | 1 + .../main/res/layout/item_music_vertical.xml | 1 + .../catchytape/feature/home/HomeViewModel.kt | 30 +--- .../src/main/res/layout/fragment_home.xml | 138 +++++++++--------- 12 files changed, 145 insertions(+), 89 deletions(-) create mode 100644 android/core/data/src/main/java/com/ohdodok/catchytape/core/data/model/MusicResponse.kt create mode 100644 android/core/data/src/main/java/com/ohdodok/catchytape/core/data/model/MusicsResponse.kt create mode 100644 android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/GetRecentUploadedMusic.kt diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/api/MusicApi.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/api/MusicApi.kt index f93f719..1b263a2 100644 --- a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/api/MusicApi.kt +++ b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/api/MusicApi.kt @@ -2,6 +2,7 @@ package com.ohdodok.catchytape.core.data.api import com.ohdodok.catchytape.core.data.model.MusicGenresResponse import com.ohdodok.catchytape.core.data.model.MusicRequest +import com.ohdodok.catchytape.core.data.model.MusicsResponse import retrofit2.Response import retrofit2.http.Body import retrofit2.http.GET @@ -17,4 +18,7 @@ interface MusicApi { @Body music: MusicRequest ): Response + @GET("musics/recent-uploads") + suspend fun getRecentUploads(): Response + } \ No newline at end of file diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/di/NetworkModule.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/di/NetworkModule.kt index 1fe3996..98adbca 100644 --- a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/di/NetworkModule.kt +++ b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/di/NetworkModule.kt @@ -61,8 +61,9 @@ object NetworkModule { @Singleton @Provides fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit { + val jsonConfig = Json { ignoreUnknownKeys = true } return Retrofit.Builder() - .addConverterFactory(Json.asConverterFactory("application/json".toMediaType())) + .addConverterFactory(jsonConfig.asConverterFactory("application/json".toMediaType())) .client(okHttpClient) .baseUrl(BuildConfig.BASE_URL) .build() diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/model/MusicResponse.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/model/MusicResponse.kt new file mode 100644 index 0000000..17e181d --- /dev/null +++ b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/model/MusicResponse.kt @@ -0,0 +1,12 @@ +package com.ohdodok.catchytape.core.data.model + +import kotlinx.serialization.Serializable + +@Serializable +data class MusicResponse ( + val musicId: Int, + val title: String, + val cover: String, + val musicFile : String, + val genre: String, +) \ No newline at end of file diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/model/MusicsResponse.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/model/MusicsResponse.kt new file mode 100644 index 0000000..d730918 --- /dev/null +++ b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/model/MusicsResponse.kt @@ -0,0 +1,8 @@ +package com.ohdodok.catchytape.core.data.model + +import kotlinx.serialization.Serializable + +@Serializable +data class MusicsResponse ( + val musics: List +) \ No newline at end of file diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/MusicRepositoryImpl.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/MusicRepositoryImpl.kt index a60a139..7547cb5 100644 --- a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/MusicRepositoryImpl.kt +++ b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/MusicRepositoryImpl.kt @@ -1,6 +1,8 @@ package com.ohdodok.catchytape.core.data.repository import com.ohdodok.catchytape.core.data.api.MusicApi +import com.ohdodok.catchytape.core.data.model.MusicResponse +import com.ohdodok.catchytape.core.domain.model.Music import com.ohdodok.catchytape.core.domain.repository.MusicRepository import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow @@ -18,4 +20,22 @@ class MusicRepositoryImpl @Inject constructor( else -> throw RuntimeException("네트워크 에러") } } + + override fun getRecentUploadedMusic(): Flow> = flow { + val response = musicApi.getRecentUploads() + when (response.code()) { + // TODO : 네트워크 에러 로직 처리 + in 200..299 -> emit(response.body()?.musics?.map { it.toDomain() } ?: emptyList()) + else -> throw RuntimeException("네트워크 에러") + } + } +} + +fun MusicResponse.toDomain() : Music { + return Music( + id = musicId, + title = title, + artist = "유랄라", + imageUrl = cover + ) } \ No newline at end of file diff --git a/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/model/Music.kt b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/model/Music.kt index d157001..442e3c8 100644 --- a/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/model/Music.kt +++ b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/model/Music.kt @@ -1,7 +1,7 @@ package com.ohdodok.catchytape.core.domain.model data class Music( - val id: String, + val id: Int, val title: String, val artist: String, val imageUrl: String diff --git a/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/repository/MusicRepository.kt b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/repository/MusicRepository.kt index 2dbacd5..f71a201 100644 --- a/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/repository/MusicRepository.kt +++ b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/repository/MusicRepository.kt @@ -1,8 +1,11 @@ package com.ohdodok.catchytape.core.domain.repository +import com.ohdodok.catchytape.core.domain.model.Music import kotlinx.coroutines.flow.Flow interface MusicRepository { fun getGenres(): Flow> + + fun getRecentUploadedMusic(): Flow> } \ No newline at end of file diff --git a/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/GetRecentUploadedMusic.kt b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/GetRecentUploadedMusic.kt new file mode 100644 index 0000000..3335e56 --- /dev/null +++ b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/GetRecentUploadedMusic.kt @@ -0,0 +1,12 @@ +package com.ohdodok.catchytape.core.domain.usecase + +import com.ohdodok.catchytape.core.domain.model.Music +import com.ohdodok.catchytape.core.domain.repository.MusicRepository +import kotlinx.coroutines.flow.Flow +import javax.inject.Inject + +class GetRecentUploadedMusic @Inject constructor( + private val musicRepository: MusicRepository +) { + operator fun invoke(): Flow> = musicRepository.getRecentUploadedMusic() +} \ No newline at end of file diff --git a/android/core/ui/src/main/res/layout/item_music_horizontal.xml b/android/core/ui/src/main/res/layout/item_music_horizontal.xml index 3f4bf6e..0c1edcb 100644 --- a/android/core/ui/src/main/res/layout/item_music_horizontal.xml +++ b/android/core/ui/src/main/res/layout/item_music_horizontal.xml @@ -19,6 +19,7 @@ android:id="@+id/iv_thumbnail" android:layout_width="@dimen/music_horizontal_img" android:layout_height="@dimen/music_horizontal_img" + app:imgUrl="@{music.imageUrl}" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" diff --git a/android/core/ui/src/main/res/layout/item_music_vertical.xml b/android/core/ui/src/main/res/layout/item_music_vertical.xml index 5b11df3..90b107c 100644 --- a/android/core/ui/src/main/res/layout/item_music_vertical.xml +++ b/android/core/ui/src/main/res/layout/item_music_vertical.xml @@ -19,6 +19,7 @@ android:id="@+id/iv_thumbnail" android:layout_width="@dimen/music_vertical_img" android:layout_height="@dimen/music_vertical_img" + app:imgUrl="@{music.imageUrl}" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" tools:background="@color/on_surface" /> diff --git a/android/feature/home/src/main/java/com/ohdodok/catchytape/feature/home/HomeViewModel.kt b/android/feature/home/src/main/java/com/ohdodok/catchytape/feature/home/HomeViewModel.kt index 4d9649d..14f2adf 100644 --- a/android/feature/home/src/main/java/com/ohdodok/catchytape/feature/home/HomeViewModel.kt +++ b/android/feature/home/src/main/java/com/ohdodok/catchytape/feature/home/HomeViewModel.kt @@ -3,32 +3,23 @@ package com.ohdodok.catchytape.feature.home import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.ohdodok.catchytape.core.domain.model.Music -import kotlinx.coroutines.flow.Flow +import com.ohdodok.catchytape.core.domain.usecase.GetRecentUploadedMusic +import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.update +import javax.inject.Inject data class HomeUiState( val recentlyUploadedMusics: List = emptyList() ) -class HomeViewModel constructor( - // todo : DI로 주입하는 코드로 변경 - private val getMusicUseCase: GetMusicUseCase = GetMusicUseCase { - flow { - emit( - listOf( - Music("1", "title1", "artist1", ""), - Music("2", "title2", "artist2", ""), - Music("3", "title3", "artist3", "") - ) // 화면 확인용 Dummy data 입니다. - ) - } - }, +@HiltViewModel +class HomeViewModel @Inject constructor( + private val getRecentUploadedMusicUseCase: GetRecentUploadedMusic ) : ViewModel() { private val _uiState = MutableStateFlow(HomeUiState()) @@ -39,7 +30,7 @@ class HomeViewModel constructor( } private fun fetchUploadedMusics() { - getMusicUseCase() + getRecentUploadedMusicUseCase() .onEach { musics -> _uiState.update { it.copy( @@ -49,9 +40,4 @@ class HomeViewModel constructor( } .launchIn(viewModelScope) } -} - -// todo : domain layer로 이동 -fun interface GetMusicUseCase { - operator fun invoke(): Flow> -} +} \ No newline at end of file diff --git a/android/feature/home/src/main/res/layout/fragment_home.xml b/android/feature/home/src/main/res/layout/fragment_home.xml index f204355..0b61b41 100644 --- a/android/feature/home/src/main/res/layout/fragment_home.xml +++ b/android/feature/home/src/main/res/layout/fragment_home.xml @@ -10,79 +10,87 @@ type="com.ohdodok.catchytape.feature.home.HomeViewModel" /> - - + - + - + - + - + - + - + + + + + \ No newline at end of file From 6ac576b84fa3497375f12b5fe0827ffb1f5708a2 Mon Sep 17 00:00:00 2001 From: youlalala Date: Thu, 23 Nov 2023 22:03:13 +0900 Subject: [PATCH 152/165] =?UTF-8?q?refactor=20:=20upload=20event=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EB=AA=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ohdodok/catchytape/feature/upload/UploadViewModel.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadViewModel.kt b/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadViewModel.kt index 154eb47..207b068 100644 --- a/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadViewModel.kt +++ b/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadViewModel.kt @@ -29,7 +29,7 @@ class UploadViewModel @Inject constructor( private val uploadMusicUseCase: UploadMusicUseCase ) : ViewModel() { - private val _events = MutableSharedFlow() + private val _events = MutableSharedFlow() val events = _events.asSharedFlow() val musicTitle = MutableStateFlow("") @@ -102,7 +102,7 @@ class UploadViewModel @Inject constructor( title = musicTitle.value, genre = musicGenre.value ).onEach { - _events.emit(UploadEventState.NavigateToBack) + _events.emit(UploadEvent.NavigateToBack) }.catch { // TODO : 업로드 실패 }.launchIn(viewModelScope) @@ -115,7 +115,7 @@ data class UploadedFileState( val url: String = "" ) -sealed interface UploadEventState { - data object NavigateToBack : UploadEventState +sealed interface UploadEvent { + data object NavigateToBack : UploadEvent } From 37be214f8bbac7f2413ea05af59d66350e0017a6 Mon Sep 17 00:00:00 2001 From: Seokyung Lim Date: Thu, 23 Nov 2023 22:09:39 +0900 Subject: [PATCH 153/165] =?UTF-8?q?fix=20:=20=EC=B5=9C=EA=B7=BC=20?= =?UTF-8?q?=EC=97=85=EB=A1=9C=EB=93=9C=20=EB=90=9C=20=EC=9D=8C=EC=95=85=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=EC=97=90=20user=20=EC=A0=95=EB=B3=B4=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/src/music/music.controller.ts | 4 ++-- server/src/music/music.service.ts | 20 +++++++++++++++++--- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/server/src/music/music.controller.ts b/server/src/music/music.controller.ts index 660ad25..62b59bb 100644 --- a/server/src/music/music.controller.ts +++ b/server/src/music/music.controller.ts @@ -38,10 +38,10 @@ export class MusicController { @Get('recent-uploads') @HttpCode(HTTP_STATUS_CODE.SUCCESS) - async getRecentMusics(): Promise<{ musics: Music[] }> { + async getRecentMusics(): Promise { const musics = await this.musicService.getRecentMusic(); - return { musics }; + return musics; } @Get('genres') diff --git a/server/src/music/music.service.ts b/server/src/music/music.service.ts index 6ab93c8..3a91361 100644 --- a/server/src/music/music.service.ts +++ b/server/src/music/music.service.ts @@ -64,14 +64,28 @@ export class MusicService { async getRecentMusic(): Promise { try { - const musics = await this.musicRepository.find({ + return await this.musicRepository.find({ + relations: { + user: true, + }, + select: { + musicId: true, + title: true, + lyrics: true, + cover: true, + musicFile: true, + genre: true, + created_at: true, + user: { + user_id: true, + nickname: true, + }, + }, order: { created_at: 'DESC', }, take: 10, }); - - return musics; } catch { throw new CatchyException( 'SERVER ERROR', From 3e0f4c3d90159d5b09397e539d32a12340ea810a Mon Sep 17 00:00:00 2001 From: hyungun Date: Thu, 23 Nov 2023 21:56:25 +0900 Subject: [PATCH 154/165] =?UTF-8?q?feat=20:=20music=20entity=20=ED=95=A8?= =?UTF-8?q?=EC=88=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * userId 로 업로드한 음악 조회하는 함수 추가 --- server/src/entity/music.entity.ts | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/server/src/entity/music.entity.ts b/server/src/entity/music.entity.ts index 5f94661..89f8f7f 100644 --- a/server/src/entity/music.entity.ts +++ b/server/src/entity/music.entity.ts @@ -41,4 +41,31 @@ export class Music extends BaseEntity { @OneToMany(() => Music_Playlist, (music_playlist) => music_playlist.music) music_playlist: Music_Playlist[]; + + static async getMusicListByUserId( + userId: string, + count: number, + ): Promise { + return await this.find({ + relations: { + user: true, + }, + where: { + user: { user_id: userId }, + }, + select: { + musicId: true, + title: true, + cover: true, + musicFile: true, + genre: true, + created_at: true, + user: { user_id: true, nickname: true }, + }, + order: { + created_at: 'DESC', + }, + take: count, + }); + } } From 666d97cf42846a9641291cc6ac36f0c2c58099d5 Mon Sep 17 00:00:00 2001 From: hyungun Date: Thu, 23 Nov 2023 21:57:09 +0900 Subject: [PATCH 155/165] =?UTF-8?q?feat=20:=20=EB=82=B4=EA=B0=80=20?= =?UTF-8?q?=EC=97=85=EB=A1=9C=EB=93=9C=ED=95=9C=20=EC=9D=8C=EC=95=85=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * /musics/my-uploads 로 조회 가능 * 최근 업로드 순으로 응답 --- server/src/music/music.controller.ts | 12 ++++++++++++ server/src/music/music.service.ts | 5 ++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/server/src/music/music.controller.ts b/server/src/music/music.controller.ts index 62b59bb..3b64d30 100644 --- a/server/src/music/music.controller.ts +++ b/server/src/music/music.controller.ts @@ -8,6 +8,7 @@ import { UseGuards, UsePipes, ValidationPipe, + Query, } from '@nestjs/common'; import { MusicService } from './music.service'; import { HTTP_STATUS_CODE } from 'src/httpStatusCode.enum'; @@ -51,4 +52,15 @@ export class MusicController { return { genres: genreName }; } + + @Get('my-uploads') + @UseGuards(AuthGuard()) + @HttpCode(HTTP_STATUS_CODE.SUCCESS) + async getMyUploads( + @Req() req, + @Query('count') count: number, + ): Promise { + const userId: string = req.user.user_id; + return this.musicService.getMyUploads(userId, count); + } } diff --git a/server/src/music/music.service.ts b/server/src/music/music.service.ts index 3a91361..d750abb 100644 --- a/server/src/music/music.service.ts +++ b/server/src/music/music.service.ts @@ -10,7 +10,6 @@ import { ERROR_CODE } from 'src/config/errorCode.enum'; @Injectable() export class MusicService { - //TODO: custom repository로 변경하기 constructor( @InjectRepository(Music) private musicRepository: Repository, ) {} @@ -94,4 +93,8 @@ export class MusicService { ); } } + + async getMyUploads(userId: string, count: number): Promise { + return Music.getMusicListByUserId(userId, count); + } } From 25443c88ffde5309c4b62da65762339835d9a679 Mon Sep 17 00:00:00 2001 From: hyungun Date: Thu, 23 Nov 2023 21:59:14 +0900 Subject: [PATCH 156/165] =?UTF-8?q?feat=20:=20=EC=98=88=EC=99=B8=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/src/music/music.service.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/server/src/music/music.service.ts b/server/src/music/music.service.ts index d750abb..9f17720 100644 --- a/server/src/music/music.service.ts +++ b/server/src/music/music.service.ts @@ -95,6 +95,14 @@ export class MusicService { } async getMyUploads(userId: string, count: number): Promise { - return Music.getMusicListByUserId(userId, count); + try { + return Music.getMusicListByUserId(userId, count); + } catch { + throw new CatchyException( + 'SERVER_ERROR', + HTTP_STATUS_CODE.SERVER_ERROR, + ERROR_CODE.SERVICE_ERROR, + ); + } } } From fc418632def345b3c192aa2809b95af1400a1895 Mon Sep 17 00:00:00 2001 From: hyungun Date: Thu, 23 Nov 2023 22:08:33 +0900 Subject: [PATCH 157/165] =?UTF-8?q?feat=20:=20=EC=9D=91=EB=8B=B5=EC=97=90?= =?UTF-8?q?=20=EA=B0=80=EC=82=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 내가 업로드한 음악에 가사 추가 --- server/src/entity/music.entity.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/server/src/entity/music.entity.ts b/server/src/entity/music.entity.ts index 89f8f7f..7b54de1 100644 --- a/server/src/entity/music.entity.ts +++ b/server/src/entity/music.entity.ts @@ -56,6 +56,7 @@ export class Music extends BaseEntity { select: { musicId: true, title: true, + lyrics: true, cover: true, musicFile: true, genre: true, From 5e3e89a42ef69a1e3c139761eabd6ee6ba16c3d0 Mon Sep 17 00:00:00 2001 From: hyungun Date: Thu, 23 Nov 2023 22:20:30 +0900 Subject: [PATCH 158/165] =?UTF-8?q?refactor=20:=20=EA=B8=B4=20=EC=BF=BC?= =?UTF-8?q?=EB=A6=AC=EB=AC=B8=20entity=20=EC=95=88=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * music service 에 있는 쿼리문 이동 --- server/src/entity/music.entity.ts | 27 ++++++++++++++++++++++++++- server/src/music/music.controller.ts | 2 +- server/src/music/music.service.ts | 23 +---------------------- 3 files changed, 28 insertions(+), 24 deletions(-) diff --git a/server/src/entity/music.entity.ts b/server/src/entity/music.entity.ts index 7b54de1..b48f261 100644 --- a/server/src/entity/music.entity.ts +++ b/server/src/entity/music.entity.ts @@ -46,7 +46,7 @@ export class Music extends BaseEntity { userId: string, count: number, ): Promise { - return await this.find({ + return this.find({ relations: { user: true, }, @@ -69,4 +69,29 @@ export class Music extends BaseEntity { take: count, }); } + + static async getRecentMusic(): Promise { + return this.find({ + relations: { + user: true, + }, + select: { + musicId: true, + title: true, + lyrics: true, + cover: true, + musicFile: true, + genre: true, + created_at: true, + user: { + user_id: true, + nickname: true, + }, + }, + order: { + created_at: 'DESC', + }, + take: 10, + }); + } } diff --git a/server/src/music/music.controller.ts b/server/src/music/music.controller.ts index 3b64d30..412674a 100644 --- a/server/src/music/music.controller.ts +++ b/server/src/music/music.controller.ts @@ -40,7 +40,7 @@ export class MusicController { @Get('recent-uploads') @HttpCode(HTTP_STATUS_CODE.SUCCESS) async getRecentMusics(): Promise { - const musics = await this.musicService.getRecentMusic(); + const musics = this.musicService.getRecentMusic(); return musics; } diff --git a/server/src/music/music.service.ts b/server/src/music/music.service.ts index 9f17720..c8dc255 100644 --- a/server/src/music/music.service.ts +++ b/server/src/music/music.service.ts @@ -63,28 +63,7 @@ export class MusicService { async getRecentMusic(): Promise { try { - return await this.musicRepository.find({ - relations: { - user: true, - }, - select: { - musicId: true, - title: true, - lyrics: true, - cover: true, - musicFile: true, - genre: true, - created_at: true, - user: { - user_id: true, - nickname: true, - }, - }, - order: { - created_at: 'DESC', - }, - take: 10, - }); + return Music.getRecentMusic(); } catch { throw new CatchyException( 'SERVER ERROR', From 55e0ef843e26fadeb1326aedbffa7c3070011e54 Mon Sep 17 00:00:00 2001 From: youlalala Date: Thu, 23 Nov 2023 22:33:33 +0900 Subject: [PATCH 159/165] =?UTF-8?q?feat=20:=20=EC=B5=9C=EA=B7=BC=20?= =?UTF-8?q?=EC=97=85=EB=A1=9C=EB=93=9C=EB=90=9C=20=EB=85=B8=EB=9E=98?= =?UTF-8?q?=EC=97=90=20artist=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/ohdodok/catchytape/core/data/api/MusicApi.kt | 4 ++-- .../ohdodok/catchytape/core/data/model/MusicResponse.kt | 1 + .../ohdodok/catchytape/core/data/model/MusicsResponse.kt | 8 -------- .../catchytape/core/data/model/NicknameResponse.kt | 4 ++-- .../core/data/repository/MusicRepositoryImpl.kt | 6 +++--- 5 files changed, 8 insertions(+), 15 deletions(-) delete mode 100644 android/core/data/src/main/java/com/ohdodok/catchytape/core/data/model/MusicsResponse.kt diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/api/MusicApi.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/api/MusicApi.kt index 1b263a2..58a77c1 100644 --- a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/api/MusicApi.kt +++ b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/api/MusicApi.kt @@ -2,7 +2,7 @@ package com.ohdodok.catchytape.core.data.api import com.ohdodok.catchytape.core.data.model.MusicGenresResponse import com.ohdodok.catchytape.core.data.model.MusicRequest -import com.ohdodok.catchytape.core.data.model.MusicsResponse +import com.ohdodok.catchytape.core.data.model.MusicResponse import retrofit2.Response import retrofit2.http.Body import retrofit2.http.GET @@ -19,6 +19,6 @@ interface MusicApi { ): Response @GET("musics/recent-uploads") - suspend fun getRecentUploads(): Response + suspend fun getRecentUploads(): Response> } \ No newline at end of file diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/model/MusicResponse.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/model/MusicResponse.kt index 17e181d..f0595fc 100644 --- a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/model/MusicResponse.kt +++ b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/model/MusicResponse.kt @@ -9,4 +9,5 @@ data class MusicResponse ( val cover: String, val musicFile : String, val genre: String, + val user: NicknameResponse ) \ No newline at end of file diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/model/MusicsResponse.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/model/MusicsResponse.kt deleted file mode 100644 index d730918..0000000 --- a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/model/MusicsResponse.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.ohdodok.catchytape.core.data.model - -import kotlinx.serialization.Serializable - -@Serializable -data class MusicsResponse ( - val musics: List -) \ No newline at end of file diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/model/NicknameResponse.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/model/NicknameResponse.kt index 1bbcff4..6218f16 100644 --- a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/model/NicknameResponse.kt +++ b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/model/NicknameResponse.kt @@ -4,5 +4,5 @@ import kotlinx.serialization.Serializable @Serializable data class NicknameResponse( - val nickname: String -) + val nickname: String, +) \ No newline at end of file diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/MusicRepositoryImpl.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/MusicRepositoryImpl.kt index 7547cb5..3f47421 100644 --- a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/MusicRepositoryImpl.kt +++ b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/MusicRepositoryImpl.kt @@ -25,17 +25,17 @@ class MusicRepositoryImpl @Inject constructor( val response = musicApi.getRecentUploads() when (response.code()) { // TODO : 네트워크 에러 로직 처리 - in 200..299 -> emit(response.body()?.musics?.map { it.toDomain() } ?: emptyList()) + in 200..299 -> emit(response.body()?.map { it.toDomain() } ?: emptyList()) else -> throw RuntimeException("네트워크 에러") } } } -fun MusicResponse.toDomain() : Music { +fun MusicResponse.toDomain(): Music { return Music( id = musicId, title = title, - artist = "유랄라", + artist = user.nickname, imageUrl = cover ) } \ No newline at end of file From fd4132c48f8b86211c57937ba58ae744739343ba Mon Sep 17 00:00:00 2001 From: youlalala Date: Thu, 23 Nov 2023 22:42:40 +0900 Subject: [PATCH 160/165] =?UTF-8?q?refactor=20:=20=EC=98=A4=ED=83=80=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/ohdodok/catchytape/feature/upload/UploadFragment.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadFragment.kt b/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadFragment.kt index 8654816..2045229 100644 --- a/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadFragment.kt +++ b/android/feature/upload/src/main/java/com/ohdodok/catchytape/feature/upload/UploadFragment.kt @@ -48,7 +48,7 @@ class UploadFragment : BaseFragment(R.layout.fragment_upl repeatOnStarted { viewModel.events.collect { event -> when (event) { - is UploadEventState.NavigateToBack -> { + is UploadEvent.NavigateToBack -> { findNavController().popBackStack() } } From 90d6271445090f596c351f96235ec77728631c69 Mon Sep 17 00:00:00 2001 From: youlalala Date: Thu, 23 Nov 2023 23:09:00 +0900 Subject: [PATCH 161/165] =?UTF-8?q?feat=20:=20=EB=B2=84=ED=8A=BC=20color?= =?UTF-8?q?=20=EC=83=89=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- android/feature/upload/src/main/res/layout/fragment_upload.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/android/feature/upload/src/main/res/layout/fragment_upload.xml b/android/feature/upload/src/main/res/layout/fragment_upload.xml index 9c988c5..04ee2f0 100644 --- a/android/feature/upload/src/main/res/layout/fragment_upload.xml +++ b/android/feature/upload/src/main/res/layout/fragment_upload.xml @@ -91,6 +91,7 @@ android:backgroundTint="@color/light_gray" android:drawableEnd="@drawable/ic_upload" android:drawableTint="@color/black" + android:textColor="@color/black" android:hint="@string/upload_file" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" From e10c0ff11372b4fb78a7fd2602b4f989c0a60d89 Mon Sep 17 00:00:00 2001 From: hyungun Date: Fri, 24 Nov 2023 00:05:02 +0900 Subject: [PATCH 162/165] =?UTF-8?q?fix=20:=20jwt=20strategy=20=EC=98=A4?= =?UTF-8?q?=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * user_id 를 userId 로 받아서 undefined 를 반환하는 문제 발견 --- server/src/auth/jwt.strategy.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/src/auth/jwt.strategy.ts b/server/src/auth/jwt.strategy.ts index 59a758b..1dda317 100644 --- a/server/src/auth/jwt.strategy.ts +++ b/server/src/auth/jwt.strategy.ts @@ -22,13 +22,13 @@ export class JwtStrategy extends PassportStrategy(Strategy) { } async validate(payload): Promise { - const { userId } = payload; + const { user_Id } = payload; const user: User = await this.userRepository.findOne({ - where: { user_id: userId }, + where: { user_id: user_Id }, }); - if (!user) { + if (!user || !user_Id) { throw new CatchyException( 'NOT_EXIST_USER', HTTP_STATUS_CODE['WRONG_TOKEN'], From 947a740e6fba81fc29f616268264cd1a8dcd628f Mon Sep 17 00:00:00 2001 From: youlalala Date: Fri, 24 Nov 2023 00:36:04 +0900 Subject: [PATCH 163/165] =?UTF-8?q?feat=20:=20AuthInterceptor=20Retention?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ohdodok/catchytape/core/data/di/qualifier/AuthInterceptor.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/di/qualifier/AuthInterceptor.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/di/qualifier/AuthInterceptor.kt index 52fffef..94af918 100644 --- a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/di/qualifier/AuthInterceptor.kt +++ b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/di/qualifier/AuthInterceptor.kt @@ -3,4 +3,5 @@ package com.ohdodok.catchytape.core.data.di.qualifier import javax.inject.Qualifier @Qualifier +@Retention(AnnotationRetention.BINARY) annotation class AuthInterceptor From 4af85f33c82bf44909a148ab4c457274185e0728 Mon Sep 17 00:00:00 2001 From: 2taezeat Date: Fri, 24 Nov 2023 01:33:17 +0900 Subject: [PATCH 164/165] =?UTF-8?q?feat=20:=20=EC=B5=9C=EA=B7=BC=20?= =?UTF-8?q?=EC=9E=AC=EC=83=9D=ED=95=9C=20=EB=85=B8=EB=9E=98=20=ED=81=B4?= =?UTF-8?q?=EB=A6=AD=EC=8B=9C,=20Home=20->=20Player=20navigate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/ohdodok/catchytape/feature/home/HomeFragment.kt | 8 ++++++++ .../player/src/main/res/navigation/player_navigation.xml | 7 ++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/android/feature/home/src/main/java/com/ohdodok/catchytape/feature/home/HomeFragment.kt b/android/feature/home/src/main/java/com/ohdodok/catchytape/feature/home/HomeFragment.kt index c490d9b..6d8417e 100644 --- a/android/feature/home/src/main/java/com/ohdodok/catchytape/feature/home/HomeFragment.kt +++ b/android/feature/home/src/main/java/com/ohdodok/catchytape/feature/home/HomeFragment.kt @@ -28,5 +28,13 @@ class HomeFragment : BaseFragment(R.layout.fragment_home) { .build() findNavController().navigate(request) } + + binding.ivRecentlyPlayedSong.setOnClickListener { + val request = NavDeepLinkRequest.Builder + .fromUri("android-app://com.ohdodok.catchytape/player_fragment".toUri()) + .build() + + findNavController().navigate(request) + } } } \ No newline at end of file diff --git a/android/feature/player/src/main/res/navigation/player_navigation.xml b/android/feature/player/src/main/res/navigation/player_navigation.xml index 1633715..9a9af31 100644 --- a/android/feature/player/src/main/res/navigation/player_navigation.xml +++ b/android/feature/player/src/main/res/navigation/player_navigation.xml @@ -9,6 +9,11 @@ android:id="@+id/player_fragment" android:name="com.ohdodok.catchytape.feature.player.PlayerFragment" android:label="player" - tools:layout="@layout/fragment_player" /> + tools:layout="@layout/fragment_player"> + + + + + \ No newline at end of file From 2b3b6dadf1bd97166c20db249bc9bf899e1eaac5 Mon Sep 17 00:00:00 2001 From: youlalala Date: Fri, 24 Nov 2023 01:43:00 +0900 Subject: [PATCH 165/165] =?UTF-8?q?feat=20:=20token=20=EB=AA=BB=EA=B0=80?= =?UTF-8?q?=EC=A0=B8=20=EC=98=A4=EB=8A=94=20=EC=9D=B4=EC=8A=88=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/ohdodok/catchytape/core/data/di/NetworkModule.kt | 5 ++--- .../catchytape/core/data/di/qualifier/AuthInterceptor.kt | 3 +-- .../com/ohdodok/catchytape/feature/home/HomeFragment.kt | 1 + .../com/ohdodok/catchytape/feature/home/HomeViewModel.kt | 6 +----- 4 files changed, 5 insertions(+), 10 deletions(-) diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/di/NetworkModule.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/di/NetworkModule.kt index 98adbca..d59c83d 100644 --- a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/di/NetworkModule.kt +++ b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/di/NetworkModule.kt @@ -27,9 +27,8 @@ object NetworkModule { @Provides fun provideAuthInterceptor(tokenDataSource: TokenLocalDataSource): Interceptor { - val accessToken = runBlocking { tokenDataSource.getAccessToken() } - return Interceptor { chain -> + val accessToken = runBlocking { tokenDataSource.getAccessToken() } val newRequest = chain.request().newBuilder() .addHeader("Authorization", "Bearer $accessToken") .build() @@ -53,8 +52,8 @@ object NetworkModule { @AuthInterceptor authInterceptor: Interceptor, ): OkHttpClient { return OkHttpClient.Builder() - .addInterceptor(loggingInterceptor) .addInterceptor(authInterceptor) + .addInterceptor(loggingInterceptor) .build() } diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/di/qualifier/AuthInterceptor.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/di/qualifier/AuthInterceptor.kt index 94af918..044f6d7 100644 --- a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/di/qualifier/AuthInterceptor.kt +++ b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/di/qualifier/AuthInterceptor.kt @@ -3,5 +3,4 @@ package com.ohdodok.catchytape.core.data.di.qualifier import javax.inject.Qualifier @Qualifier -@Retention(AnnotationRetention.BINARY) -annotation class AuthInterceptor +annotation class AuthInterceptor \ No newline at end of file diff --git a/android/feature/home/src/main/java/com/ohdodok/catchytape/feature/home/HomeFragment.kt b/android/feature/home/src/main/java/com/ohdodok/catchytape/feature/home/HomeFragment.kt index c490d9b..d1fbebe 100644 --- a/android/feature/home/src/main/java/com/ohdodok/catchytape/feature/home/HomeFragment.kt +++ b/android/feature/home/src/main/java/com/ohdodok/catchytape/feature/home/HomeFragment.kt @@ -22,6 +22,7 @@ class HomeFragment : BaseFragment(R.layout.fragment_home) { binding.viewModel = viewModel binding.rvRecentlyAddedSong.adapter = MusicAdapter(musicItemOrientation = Orientation.Horizontal) + viewModel.fetchUploadedMusics() binding.ibUpload.setOnClickListener { val request = NavDeepLinkRequest.Builder .fromUri("android-app://com.ohdodok.catchytape/upload_fragment".toUri()) diff --git a/android/feature/home/src/main/java/com/ohdodok/catchytape/feature/home/HomeViewModel.kt b/android/feature/home/src/main/java/com/ohdodok/catchytape/feature/home/HomeViewModel.kt index 14f2adf..dbe14b8 100644 --- a/android/feature/home/src/main/java/com/ohdodok/catchytape/feature/home/HomeViewModel.kt +++ b/android/feature/home/src/main/java/com/ohdodok/catchytape/feature/home/HomeViewModel.kt @@ -25,11 +25,7 @@ class HomeViewModel @Inject constructor( private val _uiState = MutableStateFlow(HomeUiState()) val uiState: StateFlow = _uiState.asStateFlow() - init { - fetchUploadedMusics() - } - - private fun fetchUploadedMusics() { + fun fetchUploadedMusics() { getRecentUploadedMusicUseCase() .onEach { musics -> _uiState.update {