diff --git a/src/main/kotlin/su/coolpeople/model/Profile.kt b/src/main/kotlin/su/coolpeople/model/Profile.kt deleted file mode 100644 index 3f53f2a..0000000 --- a/src/main/kotlin/su/coolpeople/model/Profile.kt +++ /dev/null @@ -1,17 +0,0 @@ -package su.coolpeople.model - -import kotlinx.serialization.Serializable - -// https://wiki.dc09.ru/doku.php?id=wiki:coolpeople:dev:backend#анкета -@Serializable -data class Profile( - val name: String, - val age: UInt, - val lat: Float, - val lon: Float, - val city: String, - val approx_loc: Boolean, - val desc: String, - val tags: Array, // TODO: maybe MutableList - val contacts: Array, // TODO: same -) diff --git a/src/main/kotlin/su/coolpeople/model/ProfileRepository.kt b/src/main/kotlin/su/coolpeople/model/ProfileRepository.kt deleted file mode 100644 index 3fbe2ec..0000000 --- a/src/main/kotlin/su/coolpeople/model/ProfileRepository.kt +++ /dev/null @@ -1,22 +0,0 @@ -package su.coolpeople.model - -object ProfileRepository { - // TODO: Meilisearch - private val profiles = hashMapOf( - 0u to Profile( - "name", 20u, - 60f, 49f, "city", true, - "description", - arrayOf("programming", "music"), - arrayOf("t.me/example"), - ), - ) - - fun get(id: UInt): Profile? { - return profiles.get(id) - } - - fun delete(id: UInt): Boolean { - return profiles.remove(id) != null - } -} diff --git a/src/main/kotlin/su/coolpeople/models/Contact.kt b/src/main/kotlin/su/coolpeople/models/Contact.kt new file mode 100644 index 0000000..29831fb --- /dev/null +++ b/src/main/kotlin/su/coolpeople/models/Contact.kt @@ -0,0 +1,23 @@ +package su.coolpeople.models + +import kotlinx.serialization.Serializable +import su.coolpeople.models.enums.ContactType + +/** + * Контакт пользователя, который он может указать в своей анкете + * + * @property type соц. сеть от которой это контакт. Например "telegram" + * @property id id пользователя в соответствующей соц. сети. Может быть сериализованным числом + * @property link ссылка на социальную сеть пользователя в виде URL + */ +@Serializable +data class Contact( + val type: ContactType, + val id: String, +) { + val link: String + get() = when(type) { + ContactType.TELEGRAM -> "tg://user?id=929365483" + else -> throw NotImplementedError("Contact type $type does not have link implementation") + } +} diff --git a/src/main/kotlin/su/coolpeople/models/Profile.kt b/src/main/kotlin/su/coolpeople/models/Profile.kt new file mode 100644 index 0000000..9a1bbbd --- /dev/null +++ b/src/main/kotlin/su/coolpeople/models/Profile.kt @@ -0,0 +1,37 @@ +package su.coolpeople.models + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import su.coolpeople.models.geo.ApproximateLocation + +/** + * Анкета пользователя сервиса + * [wiki](https://wiki.dc09.ru/doku.php?id=wiki:coolpeople:dev:backend#%D0%B0%D0%BD%D0%BA%D0%B5%D1%82%D0%B0) + * + * @property id анутренний id пользователя + * @property name видимое другим имя пользователя + * @property age реальный возраст пользователя + * @property location местоположение пользователя. Точные коорд + * @property description описание анкеты пользователя TODO: про проверку на XSS не забываем!! + * @property tags список тегов по которым и идет матчинг анкет + * @property contacts список привязанных к анкете + */ +@Serializable +data class Profile( + val id: UInt, + val name: String, + val age: Int, + val location: ApproximateLocation, + val description: String, + val tags: List = listOf(), + val contacts: List = listOf(), +) { + suspend fun addTag(tag: String): Profile { + // data-классы как и модели по умолчанию иммутабельны. Поэтому изменяем методом и создаем копию + TODO("Реализовать когда настроим базу данных. Должно обновлять запись в бд и возвращать новый экземпляр через .copy()") + } + + suspend fun removeTag(tag: String): Profile { + TODO("Реализовать когда настроим базу данных. Должно обновлять запись в бд и возвращать новый экземпляр через .copy()") + } +} diff --git a/src/main/kotlin/su/coolpeople/models/ProfileRepository.kt b/src/main/kotlin/su/coolpeople/models/ProfileRepository.kt new file mode 100644 index 0000000..ed63be4 --- /dev/null +++ b/src/main/kotlin/su/coolpeople/models/ProfileRepository.kt @@ -0,0 +1,30 @@ +package su.coolpeople.models + +import su.coolpeople.models.enums.ContactType +import su.coolpeople.models.geo.ApproximateLocation + +object ProfileRepository { + // TODO: Meilisearch + private val profiles = mutableMapOf( + 0U to Profile( + 0U, + "Name", + 20, + ApproximateLocation.fromCity("City"), + "im super good and interesting people", + tags = listOf("coding", "music"), + contacts = listOf( + Contact(ContactType.TELEGRAM, "1234567890"), + ), + ), + ) + + fun get(id: UInt): Profile? { + return profiles[id] + } + + /* TODO: а точно ли нужно удалять аккаунты? */ + fun delete(id: UInt): Boolean { + return profiles.remove(id) != null + } +} diff --git a/src/main/kotlin/su/coolpeople/models/enums/ContactType.kt b/src/main/kotlin/su/coolpeople/models/enums/ContactType.kt new file mode 100644 index 0000000..e6a1f17 --- /dev/null +++ b/src/main/kotlin/su/coolpeople/models/enums/ContactType.kt @@ -0,0 +1,9 @@ +package su.coolpeople.models.enums + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +enum class ContactType { + @SerialName("telegram") TELEGRAM +} \ No newline at end of file diff --git a/src/main/kotlin/su/coolpeople/models/geo/ApproximateLocation.kt b/src/main/kotlin/su/coolpeople/models/geo/ApproximateLocation.kt new file mode 100644 index 0000000..a4c598c --- /dev/null +++ b/src/main/kotlin/su/coolpeople/models/geo/ApproximateLocation.kt @@ -0,0 +1,28 @@ +package su.coolpeople.models.geo + +import kotlinx.serialization.Serializable + + +/** + * Возможно, приблизительное местоположение + * + * @property accurate является ли местоположение точным + * @property location точное местоположение. Не null только если accurate == true + * @property city город, в котором находится пользователь. Не null только если accurate == false + */ +@Serializable +data class ApproximateLocation ( + val accurate: Boolean, + val location: Location? = null, + val city: String? = null, +) { + companion object { + fun fromLocation(location: Location): ApproximateLocation { + return ApproximateLocation(true, location) + } + + fun fromCity(city: String): ApproximateLocation { + return ApproximateLocation(false, city = city) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/su/coolpeople/models/geo/Location.kt b/src/main/kotlin/su/coolpeople/models/geo/Location.kt new file mode 100644 index 0000000..4d4df06 --- /dev/null +++ b/src/main/kotlin/su/coolpeople/models/geo/Location.kt @@ -0,0 +1,13 @@ +package su.coolpeople.models.geo + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * Представление точного местоположения + */ +@Serializable +data class Location( + @SerialName("lat") val latitude: Float, + @SerialName("lon") val longitude: Float, +) \ No newline at end of file diff --git a/src/main/kotlin/su/coolpeople/plugins/Routing.kt b/src/main/kotlin/su/coolpeople/plugins/Routing.kt index 1d9ce52..dd6916b 100644 --- a/src/main/kotlin/su/coolpeople/plugins/Routing.kt +++ b/src/main/kotlin/su/coolpeople/plugins/Routing.kt @@ -5,37 +5,11 @@ import io.ktor.server.response.* import io.ktor.server.routing.* import io.ktor.server.plugins.BadRequestException import io.ktor.http.HttpStatusCode -import su.coolpeople.model.ProfileRepository -import kotlin.text.toUInt +import su.coolpeople.models.ProfileRepository +import su.coolpeople.routes.profile fun Application.configureRouting() { routing { - route("/api/profile") { - put { - TODO() - } - - get("/{id}") { - val id = call.parameters["id"]?.toUIntOrNull() ?: throw BadRequestException("Invalid ID") - - val profile = ProfileRepository.get(id) - if (profile == null) { - call.respond(HttpStatusCode.NotFound) - return@get - } - - call.respond(profile) - } - - delete("/{id}") { - val id = call.parameters["id"]?.toUIntOrNull() ?: throw BadRequestException("Invalid ID") - - if (!ProfileRepository.delete(id)) { - call.respond(HttpStatusCode.NotFound) - } else { - call.respond(HttpStatusCode.OK) - } - } - } + profile() } } diff --git a/src/main/kotlin/su/coolpeople/plugins/Serialization.kt b/src/main/kotlin/su/coolpeople/plugins/Serialization.kt index 18b952b..1481e1c 100644 --- a/src/main/kotlin/su/coolpeople/plugins/Serialization.kt +++ b/src/main/kotlin/su/coolpeople/plugins/Serialization.kt @@ -5,9 +5,14 @@ import io.ktor.server.application.* import io.ktor.server.plugins.contentnegotiation.* import io.ktor.server.response.* import io.ktor.server.routing.* +import kotlinx.serialization.json.Json fun Application.configureSerialization() { install(ContentNegotiation) { - json() + val tolerantJson = Json { + ignoreUnknownKeys = true // Это проблемы с обратной совместимостью в будующем может решить + } + + json(json = tolerantJson) } } diff --git a/src/main/kotlin/su/coolpeople/routes/Profile.kt b/src/main/kotlin/su/coolpeople/routes/Profile.kt new file mode 100644 index 0000000..3caf4e9 --- /dev/null +++ b/src/main/kotlin/su/coolpeople/routes/Profile.kt @@ -0,0 +1,33 @@ +package su.coolpeople.routes + +import io.ktor.http.* +import io.ktor.server.application.* +import io.ktor.server.plugins.* +import io.ktor.server.response.* +import io.ktor.server.routing.* +import su.coolpeople.models.ProfileRepository + +fun Routing.profile() { + route("/api/profile") { + put { + TODO() + } + + get("/{id}") { + val id = call.parameters["id"]?.toUIntOrNull() ?: throw BadRequestException("Malformed profile id") + val profile = ProfileRepository.get(id) ?: throw NotFoundException("Profile not found") + + call.respond(profile) + } + + delete("/{id}") { + val id = call.parameters["id"]?.toUIntOrNull() ?: throw BadRequestException("Malformed profile id") + + if (!ProfileRepository.delete(id)) { + call.respond(HttpStatusCode.NotFound) + } else { + call.respond(HttpStatusCode.OK) + } + } + } +} \ No newline at end of file