Updated on 2026-08-14
This commit is contained in:
parent
9105ffbd75
commit
dcb8b474cf
20 changed files with 550 additions and 8 deletions
|
|
@ -54,5 +54,9 @@
|
|||
{
|
||||
"name": "ACCOUNTS_FEATURE_ENABLED",
|
||||
"version": "undefined"
|
||||
},
|
||||
{
|
||||
"name": "FEED_ENABLED",
|
||||
"version": "undefined"
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ sealed class ApiConfig {
|
|||
BlockAid,
|
||||
YieldSupply,
|
||||
MoonPay,
|
||||
News,
|
||||
}
|
||||
|
||||
private fun initializeId(): ID {
|
||||
|
|
@ -41,6 +42,7 @@ sealed class ApiConfig {
|
|||
is BlockAid -> ID.BlockAid
|
||||
is YieldSupply -> ID.YieldSupply
|
||||
is MoonPay -> ID.MoonPay
|
||||
is News -> ID.News
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,62 @@
|
|||
package com.tangem.datasource.api.common.config
|
||||
|
||||
import com.tangem.datasource.BuildConfig
|
||||
import com.tangem.datasource.api.common.AuthProvider
|
||||
import com.tangem.datasource.utils.RequestHeader
|
||||
import com.tangem.utils.Provider
|
||||
|
||||
/**
|
||||
* News [ApiConfig]
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class News(
|
||||
private val authProvider: AuthProvider,
|
||||
) : ApiConfig() {
|
||||
|
||||
override val defaultEnvironment: ApiEnvironment = getInitialEnvironment()
|
||||
|
||||
override val environmentConfigs: List<ApiEnvironmentConfig> = listOf(
|
||||
createProdEnvironment(),
|
||||
createDevEnvironment(),
|
||||
)
|
||||
|
||||
private fun getInitialEnvironment(): ApiEnvironment {
|
||||
return when (BuildConfig.BUILD_TYPE) {
|
||||
MOCKED_BUILD_TYPE,
|
||||
DEBUG_BUILD_TYPE,
|
||||
-> ApiEnvironment.DEV
|
||||
INTERNAL_BUILD_TYPE,
|
||||
EXTERNAL_BUILD_TYPE,
|
||||
RELEASE_BUILD_TYPE,
|
||||
-> ApiEnvironment.PROD
|
||||
else -> error("Unknown build type [${BuildConfig.BUILD_TYPE}]")
|
||||
}
|
||||
}
|
||||
|
||||
private fun createProdEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
|
||||
environment = ApiEnvironment.PROD,
|
||||
baseUrl = PROD_BASE_URL,
|
||||
headers = createHeaders(ApiEnvironment.PROD),
|
||||
)
|
||||
|
||||
private fun createDevEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
|
||||
environment = ApiEnvironment.DEV,
|
||||
baseUrl = DEV_BASE_URL,
|
||||
headers = createHeaders(ApiEnvironment.DEV),
|
||||
)
|
||||
|
||||
private fun createHeaders(environment: ApiEnvironment) = buildMap {
|
||||
putAll(
|
||||
RequestHeader.TangemApiKeyHeader(
|
||||
authProvider = authProvider,
|
||||
apiEnvironment = Provider { environment },
|
||||
).values,
|
||||
)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
|
||||
private const val PROD_BASE_URL = "https://api.tangem.org/"
|
||||
private const val DEV_BASE_URL = "[REDACTED_ENV_URL]"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
package com.tangem.datasource.api.news
|
||||
|
||||
import com.tangem.datasource.api.common.response.ApiResponse
|
||||
import com.tangem.datasource.api.news.models.response.NewsCategoriesResponse
|
||||
import com.tangem.datasource.api.news.models.response.NewsDetailsResponse
|
||||
import com.tangem.datasource.api.news.models.response.NewsListResponse
|
||||
import com.tangem.datasource.api.news.models.response.NewsTrendingResponse
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Path
|
||||
import retrofit2.http.Query
|
||||
|
||||
interface NewsApi {
|
||||
|
||||
@GET(NEWS_PATH)
|
||||
suspend fun getNews(
|
||||
@Query("page") page: Int? = null,
|
||||
@Query("limit") limit: Int? = null,
|
||||
@Query("lang") language: String? = null,
|
||||
@Query("asOf") snapshot: String? = null,
|
||||
@Query("tokenIds") tokenIds: List<String>? = null,
|
||||
@Query("categoryIds") categoryIds: List<Int>? = null,
|
||||
): ApiResponse<NewsListResponse>
|
||||
|
||||
@GET("$NEWS_PATH/{newsId}")
|
||||
suspend fun getNewsDetails(
|
||||
@Path("newsId") newsId: Int,
|
||||
@Query("lang") language: String? = null,
|
||||
): ApiResponse<NewsDetailsResponse>
|
||||
|
||||
@GET("$NEWS_PATH/trending")
|
||||
suspend fun getTrendingNews(
|
||||
@Query("limit") limit: Int? = null,
|
||||
@Query("lang") language: String? = null,
|
||||
): ApiResponse<NewsTrendingResponse>
|
||||
|
||||
@GET("$NEWS_PATH/categories")
|
||||
suspend fun getCategories(): ApiResponse<NewsCategoriesResponse>
|
||||
|
||||
private companion object {
|
||||
|
||||
private const val NEWS_PATH = "api/v1/news"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
package com.tangem.datasource.api.news.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class NewsArticleDto(
|
||||
@Json(name = "id") val id: Int,
|
||||
@Json(name = "createdAt") val createdAt: String,
|
||||
@Json(name = "score") val score: Double,
|
||||
@Json(name = "language") val language: String,
|
||||
@Json(name = "isTrending") val isTrending: Boolean,
|
||||
@Json(name = "categories") val categories: List<NewsCategoryDto>,
|
||||
@Json(name = "relatedTokens") val relatedTokens: List<NewsRelatedTokenDto>,
|
||||
@Json(name = "title") val title: String,
|
||||
@Json(name = "newsUrl") val newsUrl: String,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class NewsCategoryDto(
|
||||
@Json(name = "id") val id: Int,
|
||||
@Json(name = "name") val name: String,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class NewsRelatedTokenDto(
|
||||
@Json(name = "id") val id: String,
|
||||
@Json(name = "symbol") val symbol: String,
|
||||
@Json(name = "name") val name: String,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class NewsOriginalArticleDto(
|
||||
@Json(name = "id") val id: Int,
|
||||
@Json(name = "title") val title: String,
|
||||
@Json(name = "sourceName") val sourceName: String,
|
||||
@Json(name = "language") val language: String,
|
||||
@Json(name = "publishedAt") val publishedAt: String,
|
||||
@Json(name = "url") val url: String,
|
||||
@Json(name = "imageUrl") val imageUrl: String? = null,
|
||||
)
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package com.tangem.datasource.api.news.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class NewsCategoriesResponse(
|
||||
@Json(name = "items") val items: List<NewsCategoryDto>,
|
||||
)
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.datasource.api.news.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class NewsDetailsResponse(
|
||||
@Json(name = "id") val id: Int,
|
||||
@Json(name = "createdAt") val createdAt: String,
|
||||
@Json(name = "score") val score: Double,
|
||||
@Json(name = "language") val language: String,
|
||||
@Json(name = "isTrending") val isTrending: Boolean,
|
||||
@Json(name = "categories") val categories: List<NewsCategoryDto>,
|
||||
@Json(name = "relatedTokens") val relatedTokens: List<NewsRelatedTokenDto>,
|
||||
@Json(name = "title") val title: String,
|
||||
@Json(name = "newsUrl") val newsUrl: String,
|
||||
@Json(name = "shortContent") val shortContent: String,
|
||||
@Json(name = "content") val content: String,
|
||||
@Json(name = "originalArticles") val originalArticles: List<NewsOriginalArticleDto>,
|
||||
)
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package com.tangem.datasource.api.news.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class NewsListResponse(
|
||||
@Json(name = "meta") val meta: NewsListMetaDto,
|
||||
@Json(name = "items") val items: List<NewsArticleDto>,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class NewsListMetaDto(
|
||||
@Json(name = "page") val page: Int,
|
||||
@Json(name = "limit") val limit: Int,
|
||||
@Json(name = "total") val total: Long,
|
||||
@Json(name = "hasNext") val hasNext: Boolean,
|
||||
@Json(name = "asOf") val asOf: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package com.tangem.datasource.api.news.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class NewsTrendingResponse(
|
||||
@Json(name = "meta") val meta: NewsTrendingMetaDto,
|
||||
@Json(name = "items") val items: List<NewsArticleDto>,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class NewsTrendingMetaDto(
|
||||
@Json(name = "limit") val limit: Int,
|
||||
)
|
||||
|
|
@ -58,6 +58,10 @@ internal object ApiConfigsModule {
|
|||
appInfoProvider = appInfoProvider,
|
||||
)
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
fun provideNewsConfig(authProvider: AuthProvider): ApiConfig = News(authProvider = authProvider)
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
fun provideYieldSupplyConfig(
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import com.tangem.datasource.api.common.config.managers.ProdApiConfigsManager
|
|||
import com.tangem.datasource.api.express.TangemExpressApi
|
||||
import com.tangem.datasource.api.markets.TangemTechMarketsApi
|
||||
import com.tangem.datasource.api.moonpay.MoonPayApi
|
||||
import com.tangem.datasource.api.news.NewsApi
|
||||
import com.tangem.datasource.api.onramp.OnrampApi
|
||||
import com.tangem.datasource.api.ethpool.P2PEthPoolApi
|
||||
import com.tangem.datasource.api.pay.TangemPayApi
|
||||
|
|
@ -151,4 +152,13 @@ internal object NetworkModule {
|
|||
applyTimeoutAnnotations = false,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideNewsApi(retrofitApiBuilder: RetrofitApiBuilder): NewsApi {
|
||||
return retrofitApiBuilder.build(
|
||||
apiConfigId = ApiConfig.ID.News,
|
||||
applyTimeoutAnnotations = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -73,6 +73,7 @@ class ApiConfigTest {
|
|||
ApiConfig.ID.BlockAid -> BlockAid(configStorage = mockk())
|
||||
ApiConfig.ID.MoonPay -> MoonPay()
|
||||
ApiConfig.ID.P2PEthPool -> P2PEthPool(p2pAuthProvider = mockk())
|
||||
ApiConfig.ID.News -> News(authProvider = appAuthProvider)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -114,6 +114,7 @@ internal class ProdApiConfigsManagerTest {
|
|||
ApiConfig.ID.BlockAid -> BlockAid(configStorage = environmentConfigStorage)
|
||||
ApiConfig.ID.MoonPay -> MoonPay()
|
||||
ApiConfig.ID.P2PEthPool -> P2PEthPool(p2pAuthProvider = p2pEthPoolAuthProvider)
|
||||
ApiConfig.ID.News -> News(authProvider = appAuthProvider)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -128,6 +129,7 @@ internal class ProdApiConfigsManagerTest {
|
|||
ApiConfig.ID.BlockAid -> createBlockAidSdkModel()
|
||||
ApiConfig.ID.MoonPay -> createMoonPayModel()
|
||||
ApiConfig.ID.P2PEthPool -> createP2PModel()
|
||||
ApiConfig.ID.News -> createNewsModel()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -295,6 +297,29 @@ internal class ProdApiConfigsManagerTest {
|
|||
)
|
||||
}
|
||||
|
||||
private fun createNewsModel(): TestModel {
|
||||
val (environment, baseUrl) = when (BuildConfig.BUILD_TYPE) {
|
||||
MOCKED_BUILD_TYPE,
|
||||
DEBUG_BUILD_TYPE,
|
||||
-> ApiEnvironment.DEV to "[REDACTED_ENV_URL]"
|
||||
INTERNAL_BUILD_TYPE,
|
||||
EXTERNAL_BUILD_TYPE,
|
||||
RELEASE_BUILD_TYPE,
|
||||
-> ApiEnvironment.PROD to "https://tangem.com/"
|
||||
else -> error("Unknown build type [${BuildConfig.BUILD_TYPE}]")
|
||||
}
|
||||
return TestModel(
|
||||
id = ApiConfig.ID.News,
|
||||
expected = ApiEnvironmentConfig(
|
||||
environment = environment,
|
||||
baseUrl = baseUrl,
|
||||
headers = mapOf(
|
||||
"api-key" to ProviderSuspend { TANGEM_API_KEY },
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun String.checkHeaderValueOrEmpty(): String {
|
||||
for (i in this.indices) {
|
||||
val c = this[i]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue