1+ package io.klibs.app.indexing
2+
3+ import io.klibs.app.util.toIndexRequest
4+ import io.klibs.core.pckg.repository.IndexingRequestRepository
5+ import io.klibs.core.pckg.repository.PackageRepository
6+ import io.klibs.integration.maven.MavenArtifact
7+ import io.klibs.integration.maven.ScraperType
8+ import io.klibs.integration.maven.search.ArtifactData
9+ import io.klibs.integration.maven.search.impl.CentralSonatypeSearchClient
10+ import io.klibs.integration.maven.search.paginateSearch
11+ import org.apache.maven.search.api.request.BooleanQuery
12+ import org.apache.maven.search.api.request.Query
13+ import org.slf4j.LoggerFactory
14+ import org.springframework.http.HttpStatus
15+ import org.springframework.stereotype.Service
16+ import org.springframework.transaction.annotation.Transactional
17+ import org.springframework.web.server.ResponseStatusException
18+
19+ @Service
20+ class RequestIndexingService (
21+ private val centralSonatypeSearchClient : CentralSonatypeSearchClient ,
22+ private val indexingRequestRepository : IndexingRequestRepository ,
23+ private val packageRepository : PackageRepository ,
24+ ) {
25+ /* *
26+ * Discovers and saves packages for indexing after a manual request
27+ *
28+ * @param groupId Maven group ID (required)
29+ * @param artifactId Maven artifact ID (optional - if null, all artifacts in the group are indexed)
30+ * @param version Maven version (optional - if null, all versions of the artifact(s) are indexed)
31+ */
32+ @Transactional
33+ fun requestIndexing (groupId : String , artifactId : String? , version : String? ) {
34+ val artifacts = discoverArtifacts(groupId, artifactId, version)
35+ saveIndexRequests(artifacts)
36+ }
37+
38+ private fun discoverArtifacts (
39+ groupId : String ,
40+ artifactId : String? ,
41+ version : String? ,
42+ ): List <MavenArtifact > {
43+ if (artifactId != null && version != null ) {
44+ return listOf (resolveSpecificVersion(groupId, artifactId, version))
45+ }
46+
47+ if (version != null ) {
48+ logger.warn(" Version is specified but artifactId is not. Ignoring version." )
49+ }
50+
51+ val query = buildKmpQuery(groupId, artifactId)
52+ val searchResult = paginateSearch(query)
53+
54+ if (searchResult.isEmpty()) {
55+ throw badRequest(
56+ " No Kotlin Multiplatform artifacts found for $groupId${
57+ artifactId?.let { " :$it " }.orEmpty()
58+ } "
59+ )
60+ }
61+
62+ val artifactsToSave = searchResult
63+ .map { it.toMavenArtifact() }
64+ .filterNot { isAlreadyIndexedOrQueued(it) }
65+
66+ if (artifactsToSave.isEmpty()) throw badRequest(" All artifacts from this request are already indexed or queued" )
67+
68+ return artifactsToSave
69+ }
70+
71+ private fun resolveSpecificVersion (groupId : String , artifactId : String , version : String ): MavenArtifact {
72+ val artifact = MavenArtifact (groupId, artifactId, version, ScraperType .MANUAL_REQUEST )
73+ if (isAlreadyIndexedOrQueued(artifact)) throw badRequest(" Artifact $groupId :$artifactId :$version is already indexed or queued" )
74+
75+ centralSonatypeSearchClient.getKotlinToolingMetadata(artifact)
76+ ? : throw badRequest(
77+ " Artifact $groupId :$artifactId :$version is not a valid Kotlin Multiplatform library " +
78+ " (kotlin-tooling-metadata.json not found)"
79+ )
80+
81+ return artifact
82+ }
83+
84+ private fun isAlreadyIndexedOrQueued (artifact : MavenArtifact ): Boolean =
85+ with (artifact) {
86+ when {
87+ packageRepository.findByGroupIdAndArtifactIdAndVersion(groupId, artifactId, version) != null -> {
88+ logger.debug(" Already indexed: $groupId :$artifactId :$version , skipping" )
89+ true
90+ }
91+
92+ indexingRequestRepository.findByGroupIdAndArtifactIdAndVersion(groupId, artifactId, version) != null -> {
93+ logger.debug(" Already queued: $groupId :$artifactId :$version , skipping" )
94+ true
95+ }
96+
97+ else -> false
98+ }
99+ }
100+
101+ private fun buildKmpQuery (groupId : String , artifactId : String? ): Query {
102+ var query = BooleanQuery .and (
103+ Query .query(" g:$groupId " ),
104+ Query .query(" l:kotlin-tooling-metadata" )
105+ )
106+ if (artifactId != null ) {
107+ query = BooleanQuery .and (query, Query .query(" a:$artifactId " ))
108+ }
109+ return query
110+ }
111+
112+ private fun paginateSearch (query : Query ): List <ArtifactData > =
113+ try {
114+ centralSonatypeSearchClient.paginateSearch(query).toList()
115+ } catch (e: Exception ) {
116+ logger.error(" Central Sonatype search failed: ${e.message} " , e)
117+ throw ResponseStatusException (
118+ HttpStatus .SERVICE_UNAVAILABLE ,
119+ " Central Sonatype search failed"
120+ )
121+ }
122+
123+ private fun saveIndexRequests (mavenArtifacts : List <MavenArtifact >) {
124+ val requests = mavenArtifacts.map { it.toIndexRequest() }
125+
126+ try {
127+ indexingRequestRepository.saveAll(requests)
128+ logger.info(" Saved ${requests.size} index requests" )
129+ } catch (e: Exception ) {
130+ logger.error(" Failed to save index requests: ${e.message} " )
131+
132+ throw ResponseStatusException (
133+ HttpStatus .INTERNAL_SERVER_ERROR ,
134+ " Failed to save index requests"
135+ )
136+ }
137+ }
138+
139+ private fun ArtifactData.toMavenArtifact () = MavenArtifact (
140+ groupId = groupId,
141+ artifactId = artifactId,
142+ version = version,
143+ scraperType = ScraperType .MANUAL_REQUEST ,
144+ releasedAt = releasedAt,
145+ )
146+
147+ private fun badRequest (message : String ): ResponseStatusException =
148+ ResponseStatusException (HttpStatus .BAD_REQUEST , message)
149+
150+ companion object {
151+ private val logger = LoggerFactory .getLogger(RequestIndexingService ::class .java)
152+ }
153+ }
0 commit comments