refactor: full refactoring of project

Location and contact allocated to classes
KDoc added
Routes allocated to separate package
This commit is contained in:
lavafrai 2024-06-21 18:00:46 +03:00
parent 591cd28eb4
commit e6036e2f22
11 changed files with 182 additions and 69 deletions

View file

@ -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<String>, // TODO: maybe MutableList<String>
val contacts: Array<String>, // TODO: same
)

View file

@ -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
}
}

View file

@ -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")
}
}

View file

@ -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<String> = listOf(),
val contacts: List<Contact> = listOf(),
) {
suspend fun addTag(tag: String): Profile {
// data-классы как и модели по умолчанию иммутабельны. Поэтому изменяем методом и создаем копию
TODO("Реализовать когда настроим базу данных. Должно обновлять запись в бд и возвращать новый экземпляр через .copy()")
}
suspend fun removeTag(tag: String): Profile {
TODO("Реализовать когда настроим базу данных. Должно обновлять запись в бд и возвращать новый экземпляр через .copy()")
}
}

View file

@ -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
}
}

View file

@ -0,0 +1,9 @@
package su.coolpeople.models.enums
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
enum class ContactType {
@SerialName("telegram") TELEGRAM
}

View file

@ -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)
}
}
}

View file

@ -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,
)

View file

@ -5,37 +5,11 @@ import io.ktor.server.response.*
import io.ktor.server.routing.* import io.ktor.server.routing.*
import io.ktor.server.plugins.BadRequestException import io.ktor.server.plugins.BadRequestException
import io.ktor.http.HttpStatusCode import io.ktor.http.HttpStatusCode
import su.coolpeople.model.ProfileRepository import su.coolpeople.models.ProfileRepository
import kotlin.text.toUInt import su.coolpeople.routes.profile
fun Application.configureRouting() { fun Application.configureRouting() {
routing { routing {
route("/api/profile") { 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)
}
}
}
} }
} }

View file

@ -5,9 +5,14 @@ import io.ktor.server.application.*
import io.ktor.server.plugins.contentnegotiation.* import io.ktor.server.plugins.contentnegotiation.*
import io.ktor.server.response.* import io.ktor.server.response.*
import io.ktor.server.routing.* import io.ktor.server.routing.*
import kotlinx.serialization.json.Json
fun Application.configureSerialization() { fun Application.configureSerialization() {
install(ContentNegotiation) { install(ContentNegotiation) {
json() val tolerantJson = Json {
ignoreUnknownKeys = true // Это проблемы с обратной совместимостью в будующем может решить
}
json(json = tolerantJson)
} }
} }

View file

@ -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)
}
}
}
}