diff --git a/app/src/debug/AndroidManifest.xml b/app/src/debug/AndroidManifest.xml
new file mode 100644
index 0000000..cbc4f57
--- /dev/null
+++ b/app/src/debug/AndroidManifest.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/data/.gitignore b/data/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/data/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/data/build.gradle.kts b/data/build.gradle.kts
new file mode 100644
index 0000000..e3b4d86
--- /dev/null
+++ b/data/build.gradle.kts
@@ -0,0 +1,91 @@
+import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties
+
+plugins {
+ alias(libs.plugins.android.library)
+ alias(libs.plugins.kotlin.android)
+ alias(libs.plugins.google.devtools.ksp)
+ alias(libs.plugins.hilt.android)
+}
+
+android {
+ namespace = "eu.project.data"
+ compileSdk {
+ version = release(36)
+ }
+
+ defaultConfig {
+ minSdk = 26
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles("consumer-rules.pro")
+ }
+
+ buildTypes {
+ debug {
+ isMinifyEnabled = false
+
+ val apiBaseUrlDebug = gradleLocalProperties(rootDir, providers)
+ .getProperty("API_BASE_URL_DEBUG") ?: "\"http://example.com\""
+
+ buildConfigField("String", "API_BASE_URL", apiBaseUrlDebug)
+ }
+ release {
+ isMinifyEnabled = false
+
+ val apiBaseUrlRelease = gradleLocalProperties(rootDir, providers)
+ .getProperty("API_BASE_URL_RELEASE") ?: "\"https://example.com\""
+
+ buildConfigField("String", "API_BASE_URL", apiBaseUrlRelease)
+
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro"
+ )
+ }
+ }
+ buildFeatures {
+ buildConfig = true
+ }
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_11
+ targetCompatibility = JavaVersion.VERSION_11
+ }
+ kotlinOptions {
+ jvmTarget = "11"
+ }
+}
+
+dependencies {
+ implementation(libs.androidx.core.ktx)
+ implementation(libs.androidx.appcompat)
+ implementation(libs.material)
+ testImplementation(libs.junit)
+ androidTestImplementation(libs.androidx.junit)
+ androidTestImplementation(libs.androidx.espresso.core)
+
+ // Hilt
+ implementation(libs.hilt.android)
+ ksp(libs.hilt.android.compiler)
+
+ // Retrofit
+ implementation(libs.retrofit)
+
+ // OkHttp
+ implementation(libs.okhttp)
+ implementation(libs.logging.interceptor)
+ testImplementation(libs.mockwebserver)
+
+ // Gson converter
+ implementation(libs.converter.gson)
+
+ // MockK
+ testImplementation(libs.mockk)
+ testImplementation(libs.mockk.android)
+ testImplementation(libs.mockk.agent)
+
+ // Coroutines test
+ testImplementation(libs.kotlinx.coroutines.test)
+
+ implementation(project(":common"))
+ implementation(project(":auth"))
+}
\ No newline at end of file
diff --git a/data/consumer-rules.pro b/data/consumer-rules.pro
new file mode 100644
index 0000000..e69de29
diff --git a/data/proguard-rules.pro b/data/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/data/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/data/src/main/AndroidManifest.xml b/data/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..568741e
--- /dev/null
+++ b/data/src/main/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/data/src/main/java/eu/project/data/remote/client/ApplicationOkHttpClient.kt b/data/src/main/java/eu/project/data/remote/client/ApplicationOkHttpClient.kt
new file mode 100644
index 0000000..0a192f2
--- /dev/null
+++ b/data/src/main/java/eu/project/data/remote/client/ApplicationOkHttpClient.kt
@@ -0,0 +1,23 @@
+package eu.project.data.remote.client
+
+import eu.project.data.remote.interceptor.AuthzInterceptor
+import eu.project.data.remote.interceptor.LoggingInterceptor
+import okhttp3.OkHttpClient
+import java.util.concurrent.TimeUnit
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+internal class ApplicationOkHttpClient @Inject constructor(
+ authzInterceptor: AuthzInterceptor,
+ loggingInterceptor: LoggingInterceptor
+) {
+
+ val client = OkHttpClient.Builder()
+ .addInterceptor(authzInterceptor)
+ .addInterceptor(loggingInterceptor)
+ .connectTimeout(30, TimeUnit.SECONDS)
+ .readTimeout(30, TimeUnit.SECONDS)
+ .retryOnConnectionFailure(true)
+ .build()
+}
\ No newline at end of file
diff --git a/data/src/main/java/eu/project/data/remote/client/WebApplicationClient.kt b/data/src/main/java/eu/project/data/remote/client/WebApplicationClient.kt
new file mode 100644
index 0000000..5a2cd22
--- /dev/null
+++ b/data/src/main/java/eu/project/data/remote/client/WebApplicationClient.kt
@@ -0,0 +1,7 @@
+package eu.project.data.remote.client
+
+import eu.project.data.remote.endpoint.TestEndpoint
+
+internal interface WebApplicationClient {
+ val testEndpoint: TestEndpoint
+}
\ No newline at end of file
diff --git a/data/src/main/java/eu/project/data/remote/client/WebApplicationClientImplD.kt b/data/src/main/java/eu/project/data/remote/client/WebApplicationClientImplD.kt
new file mode 100644
index 0000000..96afb6c
--- /dev/null
+++ b/data/src/main/java/eu/project/data/remote/client/WebApplicationClientImplD.kt
@@ -0,0 +1,24 @@
+package eu.project.data.remote.client
+
+import eu.project.data.BuildConfig
+import eu.project.data.remote.endpoint.TestEndpoint
+import retrofit2.Retrofit
+import retrofit2.converter.gson.GsonConverterFactory
+import retrofit2.create
+import javax.inject.Inject
+
+internal class WebApplicationClientImplD @Inject constructor(
+ okHttpClient: ApplicationOkHttpClient,
+): WebApplicationClient {
+
+ private val apiBaseUrl = BuildConfig.API_BASE_URL
+
+ private val retrofit = Retrofit.Builder()
+ .baseUrl(apiBaseUrl)
+ .client(okHttpClient.client)
+ .addConverterFactory(GsonConverterFactory.create())
+ .build()
+
+ override val testEndpoint: TestEndpoint
+ get() = retrofit.create()
+}
\ No newline at end of file
diff --git a/data/src/main/java/eu/project/data/remote/di/ClientModule.kt b/data/src/main/java/eu/project/data/remote/di/ClientModule.kt
new file mode 100644
index 0000000..e60c89d
--- /dev/null
+++ b/data/src/main/java/eu/project/data/remote/di/ClientModule.kt
@@ -0,0 +1,45 @@
+package eu.project.data.remote.di
+
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import eu.project.auth.authz.AuthzManager
+import eu.project.data.remote.client.ApplicationOkHttpClient
+import eu.project.data.remote.client.WebApplicationClient
+import eu.project.data.remote.client.WebApplicationClientImplD
+import eu.project.data.remote.interceptor.AuthzInterceptor
+import eu.project.data.remote.interceptor.LoggingInterceptor
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+internal class ClientModule {
+
+ @Provides
+ @Singleton
+ fun provideAuthzInterceptor(
+ authzManager: AuthzManager
+ ): AuthzInterceptor =
+ AuthzInterceptor(authzManager)
+
+ @Provides
+ @Singleton
+ fun provideApplicationOkHttpClient(
+ authzInterceptor: AuthzInterceptor,
+ loggingInterceptor: LoggingInterceptor
+ ): ApplicationOkHttpClient =
+ ApplicationOkHttpClient(
+ authzInterceptor = authzInterceptor,
+ loggingInterceptor = loggingInterceptor
+ )
+
+ @Provides
+ @Singleton
+ fun provideWebApplicationClient(
+ applicationOkHttpClient: ApplicationOkHttpClient
+ ): WebApplicationClient =
+ WebApplicationClientImplD(
+ okHttpClient = applicationOkHttpClient
+ )
+}
\ No newline at end of file
diff --git a/data/src/main/java/eu/project/data/remote/di/RepositoryModule.kt b/data/src/main/java/eu/project/data/remote/di/RepositoryModule.kt
new file mode 100644
index 0000000..e72e5b6
--- /dev/null
+++ b/data/src/main/java/eu/project/data/remote/di/RepositoryModule.kt
@@ -0,0 +1,22 @@
+package eu.project.data.remote.di
+
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import eu.project.data.remote.client.WebApplicationClient
+import eu.project.data.remote.repository.TestRepository
+import eu.project.data.remote.repository.TestRepositoryImpl
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+internal class RepositoryModule {
+
+ @Provides
+ @Singleton
+ fun provideTestRepository (
+ webApplicationClient: WebApplicationClient
+ ): TestRepository =
+ TestRepositoryImpl(webApplicationClient.testEndpoint)
+}
\ No newline at end of file
diff --git a/data/src/main/java/eu/project/data/remote/endpoint/TestEndpoint.kt b/data/src/main/java/eu/project/data/remote/endpoint/TestEndpoint.kt
new file mode 100644
index 0000000..9d2aaae
--- /dev/null
+++ b/data/src/main/java/eu/project/data/remote/endpoint/TestEndpoint.kt
@@ -0,0 +1,10 @@
+package eu.project.data.remote.endpoint
+
+import retrofit2.Response
+import retrofit2.http.GET
+
+internal interface TestEndpoint {
+
+ @GET("/")
+ suspend fun test(): Response
+}
\ No newline at end of file
diff --git a/data/src/main/java/eu/project/data/remote/interceptor/AuthzInterceptor.kt b/data/src/main/java/eu/project/data/remote/interceptor/AuthzInterceptor.kt
new file mode 100644
index 0000000..4e6be46
--- /dev/null
+++ b/data/src/main/java/eu/project/data/remote/interceptor/AuthzInterceptor.kt
@@ -0,0 +1,28 @@
+package eu.project.data.remote.interceptor
+
+import eu.project.auth.authz.AuthzManager
+import okhttp3.Interceptor
+import okhttp3.Response
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+internal class AuthzInterceptor @Inject constructor(
+ private val authzManager: AuthzManager
+): Interceptor {
+
+ override fun intercept(chain: Interceptor.Chain): Response {
+
+ // try to get JWT access token to be passed into the 'Authorization' header
+ val rawAccessToken =
+ authzManager.getAccessToken()?.value ?:
+ return chain.proceed(chain.request()) // no token available, proceed without authorization
+
+ val originalRequest = chain.request()
+ val modifiedRequest = originalRequest.newBuilder()
+ .addHeader("Authorization", "Bearer $rawAccessToken")
+ .build()
+
+ return chain.proceed(modifiedRequest)
+ }
+}
\ No newline at end of file
diff --git a/data/src/main/java/eu/project/data/remote/interceptor/LoggingInterceptor.kt b/data/src/main/java/eu/project/data/remote/interceptor/LoggingInterceptor.kt
new file mode 100644
index 0000000..9bba066
--- /dev/null
+++ b/data/src/main/java/eu/project/data/remote/interceptor/LoggingInterceptor.kt
@@ -0,0 +1,26 @@
+package eu.project.data.remote.interceptor
+
+import eu.project.data.BuildConfig
+import okhttp3.Interceptor
+import okhttp3.Response
+import okhttp3.logging.HttpLoggingInterceptor
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+internal class LoggingInterceptor @Inject constructor() : Interceptor {
+
+ private val logger = HttpLoggingInterceptor().apply {
+ level =
+ if (BuildConfig.DEBUG) {
+ HttpLoggingInterceptor.Level.BODY
+ }
+ else {
+ HttpLoggingInterceptor.Level.NONE
+ }
+ }
+
+ override fun intercept(chain: Interceptor.Chain): Response {
+ return logger.intercept(chain)
+ }
+}
\ No newline at end of file
diff --git a/data/src/main/java/eu/project/data/remote/repository/TestRepository.kt b/data/src/main/java/eu/project/data/remote/repository/TestRepository.kt
new file mode 100644
index 0000000..ea7d3e6
--- /dev/null
+++ b/data/src/main/java/eu/project/data/remote/repository/TestRepository.kt
@@ -0,0 +1,5 @@
+package eu.project.data.remote.repository
+
+interface TestRepository {
+ suspend fun test(): Result
+}
\ No newline at end of file
diff --git a/data/src/main/java/eu/project/data/remote/repository/TestRepositoryImpl.kt b/data/src/main/java/eu/project/data/remote/repository/TestRepositoryImpl.kt
new file mode 100644
index 0000000..a5384e2
--- /dev/null
+++ b/data/src/main/java/eu/project/data/remote/repository/TestRepositoryImpl.kt
@@ -0,0 +1,22 @@
+package eu.project.data.remote.repository
+
+import eu.project.data.remote.endpoint.TestEndpoint
+import retrofit2.Response
+import javax.inject.Inject
+
+internal class TestRepositoryImpl @Inject constructor(
+ private val testEndpoint: TestEndpoint
+): TestRepository {
+
+ override suspend fun test(): Result {
+ return runCatching {
+ val response: Response = testEndpoint.test()
+ if (response.isSuccessful) {
+ response.body() ?: error("Empty response body")
+ }
+ else {
+ throw IllegalStateException("HTTP ${response.code()}: ${response.errorBody()?.string()}")
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 10ea37c..ad86f8c 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -32,3 +32,4 @@ include(":localData")
include(":remoteData")
include(":auth")
include(":feature:authenticate")
+include(":data")