Skip to content

Commit 97ed93c

Browse files
authored
feat: Migrate Android app to generated Kotlin SDK (#5)
Replace 52 manual data classes and 74 hand-written Retrofit endpoints with the generated Kotlin SDK from the OpenAPI spec. The SDK is included as a local Gradle module under sdk/. - Delete Models.kt (1051 lines) and ArtifactKeeperApi.kt (405 lines) - Add TypeAliases.kt bridging old model names to SDK types - Add LocalModels.kt for staging-specific types not in SDK - Add StagingApi.kt for staging endpoints not covered by SDK - Refactor ApiClient.kt to expose typed SDK API services - Update all 30+ screens and ViewModels to use SDK types - Update formatRelativeTime to handle OffsetDateTime from SDK Closes #4
1 parent 3537f42 commit 97ed93c

345 files changed

Lines changed: 21026 additions & 1790 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ local.properties
1212
# Release signing
1313
*.jks
1414
*.keystore
15+
app/build/

app/build.gradle.kts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,12 @@ dependencies {
6363
implementation("androidx.activity:activity-compose:1.9.3")
6464
implementation("androidx.navigation:navigation-compose:2.8.5")
6565

66+
// SDK
67+
implementation(project(":sdk"))
68+
6669
// Networking
6770
implementation("com.squareup.retrofit2:retrofit:2.11.0")
6871
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")
69-
implementation("com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0")
7072
implementation("com.squareup.okhttp3:okhttp:4.12.0")
7173
implementation("com.squareup.okhttp3:logging-interceptor:4.12.0")
7274

Lines changed: 95 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,77 @@
11
package com.artifactkeeper.android.data.api
22

33
import com.artifactkeeper.android.BuildConfig
4-
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
5-
import kotlinx.serialization.json.Json
6-
import okhttp3.MediaType.Companion.toMediaType
4+
import com.artifactkeeper.client.apis.AdminApi
5+
import com.artifactkeeper.client.apis.AnalyticsApi
6+
import com.artifactkeeper.client.apis.AuthApi
7+
import com.artifactkeeper.client.apis.BuildsApi
8+
import com.artifactkeeper.client.apis.GroupsApi
9+
import com.artifactkeeper.client.apis.HealthApi
10+
import com.artifactkeeper.client.apis.MonitoringApi
11+
import com.artifactkeeper.client.apis.PackagesApi
12+
import com.artifactkeeper.client.apis.PeersApi
13+
import com.artifactkeeper.client.apis.PromotionApi
14+
import com.artifactkeeper.client.apis.RepositoriesApi
15+
import com.artifactkeeper.client.apis.SbomApi
16+
import com.artifactkeeper.client.apis.SecurityApi
17+
import com.artifactkeeper.client.apis.SsoApi
18+
import com.artifactkeeper.client.apis.UsersApi
19+
import com.artifactkeeper.client.apis.WebhooksApi
20+
import com.artifactkeeper.client.infrastructure.ApiClient as SdkApiClient
721
import okhttp3.OkHttpClient
822
import okhttp3.logging.HttpLoggingInterceptor
9-
import retrofit2.Retrofit
23+
import retrofit2.Response
1024
import java.security.SecureRandom
1125
import java.security.cert.X509Certificate
1226
import javax.net.ssl.SSLContext
1327
import javax.net.ssl.TrustManager
1428
import javax.net.ssl.X509TrustManager
1529

30+
/**
31+
* Unwrap a Retrofit [Response], returning the body on success
32+
* or throwing an exception with the error body on failure.
33+
*/
34+
suspend fun <T> Response<T>.unwrap(): T {
35+
if (isSuccessful) return body()!!
36+
throw Exception(errorBody()?.string() ?: "HTTP ${code()}")
37+
}
38+
1639
object ApiClient {
17-
private val json = Json {
18-
ignoreUnknownKeys = true
19-
encodeDefaults = true
20-
}
2140
private var _baseUrl = ""
2241
private var _token: String? = null
42+
private var sdkClient: SdkApiClient? = null
2343

2444
val isConfigured: Boolean get() = _baseUrl.isNotBlank()
45+
val baseUrl: String get() = _baseUrl
46+
val token: String? get() = _token
47+
48+
// --- Typed API services ---
49+
lateinit var authApi: AuthApi private set
50+
lateinit var reposApi: RepositoriesApi private set
51+
lateinit var packagesApi: PackagesApi private set
52+
lateinit var buildsApi: BuildsApi private set
53+
lateinit var securityApi: SecurityApi private set
54+
lateinit var adminApi: AdminApi private set
55+
lateinit var usersApi: UsersApi private set
56+
lateinit var groupsApi: GroupsApi private set
57+
lateinit var peersApi: PeersApi private set
58+
lateinit var webhooksApi: WebhooksApi private set
59+
lateinit var analyticsApi: AnalyticsApi private set
60+
lateinit var monitoringApi: MonitoringApi private set
61+
lateinit var healthApi: HealthApi private set
62+
lateinit var sbomApi: SbomApi private set
63+
lateinit var ssoApi: SsoApi private set
64+
lateinit var promotionApi: PromotionApi private set
65+
lateinit var stagingApi: StagingApi private set
2566

26-
private fun buildClient(): OkHttpClient {
67+
// Keep a raw OkHttpClient for cases that need direct HTTP access
68+
val httpClient: OkHttpClient get() = buildOkHttpClientBuilder().build()
69+
70+
private fun buildOkHttpClientBuilder(): OkHttpClient.Builder {
2771
val builder = OkHttpClient.Builder()
2872
.addInterceptor(HttpLoggingInterceptor().apply {
2973
level = HttpLoggingInterceptor.Level.BODY
3074
})
31-
.addInterceptor { chain ->
32-
val request = _token?.let {
33-
chain.request().newBuilder()
34-
.addHeader("Authorization", "Bearer $it")
35-
.build()
36-
} ?: chain.request()
37-
chain.proceed(request)
38-
}
3975

4076
if (BuildConfig.DEBUG) {
4177
val trustAllManager = object : X509TrustManager {
@@ -49,42 +85,66 @@ object ApiClient {
4985
builder.hostnameVerifier { _, _ -> true }
5086
}
5187

52-
return builder.build()
88+
return builder
5389
}
5490

55-
private fun buildRetrofit(): Retrofit {
91+
private fun rebuildServices() {
5692
val url = _baseUrl.ifBlank { "http://localhost/" }
57-
return Retrofit.Builder()
58-
.baseUrl(url)
59-
.client(buildClient())
60-
.addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
61-
.build()
62-
}
63-
64-
var api: ArtifactKeeperApi = buildRetrofit().create(ArtifactKeeperApi::class.java)
65-
private set
93+
val okBuilder = buildOkHttpClientBuilder()
6694

67-
val httpClient: OkHttpClient get() = buildClient()
95+
val client = if (_token != null) {
96+
SdkApiClient(
97+
baseUrl = url,
98+
okHttpClientBuilder = okBuilder,
99+
authName = "bearer_auth",
100+
bearerToken = _token!!
101+
)
102+
} else {
103+
SdkApiClient(
104+
baseUrl = url,
105+
okHttpClientBuilder = okBuilder,
106+
authNames = arrayOf("bearer_auth")
107+
)
108+
}
109+
sdkClient = client
68110

69-
val baseUrl: String get() = _baseUrl
111+
authApi = client.createService(AuthApi::class.java)
112+
reposApi = client.createService(RepositoriesApi::class.java)
113+
packagesApi = client.createService(PackagesApi::class.java)
114+
buildsApi = client.createService(BuildsApi::class.java)
115+
securityApi = client.createService(SecurityApi::class.java)
116+
adminApi = client.createService(AdminApi::class.java)
117+
usersApi = client.createService(UsersApi::class.java)
118+
groupsApi = client.createService(GroupsApi::class.java)
119+
peersApi = client.createService(PeersApi::class.java)
120+
webhooksApi = client.createService(WebhooksApi::class.java)
121+
analyticsApi = client.createService(AnalyticsApi::class.java)
122+
monitoringApi = client.createService(MonitoringApi::class.java)
123+
healthApi = client.createService(HealthApi::class.java)
124+
sbomApi = client.createService(SbomApi::class.java)
125+
ssoApi = client.createService(SsoApi::class.java)
126+
promotionApi = client.createService(PromotionApi::class.java)
127+
stagingApi = client.createService(StagingApi::class.java)
128+
}
70129

71-
val token: String? get() = _token
130+
init {
131+
rebuildServices()
132+
}
72133

73134
fun configure(baseUrl: String, token: String? = null) {
74135
_baseUrl = if (baseUrl.endsWith("/")) baseUrl else "$baseUrl/"
75136
_token = token
76-
api = buildRetrofit().create(ArtifactKeeperApi::class.java)
137+
rebuildServices()
77138
}
78139

79140
fun clearConfig() {
80141
_baseUrl = ""
81142
_token = null
82-
api = buildRetrofit().create(ArtifactKeeperApi::class.java)
143+
rebuildServices()
83144
}
84145

85146
fun setToken(token: String?) {
86147
_token = token
87-
api = buildRetrofit().create(ArtifactKeeperApi::class.java)
148+
rebuildServices()
88149
}
89-
90150
}

0 commit comments

Comments
 (0)