feat: Реализация базового CalDav без to-do через REST API обёртку
feat: Информативное логгирование и ответы сервера
This commit is contained in:
@@ -0,0 +1,347 @@
|
||||
package com.nano
|
||||
|
||||
import com.nano.Service.*
|
||||
import io.ktor.http.*
|
||||
import io.ktor.serialization.kotlinx.json.*
|
||||
import io.ktor.server.application.*
|
||||
import io.ktor.server.auth.*
|
||||
import io.ktor.server.plugins.calllogging.*
|
||||
import io.ktor.server.plugins.contentnegotiation.*
|
||||
import io.ktor.server.request.*
|
||||
import io.ktor.server.response.*
|
||||
import io.ktor.server.routing.*
|
||||
import kotlinx.serialization.Serializable
|
||||
import org.slf4j.event.*
|
||||
import java.time.LocalDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
@Serializable
|
||||
data class CreateEventRequest(
|
||||
val summary: String,
|
||||
val description: String? = null,
|
||||
val startDateTime: String, // ISO format: "2025-07-20T10:00:00"
|
||||
val endDateTime: String,
|
||||
val location: String? = null
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class EventResponse(
|
||||
val uid: String,
|
||||
val summary: String,
|
||||
val description: String? = null,
|
||||
val startDateTime: String,
|
||||
val endDateTime: String,
|
||||
val location: String? = null
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class ApiResponse<T>(
|
||||
val success: Boolean,
|
||||
val data: T? = null,
|
||||
val message: String? = null
|
||||
)
|
||||
|
||||
fun Application.configureRouting() {
|
||||
routing {
|
||||
authenticate("calendar-auth") {
|
||||
route("/calendar") {
|
||||
route("/events") {
|
||||
|
||||
// Получение всех событий
|
||||
get {
|
||||
try {
|
||||
val principal = call.principal<UserIdPrincipal>()!!
|
||||
val username = principal.name
|
||||
|
||||
// Получаем пароль из заголовка Authorization для создания DAV подключения
|
||||
val authHeader = call.request.headers["Authorization"]
|
||||
val password = authHeader?.let { header ->
|
||||
val encoded = header.substringAfter("Basic ")
|
||||
val decoded = java.util.Base64.getDecoder().decode(encoded).toString(Charsets.UTF_8)
|
||||
decoded.substringAfter(":")
|
||||
} ?: ""
|
||||
|
||||
// Получаем календарь ID из заголовка CAL_ID
|
||||
val calendarId = call.request.headers["CAL_ID"] ?: return@get call.respond(
|
||||
HttpStatusCode.BadRequest,
|
||||
ApiResponse<String>(success = false, message = "Заголовок CAL_ID обязателен")
|
||||
)
|
||||
|
||||
val dav = Dav(username, password, calendarId)
|
||||
val eventManager = dav.getEventManager()
|
||||
|
||||
eventManager.getAllEvents().fold(
|
||||
onSuccess = { events ->
|
||||
val eventResponses = events.map { event ->
|
||||
EventResponse(
|
||||
uid = event.uid,
|
||||
summary = event.summary,
|
||||
description = event.description,
|
||||
startDateTime = event.startDateTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME),
|
||||
endDateTime = event.endDateTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME),
|
||||
location = event.location
|
||||
)
|
||||
}
|
||||
call.respond(ApiResponse(success = true, data = eventResponses))
|
||||
},
|
||||
onFailure = { error ->
|
||||
call.respond(
|
||||
HttpStatusCode.InternalServerError,
|
||||
ApiResponse<String>(success = false, message = error.message)
|
||||
)
|
||||
}
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
call.respond(
|
||||
HttpStatusCode.InternalServerError,
|
||||
ApiResponse<String>(success = false, message = "Неожиданная ошибка: ${e.message}")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Создание нового события
|
||||
post {
|
||||
try {
|
||||
val principal = call.principal<UserIdPrincipal>()!!
|
||||
val username = principal.name
|
||||
|
||||
val authHeader = call.request.headers["Authorization"]
|
||||
val password = authHeader?.let { header ->
|
||||
val encoded = header.substringAfter("Basic ")
|
||||
val decoded = java.util.Base64.getDecoder().decode(encoded).toString(Charsets.UTF_8)
|
||||
decoded.substringAfter(":")
|
||||
} ?: ""
|
||||
|
||||
// Получаем календарь ID из заголовка CAL_ID
|
||||
val calendarId = call.request.headers["CAL_ID"] ?: return@post call.respond(
|
||||
HttpStatusCode.BadRequest,
|
||||
ApiResponse<String>(success = false, message = "Заголовок CAL_ID обязателен")
|
||||
)
|
||||
|
||||
val request = call.receive<CreateEventRequest>()
|
||||
|
||||
val event = CalendarEvent(
|
||||
summary = request.summary,
|
||||
description = request.description,
|
||||
startDateTime = LocalDateTime.parse(request.startDateTime),
|
||||
endDateTime = LocalDateTime.parse(request.endDateTime),
|
||||
location = request.location
|
||||
)
|
||||
|
||||
val dav = Dav(username, password, calendarId)
|
||||
val eventManager = dav.getEventManager()
|
||||
|
||||
eventManager.addEvent(event).fold(
|
||||
onSuccess = { createdEvent ->
|
||||
val response = EventResponse(
|
||||
uid = createdEvent.uid,
|
||||
summary = createdEvent.summary,
|
||||
description = createdEvent.description,
|
||||
startDateTime = createdEvent.startDateTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME),
|
||||
endDateTime = createdEvent.endDateTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME),
|
||||
location = createdEvent.location
|
||||
)
|
||||
call.respond(HttpStatusCode.Created, ApiResponse(success = true, data = response))
|
||||
},
|
||||
onFailure = { error ->
|
||||
val statusCode = when {
|
||||
error.message?.contains("авторизации") == true -> HttpStatusCode.Unauthorized
|
||||
error.message?.contains("запрещен") == true -> HttpStatusCode.Forbidden
|
||||
error.message?.contains("не найден") == true -> HttpStatusCode.NotFound
|
||||
error.message?.contains("Конфликт") == true -> HttpStatusCode.Conflict
|
||||
else -> HttpStatusCode.BadRequest
|
||||
}
|
||||
call.respond(statusCode, ApiResponse<String>(success = false, message = error.message))
|
||||
}
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
call.respond(
|
||||
HttpStatusCode.InternalServerError,
|
||||
ApiResponse<String>(success = false, message = "Ошибка создания события: ${e.message}")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Получение события по UID
|
||||
get("/{uid}") {
|
||||
try {
|
||||
val principal = call.principal<UserIdPrincipal>()!!
|
||||
val username = principal.name
|
||||
val uid = call.parameters["uid"] ?: return@get call.respond(
|
||||
HttpStatusCode.BadRequest,
|
||||
ApiResponse<String>(success = false, message = "UID события не указан")
|
||||
)
|
||||
|
||||
val authHeader = call.request.headers["Authorization"]
|
||||
val password = authHeader?.let { header ->
|
||||
val encoded = header.substringAfter("Basic ")
|
||||
val decoded = java.util.Base64.getDecoder().decode(encoded).toString(Charsets.UTF_8)
|
||||
decoded.substringAfter(":")
|
||||
} ?: ""
|
||||
|
||||
// Получаем календарь ID из заголовка CAL_ID
|
||||
val calendarId = call.request.headers["CAL_ID"] ?: return@get call.respond(
|
||||
HttpStatusCode.BadRequest,
|
||||
ApiResponse<String>(success = false, message = "Заголовок CAL_ID обязателен")
|
||||
)
|
||||
|
||||
val dav = Dav(username, password, calendarId)
|
||||
val eventManager = dav.getEventManager()
|
||||
|
||||
eventManager.getEvent(uid).fold(
|
||||
onSuccess = { event ->
|
||||
if (event != null) {
|
||||
val response = EventResponse(
|
||||
uid = event.uid,
|
||||
summary = event.summary,
|
||||
description = event.description,
|
||||
startDateTime = event.startDateTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME),
|
||||
endDateTime = event.endDateTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME),
|
||||
location = event.location
|
||||
)
|
||||
call.respond(ApiResponse(success = true, data = response))
|
||||
} else {
|
||||
call.respond(
|
||||
HttpStatusCode.NotFound,
|
||||
ApiResponse<String>(success = false, message = "Событие не найдено")
|
||||
)
|
||||
}
|
||||
},
|
||||
onFailure = { error ->
|
||||
val statusCode = when {
|
||||
error.message?.contains("авторизации") == true -> HttpStatusCode.Unauthorized
|
||||
error.message?.contains("запрещен") == true -> HttpStatusCode.Forbidden
|
||||
error.message?.contains("не найдено") == true -> HttpStatusCode.NotFound
|
||||
else -> HttpStatusCode.InternalServerError
|
||||
}
|
||||
call.respond(statusCode, ApiResponse<String>(success = false, message = error.message))
|
||||
}
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
call.respond(
|
||||
HttpStatusCode.InternalServerError,
|
||||
ApiResponse<String>(success = false, message = "Ошибка получения события: ${e.message}")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Обновление события
|
||||
put("/{uid}") {
|
||||
try {
|
||||
val principal = call.principal<UserIdPrincipal>()!!
|
||||
val username = principal.name
|
||||
val uid = call.parameters["uid"] ?: return@put call.respond(
|
||||
HttpStatusCode.BadRequest,
|
||||
ApiResponse<String>(success = false, message = "UID события не указан")
|
||||
)
|
||||
|
||||
val authHeader = call.request.headers["Authorization"]
|
||||
val password = authHeader?.let { header ->
|
||||
val encoded = header.substringAfter("Basic ")
|
||||
val decoded = java.util.Base64.getDecoder().decode(encoded).toString(Charsets.UTF_8)
|
||||
decoded.substringAfter(":")
|
||||
} ?: ""
|
||||
|
||||
// Получаем календарь ID из заголовка CAL_ID
|
||||
val calendarId = call.request.headers["CAL_ID"] ?: return@put call.respond(
|
||||
HttpStatusCode.BadRequest,
|
||||
ApiResponse<String>(success = false, message = "Заголовок CAL_ID обязателен")
|
||||
)
|
||||
|
||||
val request = call.receive<CreateEventRequest>()
|
||||
|
||||
val event = CalendarEvent(
|
||||
uid = uid,
|
||||
summary = request.summary,
|
||||
description = request.description,
|
||||
startDateTime = LocalDateTime.parse(request.startDateTime),
|
||||
endDateTime = LocalDateTime.parse(request.endDateTime),
|
||||
location = request.location
|
||||
)
|
||||
|
||||
val dav = Dav(username, password, calendarId)
|
||||
val eventManager = dav.getEventManager()
|
||||
|
||||
eventManager.updateEvent(event).fold(
|
||||
onSuccess = { updatedEvent ->
|
||||
val response = EventResponse(
|
||||
uid = updatedEvent.uid,
|
||||
summary = updatedEvent.summary,
|
||||
description = updatedEvent.description,
|
||||
startDateTime = updatedEvent.startDateTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME),
|
||||
endDateTime = updatedEvent.endDateTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME),
|
||||
location = updatedEvent.location
|
||||
)
|
||||
call.respond(ApiResponse(success = true, data = response))
|
||||
},
|
||||
onFailure = { error ->
|
||||
val statusCode = when {
|
||||
error.message?.contains("авторизации") == true -> HttpStatusCode.Unauthorized
|
||||
error.message?.contains("запрещен") == true -> HttpStatusCode.Forbidden
|
||||
error.message?.contains("не найдено") == true -> HttpStatusCode.NotFound
|
||||
error.message?.contains("Конфликт версий") == true -> HttpStatusCode.PreconditionFailed
|
||||
else -> HttpStatusCode.BadRequest
|
||||
}
|
||||
call.respond(statusCode, ApiResponse<String>(success = false, message = error.message))
|
||||
}
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
call.respond(
|
||||
HttpStatusCode.InternalServerError,
|
||||
ApiResponse<String>(success = false, message = "Ошибка обновления события: ${e.message}")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Удаление события
|
||||
delete("/{uid}") {
|
||||
try {
|
||||
val principal = call.principal<UserIdPrincipal>()!!
|
||||
val username = principal.name
|
||||
val uid = call.parameters["uid"] ?: return@delete call.respond(
|
||||
HttpStatusCode.BadRequest,
|
||||
ApiResponse<String>(success = false, message = "UID события не указан")
|
||||
)
|
||||
|
||||
val authHeader = call.request.headers["Authorization"]
|
||||
val password = authHeader?.let { header ->
|
||||
val encoded = header.substringAfter("Basic ")
|
||||
val decoded = java.util.Base64.getDecoder().decode(encoded).toString(Charsets.UTF_8)
|
||||
decoded.substringAfter(":")
|
||||
} ?: ""
|
||||
|
||||
// Получаем календарь ID из заголовка CAL_ID
|
||||
val calendarId = call.request.headers["CAL_ID"] ?: return@delete call.respond(
|
||||
HttpStatusCode.BadRequest,
|
||||
ApiResponse<String>(success = false, message = "Заголовок CAL_ID обязателен")
|
||||
)
|
||||
|
||||
val dav = Dav(username, password, calendarId)
|
||||
val eventManager = dav.getEventManager()
|
||||
|
||||
eventManager.deleteEvent(uid).fold(
|
||||
onSuccess = {
|
||||
call.respond(ApiResponse<String>(success = true, message = "Событие успешно удалено"))
|
||||
},
|
||||
onFailure = { error ->
|
||||
val statusCode = when {
|
||||
error.message?.contains("авторизации") == true -> HttpStatusCode.Unauthorized
|
||||
error.message?.contains("запрещен") == true -> HttpStatusCode.Forbidden
|
||||
error.message?.contains("не найдено") == true -> HttpStatusCode.NotFound
|
||||
else -> HttpStatusCode.BadRequest
|
||||
}
|
||||
call.respond(statusCode, ApiResponse<String>(success = false, message = error.message))
|
||||
}
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
call.respond(
|
||||
HttpStatusCode.InternalServerError,
|
||||
ApiResponse<String>(success = false, message = "Ошибка удаления события: ${e.message}")
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user