Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/android_ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ jobs:
- name: Set up local.properties
run: echo "${{ secrets.LOCAL_PROPERTIES }}" > local.properties

- name: Set up google-services.json
run: echo '${{ secrets.GOOGLE_SERVICES }}' | base64 -d > app/google-services.json

- name: Code style checks
run: ./gradlew ktlintCheck detekt

Expand Down
6 changes: 6 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
plugins {
id("ndgl.application")
alias(libs.plugins.google.services)
alias(libs.plugins.firebase.crashlytics)
}

android {
namespace = Configuration.APPLICATION_ID

buildFeatures {
buildConfig = true
}

buildTypes {
release {
isMinifyEnabled = true
Expand Down
11 changes: 10 additions & 1 deletion app/src/main/java/com/yapp/ndgl/NDGLApplication.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@ package com.yapp.ndgl

import android.app.Application
import dagger.hilt.android.HiltAndroidApp
import timber.log.Timber

@HiltAndroidApp
class NDGLApplication : Application()
class NDGLApplication : Application() {
override fun onCreate() {
super.onCreate()

if (BuildConfig.DEBUG) {
Timber.plant(Timber.DebugTree())
}
}
}
2 changes: 2 additions & 0 deletions build-logic/src/main/kotlin/NDGLAndroidLibraryPlugin.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import convention.configureComposeAndroid
import convention.configureFirebase
import convention.configureKotlinAndroid
import convention.configureTimber
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.dependencies
Expand All @@ -16,6 +17,7 @@ class NDGLAndroidLibraryPlugin : Plugin<Project> {
configureKotlinAndroid()
configureFirebase()
configureComposeAndroid()
configureTimber()
Comment thread
jihee-dev marked this conversation as resolved.

dependencies {
"implementation"(libs.findLibrary("kotlinx-immutable").get())
Expand Down
16 changes: 16 additions & 0 deletions build-logic/src/main/kotlin/NDGLDataPlugin.kt
Original file line number Diff line number Diff line change
@@ -1,17 +1,33 @@
import convention.configureCoroutineAndroid
import convention.configureHiltAndroid
import convention.configureKotlinAndroid
import convention.configureTimber
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.dependencies
import util.libs

class NDGLDataPlugin : Plugin<Project> {
override fun apply(target: Project): Unit = with(target) {
with(pluginManager) {
apply("com.android.library")
apply("org.jetbrains.kotlin.plugin.serialization")
}

configureKotlinAndroid()
configureHiltAndroid()
configureCoroutineAndroid()
configureTimber()

if (path != ":data:core") {
dependencies.add("implementation", project(":data:core"))
}

dependencies {
"implementation"(libs.findLibrary("retrofit").get())
"implementation"(libs.findLibrary("retrofit-kotlinx-serialization-json").get())
"implementation"(libs.findLibrary("kotlinx-serialization-json").get())
"implementation"(libs.findLibrary("okhttp").get())
}
}
}
2 changes: 0 additions & 2 deletions build-logic/src/main/kotlin/NDGLFeaturePlugin.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import convention.configureComposeAndroid
import convention.configureCoroutineAndroid
import convention.configureFirebase
import convention.configureHiltAndroid
import org.gradle.api.Plugin
import org.gradle.api.Project
Expand Down
13 changes: 13 additions & 0 deletions core/util/src/main/java/com/yapp/ndgl/core/util/ResultUtil.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.yapp.ndgl.core.util

import kotlin.coroutines.cancellation.CancellationException

suspend inline fun <T, R> T.suspendRunCatching(crossinline block: suspend T.() -> R): Result<R> {
return try {
Result.success(block())
} catch (e: CancellationException) {
throw e
} catch (t: Throwable) {
Result.failure(t)
}
}
2 changes: 1 addition & 1 deletion data/auth/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ android {
}

dependencies {
implementation(project(":data:core"))
implementation(libs.androidx.datastore)
}
20 changes: 20 additions & 0 deletions data/auth/src/main/java/com/yapp/ndgl/data/auth/api/AuthApi.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.yapp.ndgl.data.auth.api

import com.yapp.ndgl.data.auth.model.AuthResponse
import com.yapp.ndgl.data.auth.model.CreateUserRequest
import com.yapp.ndgl.data.auth.model.LoginRequest
import com.yapp.ndgl.data.core.model.BaseResponse
import retrofit2.http.Body
import retrofit2.http.POST

interface AuthApi {
@POST("/api/v1/auth/users")
suspend fun createUser(
@Body request: CreateUserRequest,
): BaseResponse<AuthResponse>

@POST("/api/v1/auth/login")
suspend fun login(
@Body request: LoginRequest,
): BaseResponse<AuthResponse>
}
19 changes: 19 additions & 0 deletions data/auth/src/main/java/com/yapp/ndgl/data/auth/di/AuthModule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.yapp.ndgl.data.auth.di

import com.yapp.ndgl.data.auth.token.TokenManagerImpl
import com.yapp.ndgl.data.core.token.TokenManager
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
abstract class AuthModule {
@Binds
@Singleton
abstract fun bindTokenManager(
impl: TokenManagerImpl,
): TokenManager
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.yapp.ndgl.data.auth.di

import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
import com.yapp.ndgl.data.auth.api.AuthApi
import com.yapp.ndgl.data.core.adapter.NDGLCallAdapterFactory
import com.yapp.ndgl.data.core.di.AuthClient
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import kotlinx.serialization.json.Json
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
object AuthNetworkModule {
@Provides
@Singleton
fun provideAuthApi(
json: Json,
baseUrl: String,
@AuthClient okHttpClient: OkHttpClient,
callAdapterFactory: NDGLCallAdapterFactory,
): AuthApi = Retrofit.Builder()
.baseUrl(baseUrl)
.client(okHttpClient)
.addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
.addCallAdapterFactory(callAdapterFactory)
.build()
.create(AuthApi::class.java)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.yapp.ndgl.data.auth.local

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.yapp.ndgl.data.auth.local.util.handleException
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class LocalAuthDataSource @Inject constructor(
private val dataStore: DataStore<Preferences>,
) {
private val accessToken: Flow<String> = dataStore.data
.handleException()
.map { preferences ->
preferences[ACCESS_TOKEN_KEY] ?: ""
}

private val uuid: Flow<String> = dataStore.data
.handleException()
.map { preferences ->
preferences[UUID_KEY] ?: ""
}

suspend fun getAccessToken(): String = accessToken.first()

suspend fun getUuid(): String = uuid.first()

suspend fun setAccessToken(token: String) {
dataStore.edit { preferences ->
preferences[ACCESS_TOKEN_KEY] = token
}
}

suspend fun setUuid(uuid: String) {
dataStore.edit { preferences ->
preferences[UUID_KEY] = uuid
}
Comment thread
mj010504 marked this conversation as resolved.
}

suspend fun clearSession() {
dataStore.edit { preferences ->
preferences.remove(ACCESS_TOKEN_KEY)
preferences.remove(UUID_KEY)
}
}

private companion object {
private val ACCESS_TOKEN_KEY = stringPreferencesKey("access_token")
private val UUID_KEY = stringPreferencesKey("uuid")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.yapp.ndgl.data.auth.local.di

import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.preferencesDataStore
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
object DataStoreModule {

private const val TOKEN_PREFERENCES = "token_preferences"
private val Context.tokenDataStore: DataStore<Preferences> by preferencesDataStore(name = TOKEN_PREFERENCES)

@Provides
@Singleton
fun provideTokenDataStore(
@ApplicationContext context: Context,
): DataStore<Preferences> {
return context.tokenDataStore
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.yapp.ndgl.data.auth.local.util

import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.emptyPreferences
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import java.io.IOException

internal fun Flow<Preferences>.handleException(): Flow<Preferences> =
this.catch { exception ->
if (exception is IOException) {
emit(emptyPreferences())
} else {
throw exception
}
}
Comment thread
jihee-dev marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.yapp.ndgl.data.auth.model

import kotlinx.serialization.Serializable

@Serializable
data class AuthResponse(
val uuid: String,
val accessToken: String,
val nickname: String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.yapp.ndgl.data.auth.model

import kotlinx.serialization.Serializable

@Serializable
data class CreateUserRequest(
val fcmToken: String,
val deviceModel: String,
val deviceOs: String,
val deviceOsVersion: String,
val appVersion: String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.yapp.ndgl.data.auth.model

import kotlinx.serialization.Serializable

@Serializable
data class LoginRequest(
val uuid: String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.yapp.ndgl.data.auth.token

import com.yapp.ndgl.data.auth.api.AuthApi
import com.yapp.ndgl.data.auth.local.LocalAuthDataSource
import com.yapp.ndgl.data.auth.model.LoginRequest
import com.yapp.ndgl.data.core.model.getData
import com.yapp.ndgl.data.core.token.TokenManager
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class TokenManagerImpl @Inject constructor(
private val localAuthDataSource: LocalAuthDataSource,
private val authApi: AuthApi,
) : TokenManager {

override suspend fun getAccessToken(): String {
return localAuthDataSource.getAccessToken()
}

override suspend fun getUuid(): String {
return localAuthDataSource.getUuid()
}

override suspend fun setAccessToken(accessToken: String) {
localAuthDataSource.setAccessToken(accessToken)
}

override suspend fun setUuid(uuid: String) {
localAuthDataSource.setUuid(uuid)
}

override suspend fun refreshToken(): String {
val uuid = getUuid()
check(uuid.isNotEmpty()) { "UUID is empty" }

val response = authApi.login(LoginRequest(uuid)).getData()
setAccessToken(response.accessToken)
setUuid(response.uuid)

return response.accessToken
}
Comment thread
mj010504 marked this conversation as resolved.
}
Loading