projects request and test

This commit is contained in:
user5 2025-05-27 12:07:58 +03:00
parent 7e8fbd3063
commit c0de6f6606
12 changed files with 182 additions and 34 deletions

View File

@ -13,15 +13,20 @@ object ApiFactory {
fun provideRepository(dataStoreRepository: DataStoreRepository): ApiRepository {
val json = Json { ignoreUnknownKeys = true }
val client = OkHttpClient.Builder()
.addInterceptor(HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
})
.addInterceptor(AuthInterceptor(dataStoreRepository))
.build()
val retrofit = Retrofit.Builder()
.baseUrl("https://api.matule.ru/api/")
.addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
.client(OkHttpClient.Builder().addInterceptor(HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}).build())
.client(client)
.build()
return BaseApiRepository(retrofit.create<ApiService>(), dataStoreRepository)
return BaseApiRepository(retrofit.create<ApiService>(), dataStoreRepository, Handle.Base())
}
}

View File

@ -2,7 +2,7 @@ package com.example.api.core.data.core
import com.example.api.core.data.dto.AuthUserResponseDto
import com.example.api.core.data.dto.RegisterUserDto
import com.example.api.core.data.dto.SalesAndActionsDto
import com.example.api.core.data.dto.ProjectsDto
import com.example.api.core.data.dto.SignInUserDto
import com.example.api.core.data.dto.UserResponseDto
import retrofit2.http.Body
@ -23,7 +23,7 @@ internal interface ApiService {
suspend fun auth(@Body signInUserDto: SignInUserDto): AuthUserResponseDto
@GET("collections/project/records")
suspend fun salesAndProjects(): SalesAndActionsDto
suspend fun projects(): ProjectsDto
// @GET("collections/products/records")
// suspend fun products():

View File

@ -0,0 +1,22 @@
package com.example.api.core.data.core
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import okhttp3.Interceptor
import okhttp3.Response
/**
* Автор: Манякин Дмитрий (user5)
* Дата создания: 27.05.2025
* */
class AuthInterceptor(private val dataStoreRepository: DataStoreRepository) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val token = runBlocking { dataStoreRepository.token().first() }
val request = chain.request().newBuilder().apply {
token?.let { addHeader("Authorization", "Bearer $token") }
}
return chain.proceed(request.build())
}
}

View File

@ -4,6 +4,7 @@ import com.example.api.core.domain.ApiRepository
import com.example.api.core.domain.AuthUserResponse
import com.example.api.core.domain.FetchResult
import com.example.api.core.domain.RegisterUser
import com.example.api.core.domain.Projects
import retrofit2.HttpException
import java.net.UnknownHostException
@ -12,24 +13,42 @@ import java.net.UnknownHostException
* Дата создания: 27.05.2025
* */
internal interface Handle {
suspend fun <T> handle(action: suspend () -> T): FetchResult<T>
class Base : Handle {
override suspend fun <T> handle(action: suspend () -> T): FetchResult<T> {
return try {
val result = action.invoke()
FetchResult.Success(result)
} catch (e: UnknownHostException) {
FetchResult.Error(null, "no internet connection")
} catch (e: HttpException) {
FetchResult.Error(null, e.message())
} catch (e: Exception) {
FetchResult.Error(null, e.message!!)
}
}
}
}
internal class BaseApiRepository(
private val service: ApiService,
private val dataStoreRepository: DataStoreRepository
private val dataStoreRepository: DataStoreRepository,
private val handle: Handle,
) : RemoteMapper(), ApiRepository {
override suspend fun auth(registerUser: RegisterUser): FetchResult<AuthUserResponse> {
return try {
service.register(registerUser.toRegisterUserDto())
val response = service.auth(registerUser.toSignInUserDto())
dataStoreRepository.saveToken(response.token)
override suspend fun auth(registerUser: RegisterUser): FetchResult<AuthUserResponse> = handle.handle {
service.register(registerUser.toRegisterUserDto())
val response = service.auth(registerUser.toSignInUserDto())
dataStoreRepository.saveToken(response.token)
response.toUserResponse()
}
FetchResult.Success(response.toUserResponse())
} catch (e: UnknownHostException) {
FetchResult.Error(null, "no internet connection")
} catch (e: HttpException) {
FetchResult.Error(null, e.message())
} catch (e: Exception) {
FetchResult.Error(null, e.message!!)
}
override suspend fun projects(): FetchResult<Projects> = handle.handle {
service.projects().toDomain()
}
}

View File

@ -16,7 +16,7 @@ interface DataStoreRepository {
suspend fun saveToken(token: String)
fun token(): Flow<String>
fun token(): Flow<String?>
class Base(private val preferences: DataStore<Preferences>) : DataStoreRepository {
override suspend fun saveToken(token: String) {
@ -25,9 +25,9 @@ interface DataStoreRepository {
}
}
override fun token(): Flow<String> {
override fun token(): Flow<String?> {
return preferences.data.map { preferences ->
preferences[TOKEN_KEY] ?: ""
preferences[TOKEN_KEY]
}
}
}

View File

@ -2,13 +2,15 @@ package com.example.api.core.data.core
import com.example.api.core.data.dto.AuthUserResponseDto
import com.example.api.core.data.dto.RegisterUserDto
import com.example.api.core.data.dto.SaleItemDto
import com.example.api.core.data.dto.ProjectsDto
import com.example.api.core.data.dto.SignInUserDto
import com.example.api.core.data.dto.SignInUserResponseDto
import com.example.api.core.data.dto.UserResponseDto
import com.example.api.core.domain.AuthUserResponse
import com.example.api.core.domain.RegisterUser
import com.example.api.core.domain.SaleItem
import com.example.api.core.domain.Projects
import com.example.api.core.domain.SignInUserResponse
import com.example.api.core.domain.UserResponse
/**
* Автор: Манякин Дмитрий (user5)
@ -55,4 +57,32 @@ internal abstract class RemoteMapper {
verified = verified
)
}
protected fun ProjectsDto.toDomain(): Projects {
return Projects(
page = page,
perPage = perPage,
totalPage = totalPages,
totalItems = totalItems,
items = items.map { it.toSaleItem() }
)
}
protected fun SaleItemDto.toSaleItem(): SaleItem {
return SaleItem(
id = id,
collectionId = collectionId,
collectionName = collectionName,
created = created,
updated = updated,
title = title,
dateStart = dateStart,
dateEnd = dateEnd,
gender = gender,
descriptionSource = descriptionSource,
category = category,
image = image,
userId = userId
)
}
}

View File

@ -3,10 +3,10 @@ package com.example.api.core.data.dto
import kotlinx.serialization.Serializable
@Serializable
internal data class SalesAndActionsDto(
internal data class ProjectsDto(
val page: Int,
val perPage: Int,
val totalPage: Int,
val totalPages: Int,
val totalItems: Int,
val items: List<SaleItemDto>
)

View File

@ -8,4 +8,6 @@ package com.example.api.core.domain
interface ApiRepository {
suspend fun auth(registerUser: RegisterUser): FetchResult<AuthUserResponse>
suspend fun projects(): FetchResult<Projects>
}

View File

@ -1,6 +1,6 @@
package com.example.api.core.domain
internal data class SalesAndActions(
data class Projects(
val page: Int,
val perPage: Int,
val totalPage: Int,

View File

@ -126,6 +126,49 @@ class ServiceTest {
assertEquals("/collections/users/auth-with-password", request.path)
}
@Test
fun test_project_list() = runBlocking {
val response = """
{
"items": [
{
"category": "е",
"collectionId": "pbc_3202395908",
"collectionName": "project",
"created": "2025-05-26 14:47:25.523Z",
"dateEnd": "2025-05-24 12:00:00.000Z",
"dateStart": "2025-05-15 12:00:00.000Z",
"description_source": "е",
"gender": "е",
"id": "7m35pcmbjr86kh4",
"image": "",
"title": "е",
"typeProject": "е",
"updated": "2025-05-26 14:47:25.523Z",
"user_id": ""
}
],
"page": 1,
"perPage": 30,
"totalItems": 1,
"totalPages": 1
}
""".trimIndent()
mockWebServer.enqueue(MockResponse().setResponseCode(200).setBody(response))
val responseResult = service.projects()
val request = mockWebServer.takeRequest()
assertEquals(1, responseResult.page)
assertEquals(1, responseResult.totalItems)
assertEquals(1, responseResult.items.size)
assertEquals("GET", request.method)
assertEquals("/collections/project/records", request.path)
}
@After
fun teardown() {
mockWebServer.shutdown()

View File

@ -5,6 +5,7 @@ import androidx.lifecycle.viewModelScope
import com.example.api.core.domain.ApiRepository
import com.example.api.core.domain.AuthUserResponse
import com.example.api.core.domain.RegisterUser
import com.example.api.core.domain.Projects
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
@ -24,10 +25,15 @@ class MainViewModel @Inject constructor(
private val apiRepository: ApiRepository
) : ViewModel() {
private val _loadResultUiState =
private val _authUiState =
MutableStateFlow<FetchResultUiState<AuthUserResponse>>(FetchResultUiState.Initial())
val loadResultUiState: StateFlow<FetchResultUiState<AuthUserResponse>>
get() = _loadResultUiState.asStateFlow()
val authUiState: StateFlow<FetchResultUiState<AuthUserResponse>>
get() = _authUiState.asStateFlow()
private val _ProjectsUiState =
MutableStateFlow<FetchResultUiState<Projects>>(FetchResultUiState.Initial())
val projectsUiState: StateFlow<FetchResultUiState<Projects>>
get() = _ProjectsUiState.asStateFlow()
fun auth(email: String, password: String) {
viewModelScope.launch(Dispatchers.IO) {
@ -40,7 +46,17 @@ class MainViewModel @Inject constructor(
)
withContext(Dispatchers.Main) {
_loadResultUiState.value = result.map(FetchResultMapper())
_authUiState.value = result.map(FetchResultMapper())
}
}
}
fun salesAndProjects() {
viewModelScope.launch(Dispatchers.IO) {
val result = apiRepository.projects()
withContext(Dispatchers.Main) {
_ProjectsUiState.value = result.map(FetchResultMapper())
}
}
}

View File

@ -23,11 +23,12 @@ fun TestScreen(
modifier: Modifier = Modifier,
viewModel: MainViewModel = hiltViewModel()
) {
val state by viewModel.loadResultUiState.collectAsState()
val state by viewModel.authUiState.collectAsState()
val state2 by viewModel.projectsUiState.collectAsState()
Box(modifier = modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Column {
Button(onClick = { viewModel.auth("someemael@te3st1.com", "12345678") }) {
Button(onClick = { viewModel.auth("someemael@te3s11t1.com", "12345678") }) {
Text("register")
}
@ -36,6 +37,16 @@ fun TestScreen(
onError = { _, message -> Text(text = message) },
onLoading = { CircularProgressIndicator() }
)
Button(onClick = { viewModel.salesAndProjects() }) {
Text("sales and projects")
}
state2.Show(
onSuccess = { Text(text = it.items.size.toString()) },
onError = { _, message -> Text(text = message) },
onLoading = { CircularProgressIndicator() }
)
}
}
}