forked from Olympic/NTO-2025-Client-Android-backend
Compare commits
1 Commits
backedn
...
test-push-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6af0d819db |
@@ -12,24 +12,13 @@ application {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
// СЕРВЕРНЫЕ ЗАВИСИМОСТИ KTOR
|
|
||||||
implementation(libs.ktor.server.content.negotiation)
|
implementation(libs.ktor.server.content.negotiation)
|
||||||
implementation(libs.ktor.serialization.kotlinx.json)
|
implementation(libs.ktor.serialization.kotlinx.json)
|
||||||
implementation(libs.ktor.server.core)
|
implementation(libs.ktor.server.core)
|
||||||
implementation(libs.ktor.server.netty)
|
implementation(libs.ktor.server.netty)
|
||||||
implementation(libs.logback.classic)
|
implementation(libs.logback.classic)
|
||||||
|
implementation(libs.ktor.server.core)
|
||||||
implementation(libs.ktor.server.config.yaml)
|
implementation(libs.ktor.server.config.yaml)
|
||||||
|
|
||||||
// HTTP-клиент для Kotlin/JVM (не Android!)
|
|
||||||
implementation("io.ktor:ktor-client-cio:2.3.9")
|
|
||||||
implementation("io.ktor:ktor-client-content-negotiation:2.3.9")
|
|
||||||
implementation("io.ktor:ktor-client-logging:2.3.9")
|
|
||||||
|
|
||||||
// Корутины и сериализация
|
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
|
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2")
|
|
||||||
|
|
||||||
// Тесты
|
|
||||||
testImplementation(libs.ktor.server.test.host)
|
testImplementation(libs.ktor.server.test.host)
|
||||||
testImplementation(libs.kotlin.test.junit)
|
testImplementation(libs.kotlin.test.junit)
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,6 @@
|
|||||||
kotlin = "2.2.20"
|
kotlin = "2.2.20"
|
||||||
ktor = "3.3.0"
|
ktor = "3.3.0"
|
||||||
logback = "1.4.14"
|
logback = "1.4.14"
|
||||||
lifecycleViewmodelAndroid = "2.10.0"
|
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
ktor-server-content-negotiation = { module = "io.ktor:ktor-server-content-negotiation", version.ref = "ktor" }
|
ktor-server-content-negotiation = { module = "io.ktor:ktor-server-content-negotiation", version.ref = "ktor" }
|
||||||
@@ -13,7 +12,6 @@ logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "lo
|
|||||||
ktor-server-config-yaml = { module = "io.ktor:ktor-server-config-yaml", version.ref = "ktor" }
|
ktor-server-config-yaml = { module = "io.ktor:ktor-server-config-yaml", version.ref = "ktor" }
|
||||||
ktor-server-test-host = { module = "io.ktor:ktor-server-test-host", version.ref = "ktor" }
|
ktor-server-test-host = { module = "io.ktor:ktor-server-test-host", version.ref = "ktor" }
|
||||||
kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" }
|
kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" }
|
||||||
lifecycle-viewmodel-android = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-android", version.ref = "lifecycleViewmodelAndroid" }
|
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
|
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
|
||||||
|
|||||||
@@ -4,5 +4,5 @@
|
|||||||
# Location of the SDK. This is only used by Gradle.
|
# Location of the SDK. This is only used by Gradle.
|
||||||
# For customization when using a Version Control System, please read the
|
# For customization when using a Version Control System, please read the
|
||||||
# header note.
|
# header note.
|
||||||
#Fri Dec 12 08:17:55 YEKT 2025
|
#Fri Dec 12 09:42:48 YEKT 2025
|
||||||
sdk.dir=D\:\\androidstudio
|
sdk.dir=D\:\\androidstudio
|
||||||
|
|||||||
2
src/main/kotlin/Application.kt
Normal file → Executable file
2
src/main/kotlin/Application.kt
Normal file → Executable file
@@ -10,7 +10,7 @@ fun main(args: Array<String>) {
|
|||||||
|
|
||||||
fun Application.module() {
|
fun Application.module() {
|
||||||
install(ContentNegotiation) {
|
install(ContentNegotiation) {
|
||||||
json()
|
json()asd
|
||||||
}
|
}
|
||||||
configureRouting()
|
configureRouting()
|
||||||
}
|
}
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
package com.example
|
|
||||||
|
|
||||||
import com.example.di.Dependencies
|
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
|
|
||||||
fun main() = runBlocking {
|
|
||||||
val viewModel = Dependencies.createBookingViewModel()
|
|
||||||
|
|
||||||
// Настраиваем колбэки
|
|
||||||
viewModel.onBookingsUpdated = { bookings ->
|
|
||||||
println("Бронирования обновлены:")
|
|
||||||
bookings.forEach { booking ->
|
|
||||||
println(" Комната: ${booking.room}, Время: ${booking.time}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
viewModel.onError = { error ->
|
|
||||||
if (error != null) {
|
|
||||||
println("Ошибка: $error")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Загружаем текущие бронирования
|
|
||||||
println("Загружаем бронирования...")
|
|
||||||
viewModel.loadBookings()
|
|
||||||
|
|
||||||
delay(1000) // Ждём загрузки
|
|
||||||
|
|
||||||
// Добавляем новое бронирование
|
|
||||||
println("\nДобавляем новое бронирование...")
|
|
||||||
viewModel.addBooking("505.6", "14:00 - 16:00")
|
|
||||||
|
|
||||||
delay(1000) // Ждём обработки
|
|
||||||
|
|
||||||
viewModel.dispose()
|
|
||||||
}
|
|
||||||
@@ -8,7 +8,6 @@ import io.ktor.http.content.PartData
|
|||||||
import io.ktor.http.content.forEachPart
|
import io.ktor.http.content.forEachPart
|
||||||
import io.ktor.server.application.*
|
import io.ktor.server.application.*
|
||||||
import io.ktor.server.request.receiveMultipart
|
import io.ktor.server.request.receiveMultipart
|
||||||
import io.ktor.server.request.receiveParameters
|
|
||||||
import io.ktor.server.response.*
|
import io.ktor.server.response.*
|
||||||
import io.ktor.server.routing.*
|
import io.ktor.server.routing.*
|
||||||
|
|
||||||
@@ -28,29 +27,38 @@ fun Application.configureRouting() {
|
|||||||
get("/user") {
|
get("/user") {
|
||||||
call.respond(UserDto(booking = booking))
|
call.respond(UserDto(booking = booking))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ЗАМЕНИТЕ ЭТОТ БЛОК (от post("/book") до закрывающей скобки):
|
|
||||||
post("/book") {
|
post("/book") {
|
||||||
try {
|
var room = ""
|
||||||
// Получаем данные в формате x-www-form-urlencoded
|
var time = ""
|
||||||
val parameters = call.receiveParameters()
|
val multipartData = call.receiveMultipart()
|
||||||
val room = parameters["room"]?.trim() ?: ""
|
|
||||||
val time = parameters["time"]?.trim() ?: ""
|
|
||||||
|
|
||||||
if (room.isEmpty() || time.isEmpty()) {
|
multipartData.forEachPart { part ->
|
||||||
call.respond(
|
try {
|
||||||
HttpStatusCode.BadRequest,
|
if (part !is PartData.FormItem) return@forEachPart
|
||||||
ErrorDto(error = "Fields 'room' and 'time' are required")
|
when (part.name) {
|
||||||
)
|
"room" -> {
|
||||||
} else {
|
room = part.value.trim()
|
||||||
booking.add(BookingDto(room = room, time = time))
|
}
|
||||||
call.respond(HttpStatusCode.OK, mapOf("success" to true))
|
|
||||||
|
"time" -> {
|
||||||
|
time = part.value.trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> Unit
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
part.dispose()
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
}
|
||||||
|
|
||||||
|
if (room.isEmpty() && time.isEmpty()) {
|
||||||
call.respond(
|
call.respond(
|
||||||
HttpStatusCode.BadRequest,
|
HttpStatusCode.BadRequest,
|
||||||
ErrorDto(error = "Invalid request format")
|
ErrorDto(error = "Field is empty")
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
booking.add(BookingDto(room = room, time = time))
|
||||||
|
call.respond(HttpStatusCode.OK)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
package com.example.data.mapper
|
|
||||||
|
|
||||||
import com.example.data.remote.dto.BookingResponse
|
|
||||||
import com.example.domain.models.Booking
|
|
||||||
|
|
||||||
fun BookingResponse.toDomain(): Booking {
|
|
||||||
return Booking(
|
|
||||||
room = room,
|
|
||||||
time = time
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
package com.example.data.remote
|
|
||||||
|
|
||||||
import com.example.data.remote.dto.BookingResponse
|
|
||||||
import com.example.data.remote.dto.UserResponse
|
|
||||||
import io.ktor.client.*
|
|
||||||
import io.ktor.client.call.*
|
|
||||||
import io.ktor.client.request.*
|
|
||||||
import io.ktor.http.*
|
|
||||||
import io.ktor.client.request.forms.*
|
|
||||||
|
|
||||||
interface BookingApi {
|
|
||||||
suspend fun getUser(): UserResponse
|
|
||||||
suspend fun bookRoom(room: String, time: String): Boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
class BookingApiImpl(private val client: HttpClient) : BookingApi {
|
|
||||||
private val baseUrl = "http://localhost:8080"
|
|
||||||
|
|
||||||
override suspend fun getUser(): UserResponse {
|
|
||||||
return client.get("$baseUrl/user").body()
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun bookRoom(room: String, time: String): Boolean {
|
|
||||||
return try {
|
|
||||||
val response = client.submitForm(
|
|
||||||
url = "$baseUrl/book",
|
|
||||||
formParameters = Parameters.build {
|
|
||||||
append("room", room)
|
|
||||||
append("time", time)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
response.status == HttpStatusCode.OK
|
|
||||||
} catch (e: Exception) {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
package com.example.data.remote.dto
|
|
||||||
|
|
||||||
import kotlinx.serialization.SerialName
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class UserResponse(
|
|
||||||
@SerialName("name")
|
|
||||||
val name: String,
|
|
||||||
@SerialName("booking")
|
|
||||||
val booking: List<BookingResponse>
|
|
||||||
)
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class BookingResponse(
|
|
||||||
@SerialName("room")
|
|
||||||
val room: String,
|
|
||||||
@SerialName("time")
|
|
||||||
val time: String
|
|
||||||
)
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
package com.example.data.repository
|
|
||||||
|
|
||||||
import com.example.data.mapper.toDomain
|
|
||||||
import com.example.data.remote.BookingApi
|
|
||||||
import com.example.domain.models.Booking
|
|
||||||
import com.example.domain.repository.BookingRepository
|
|
||||||
|
|
||||||
class BookingRepositoryImpl(
|
|
||||||
private val api: BookingApi
|
|
||||||
) : BookingRepository {
|
|
||||||
|
|
||||||
override suspend fun getBookings(): List<Booking> {
|
|
||||||
return try {
|
|
||||||
api.getUser().booking.map { it.toDomain() }
|
|
||||||
} catch (e: Exception) {
|
|
||||||
emptyList()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun addBooking(room: String, time: String): Boolean {
|
|
||||||
return api.bookRoom(room, time)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
package com.example.di
|
|
||||||
|
|
||||||
import com.example.data.remote.BookingApi
|
|
||||||
import com.example.data.remote.BookingApiImpl
|
|
||||||
import com.example.data.repository.BookingRepositoryImpl
|
|
||||||
import com.example.domain.repository.BookingRepository
|
|
||||||
import com.example.presentation.BookingViewModel
|
|
||||||
import io.ktor.client.*
|
|
||||||
import io.ktor.client.engine.cio.*
|
|
||||||
import io.ktor.client.plugins.contentnegotiation.*
|
|
||||||
import io.ktor.client.plugins.logging.*
|
|
||||||
import io.ktor.serialization.kotlinx.json.*
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
|
|
||||||
object Dependencies {
|
|
||||||
val httpClient: HttpClient by lazy {
|
|
||||||
HttpClient(CIO) {
|
|
||||||
install(ContentNegotiation) {
|
|
||||||
json(Json {
|
|
||||||
ignoreUnknownKeys = true
|
|
||||||
isLenient = true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
install(Logging) {
|
|
||||||
level = LogLevel.ALL
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val bookingApi: BookingApi by lazy {
|
|
||||||
BookingApiImpl(httpClient)
|
|
||||||
}
|
|
||||||
|
|
||||||
val bookingRepository: BookingRepository by lazy {
|
|
||||||
BookingRepositoryImpl(bookingApi)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun createBookingViewModel(): BookingViewModel {
|
|
||||||
return BookingViewModel(bookingRepository)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
package com.example.domain.models
|
|
||||||
|
|
||||||
data class Booking(
|
|
||||||
val room: String,
|
|
||||||
val time: String
|
|
||||||
)
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
package com.example.domain.models
|
|
||||||
|
|
||||||
data class User(
|
|
||||||
val name: String,
|
|
||||||
val bookings: List<Booking>
|
|
||||||
)
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
package com.example.domain.repository
|
|
||||||
|
|
||||||
import com.example.domain.models.Booking
|
|
||||||
|
|
||||||
interface BookingRepository {
|
|
||||||
suspend fun getBookings(): List<Booking>
|
|
||||||
suspend fun addBooking(room: String, time: String): Boolean
|
|
||||||
}
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
package com.example.presentation
|
|
||||||
|
|
||||||
import com.example.domain.models.Booking
|
|
||||||
import com.example.domain.repository.BookingRepository
|
|
||||||
import kotlinx.coroutines.*
|
|
||||||
import kotlin.coroutines.CoroutineContext
|
|
||||||
|
|
||||||
class BookingViewModel(
|
|
||||||
private val repository: BookingRepository,
|
|
||||||
private val coroutineContext: CoroutineContext = Dispatchers.Default
|
|
||||||
) {
|
|
||||||
private var bookings: List<Booking> = emptyList()
|
|
||||||
private var error: String? = null
|
|
||||||
private val scope = CoroutineScope(coroutineContext)
|
|
||||||
|
|
||||||
// Колбэки для обновления UI
|
|
||||||
var onBookingsUpdated: (List<Booking>) -> Unit = {}
|
|
||||||
var onError: (String?) -> Unit = {}
|
|
||||||
|
|
||||||
fun loadBookings() {
|
|
||||||
scope.launch {
|
|
||||||
try {
|
|
||||||
bookings = repository.getBookings()
|
|
||||||
error = null
|
|
||||||
onBookingsUpdated(bookings)
|
|
||||||
onError(null)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
error = "Ошибка загрузки: ${e.message}"
|
|
||||||
onError(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addBooking(room: String, time: String) {
|
|
||||||
if (room.isBlank() || time.isBlank()) {
|
|
||||||
error = "Заполните все поля"
|
|
||||||
onError(error)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
scope.launch {
|
|
||||||
try {
|
|
||||||
val success = repository.addBooking(room, time)
|
|
||||||
if (success) {
|
|
||||||
loadBookings() // Обновляем список
|
|
||||||
} else {
|
|
||||||
error = "Ошибка бронирования"
|
|
||||||
onError(error)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
error = "Ошибка: ${e.message}"
|
|
||||||
onError(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getCurrentBookings(): List<Booking> = bookings
|
|
||||||
fun getCurrentError(): String? = error
|
|
||||||
|
|
||||||
fun dispose() {
|
|
||||||
scope.cancel()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user