Skip to content

Commit 4fe988e

Browse files
authored
#65 NOT WORKING attempt to implement GET /people (#84)
Signed-off-by: vityaman <vityaman.dev@yandex.ru>
1 parent 0441685 commit 4fe988e

File tree

15 files changed

+328
-118
lines changed

15 files changed

+328
-118
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package ru.ifmo.se.dating.collection
2+
3+
import java.util.*
4+
import kotlin.enums.enumEntries
5+
6+
interface BiMap<T, U> {
7+
fun toRight(value: T): U
8+
fun toLeft(value: U): T
9+
10+
companion object {
11+
inline fun <reified K : Enum<K>, V> from(
12+
vararg pairs: Pair<K, V>,
13+
): ExhaustiveMap<K, V> = ExhaustiveMap.from(pairs.asList())
14+
15+
inline fun <reified T : Enum<T>, reified U : Enum<U>> from(
16+
pairs: List<Pair<T, U>>,
17+
): BiMap<T, U> = object : BiMap<T, U> {
18+
private val rhs = pairs
19+
.associateTo(EnumMap(T::class.java)) { (l, r) -> l to r }
20+
21+
private val lhs = pairs
22+
.associateTo(EnumMap(U::class.java)) { (l, r) -> r to l }
23+
24+
init {
25+
val left = pairs.map { it.first }.toSet()
26+
val right = pairs.map { it.second }.toSet()
27+
require(left.size == right.size)
28+
require(enumEntries<T>().size == left.size)
29+
}
30+
31+
override fun toRight(value: T): U = rhs[value]!!
32+
33+
override fun toLeft(value: U): T = lhs[value]!!
34+
}
35+
36+
@JvmName("BiMapLeftToRight")
37+
operator fun <T, U> BiMap<T, U>.invoke(value: T): U =
38+
this.toRight(value)
39+
40+
@JvmName("BiMapRightToLeft")
41+
operator fun <T, U> BiMap<T, U>.invoke(value: U): T =
42+
this.toLeft(value)
43+
}
44+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package ru.ifmo.se.dating.collection
2+
3+
import java.util.*
4+
import kotlin.enums.enumEntries
5+
6+
interface ExhaustiveMap<K, V> : Map<K, V> {
7+
override operator fun get(key: K): V
8+
9+
companion object {
10+
inline fun <reified K : Enum<K>, V> from(
11+
vararg pairs: Pair<K, V>,
12+
): ExhaustiveMap<K, V> = from(pairs.asList())
13+
14+
inline fun <reified K : Enum<K>, V> from(
15+
pairs: List<Pair<K, V>>,
16+
): ExhaustiveMap<K, V> = object : ExhaustiveMap<K, V> {
17+
private val origin = pairs
18+
.associateTo(EnumMap(K::class.java)) { (l, r) -> l to r }
19+
20+
init {
21+
val keys = pairs.map { it.first }
22+
require(keys.distinct().size == keys.size)
23+
require(enumEntries<K>().size == keys.size)
24+
}
25+
26+
override val entries: Set<Map.Entry<K, V>>
27+
get() = origin.entries
28+
29+
override val keys: Set<K>
30+
get() = origin.keys
31+
32+
override val size: Int
33+
get() = origin.size
34+
35+
override val values: Collection<V>
36+
get() = origin.values
37+
38+
override operator fun get(key: K): V = origin[key]!!
39+
40+
override fun isEmpty(): Boolean =
41+
origin.isEmpty()
42+
43+
override fun containsValue(value: V): Boolean =
44+
origin.containsValue(value)
45+
46+
override fun containsKey(key: K): Boolean =
47+
origin.containsKey(key)
48+
}
49+
50+
inline fun <reified K : Enum<K>, V> from(
51+
mapping: (K) -> V,
52+
): ExhaustiveMap<K, V> {
53+
val entries = enumEntries<K>()
54+
return Array(entries.size) {
55+
entries[it] to mapping(entries[it])
56+
}.let { from(pairs = it) }
57+
}
58+
}
59+
}

backend/foundation/src/main/resources/application-foundation.yml

+3
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,8 @@ logging:
2929
level:
3030
web: INFO
3131
liquibase: WARN
32+
r2dbc: DEBUG
33+
group:
34+
r2dbc: org.springframework.r2dbc,org.springframework.data.r2dbc,org.jooq.tools.LoggerListener
3235
pattern:
3336
console: "%clr(%d{yyyy-MM-dd'T'HH:mm:ss.SSS}){faint} %clr([%level]) %clr(%logger{36}){blue}: %msg%n"

backend/gateway/src/main/resources/application.yml

+6
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,12 @@ spring:
6161
- Method=GET
6262
- Path=/api/people/{person_id}/matches
6363

64+
- id: get-people
65+
uri: lb://people
66+
predicates:
67+
- Method=GET
68+
- Path=/api/people
69+
6470
- id: get-people-person-id
6571
uri: lb://people
6672
predicates:

backend/people/src/main/kotlin/ru/ifmo/se/dating/people/api/HttpPeopleApi.kt

+37-22
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import org.springframework.core.io.Resource
77
import org.springframework.http.ResponseEntity
88
import org.springframework.stereotype.Controller
99
import ru.ifmo.se.dating.exception.AuthorizationException
10+
import ru.ifmo.se.dating.exception.InvalidValueException
1011
import ru.ifmo.se.dating.exception.orThrowNotFound
1112
import ru.ifmo.se.dating.pagging.Page
1213
import ru.ifmo.se.dating.people.api.generated.PeopleApiDelegate
@@ -15,10 +16,7 @@ import ru.ifmo.se.dating.people.api.mapping.toModel
1516
import ru.ifmo.se.dating.people.logic.InterestService
1617
import ru.ifmo.se.dating.people.logic.PersonService
1718
import ru.ifmo.se.dating.people.logic.PictureService
18-
import ru.ifmo.se.dating.people.model.Faculty
19-
import ru.ifmo.se.dating.people.model.Person
20-
import ru.ifmo.se.dating.people.model.Picture
21-
import ru.ifmo.se.dating.people.model.Topic
19+
import ru.ifmo.se.dating.people.model.*
2220
import ru.ifmo.se.dating.people.model.generated.*
2321
import ru.ifmo.se.dating.security.auth.User
2422
import ru.ifmo.se.dating.spring.security.auth.SpringSecurityContext
@@ -32,6 +30,7 @@ class HttpPeopleApi(
3230
private val interestService: InterestService,
3331
private val pictureService: PictureService,
3432
) : PeopleApiDelegate {
33+
@Suppress("LongMethod", "CyclomaticComplexMethod", "MagicNumber")
3534
override fun peopleGet(
3635
offset: Long,
3736
limit: Long,
@@ -44,39 +43,55 @@ class HttpPeopleApi(
4443
heightMax: Int?,
4544
birthdayMin: LocalDate?,
4645
birthdayMax: LocalDate?,
47-
zodiac: List<ZodiacSignMessage>?,
48-
faculty: List<Long>?,
46+
zodiac: ZodiacSignMessage?,
47+
facultyId: Long?,
4948
latitude: Double?,
5049
longitude: Double?,
5150
radius: Int?,
5251
updatedMin: OffsetDateTime?,
5352
updatedMax: OffsetDateTime?,
5453
sortBy: List<PersonSortingKeyMessage>?,
5554
): ResponseEntity<Flow<PersonMessage>> {
56-
if (listOfNotNull(
57-
picturesCountMin,
58-
picturesCountMax,
59-
topicId,
60-
zodiac,
61-
latitude,
62-
longitude,
63-
radius,
64-
updatedMin,
65-
updatedMax,
66-
sortBy
67-
).isNotEmpty()
68-
) {
55+
if (!sortBy.isNullOrEmpty()) {
6956
TODO("Unsupported GET /people query parameter was provided")
7057
}
7158

59+
val area = when (listOfNotNull(latitude, longitude, radius).size) {
60+
0 -> {
61+
null
62+
}
63+
64+
3 -> {
65+
Area(
66+
center = Coordinates(
67+
latitude = latitude!!,
68+
longitude = longitude!!
69+
),
70+
radius = radius!!.toDouble(),
71+
)
72+
}
73+
74+
else -> {
75+
buildString {
76+
append("Expected a complete area, but got ")
77+
append("(latitude: $latitude, longitude: $longitude, radius: $radius)")
78+
}.let { throw InvalidValueException(it) }
79+
}
80+
}
81+
7282
return personService.getFiltered(
7383
Page(offset = offset.toInt(), limit = limit.toInt()),
7484
PersonService.Filter(
75-
firstName = firstName?.let { Regex(it) } ?: Regex(".*"),
76-
lastName = lastName?.let { Regex(it) } ?: Regex(".*"),
85+
firstName = firstName?.let { Regex(it) },
86+
lastName = lastName?.let { Regex(it) },
7787
height = (heightMin ?: Int.MIN_VALUE)..(heightMax ?: Int.MAX_VALUE),
7888
birthday = (birthdayMin ?: LocalDate.MIN)..(birthdayMax ?: LocalDate.MAX),
79-
faculty = (faculty ?: listOf()).map { Faculty.Id(it.toInt()) }.toSet(),
89+
facultyId = facultyId?.let { Faculty.Id(it.toInt()) },
90+
updated = (updatedMin ?: OffsetDateTime.MIN)..(updatedMax ?: OffsetDateTime.MAX),
91+
area = area,
92+
picturesCount = (picturesCountMin ?: 0)..(picturesCountMax ?: Int.MAX_VALUE),
93+
zodiac = zodiac?.toModel(),
94+
topicIds = topicId?.map { Topic.Id(it.toInt()) }?.toSet() ?: emptySet(),
8095
),
8196
).map { it.toMessage() }.let { ResponseEntity.ok(it) }
8297
}

backend/people/src/main/kotlin/ru/ifmo/se/dating/people/api/mapping/PersonMapping.kt

+1-16
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ fun Person.toMessage() = PersonMessage(
2828
height = height,
2929
birthday = birthday,
3030
facultyId = facultyId.number.toLong(),
31-
locationId = locationId.number.toLong(),
31+
locationId = location.id.number.toLong(),
3232
interests = interests.map { it.toMessage() }.toSet(),
3333
zodiac = zodiac.toMessage(),
3434
pictures = pictureIds.map { PictureMessage(id = it.number.toLong()) }.toSet()
@@ -58,18 +58,3 @@ fun Person.Interest.toMessage(): InterestMessage =
5858
topicId = topicId.number.toLong(),
5959
level = InterestLevelMessage.forValue(degree.toString()),
6060
)
61-
62-
fun Person.Zodiac.toMessage(): ZodiacSignMessage = when (this) {
63-
Person.Zodiac.ARIES -> ZodiacSignMessage.aries
64-
Person.Zodiac.TAURUS -> ZodiacSignMessage.taurus
65-
Person.Zodiac.GEMINI -> ZodiacSignMessage.gemini
66-
Person.Zodiac.CANCER -> ZodiacSignMessage.cancer
67-
Person.Zodiac.LEO -> ZodiacSignMessage.leo
68-
Person.Zodiac.VIRGO -> ZodiacSignMessage.virgo
69-
Person.Zodiac.LIBRA -> ZodiacSignMessage.libra
70-
Person.Zodiac.SCORPIO -> ZodiacSignMessage.scorpio
71-
Person.Zodiac.SAGITTARIUS -> ZodiacSignMessage.sagittarius
72-
Person.Zodiac.CAPRICORN -> ZodiacSignMessage.capricorn
73-
Person.Zodiac.AQUARIUS -> ZodiacSignMessage.aquarius
74-
Person.Zodiac.PISCES -> ZodiacSignMessage.pisces
75-
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package ru.ifmo.se.dating.people.api.mapping
2+
3+
import ru.ifmo.se.dating.collection.BiMap
4+
import ru.ifmo.se.dating.collection.BiMap.Companion.invoke
5+
import ru.ifmo.se.dating.collection.ExhaustiveMap
6+
import ru.ifmo.se.dating.people.model.Person
7+
import ru.ifmo.se.dating.people.model.generated.ZodiacSignMessage
8+
9+
val zodiacMap = ExhaustiveMap.from<Person.Zodiac, ZodiacSignMessage> {
10+
when (it) {
11+
Person.Zodiac.ARIES -> ZodiacSignMessage.aries
12+
Person.Zodiac.TAURUS -> ZodiacSignMessage.taurus
13+
Person.Zodiac.GEMINI -> ZodiacSignMessage.gemini
14+
Person.Zodiac.CANCER -> ZodiacSignMessage.cancer
15+
Person.Zodiac.LEO -> ZodiacSignMessage.leo
16+
Person.Zodiac.VIRGO -> ZodiacSignMessage.virgo
17+
Person.Zodiac.LIBRA -> ZodiacSignMessage.libra
18+
Person.Zodiac.SCORPIO -> ZodiacSignMessage.scorpio
19+
Person.Zodiac.SAGITTARIUS -> ZodiacSignMessage.sagittarius
20+
Person.Zodiac.CAPRICORN -> ZodiacSignMessage.capricorn
21+
Person.Zodiac.AQUARIUS -> ZodiacSignMessage.aquarius
22+
Person.Zodiac.PISCES -> ZodiacSignMessage.pisces
23+
}
24+
}.let { BiMap.from(it.entries.map { (k, v) -> k to v }.toList()) }
25+
26+
fun Person.Zodiac.toMessage(): ZodiacSignMessage = zodiacMap(this)
27+
28+
fun ZodiacSignMessage.toModel(): Person.Zodiac = zodiacMap(this)

backend/people/src/main/kotlin/ru/ifmo/se/dating/people/logic/PersonService.kt

+10-6
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@ package ru.ifmo.se.dating.people.logic
22

33
import kotlinx.coroutines.flow.Flow
44
import ru.ifmo.se.dating.pagging.Page
5-
import ru.ifmo.se.dating.people.model.Faculty
6-
import ru.ifmo.se.dating.people.model.Person
7-
import ru.ifmo.se.dating.people.model.PersonVariant
5+
import ru.ifmo.se.dating.people.model.*
86
import ru.ifmo.se.dating.security.auth.User
97
import java.time.LocalDate
8+
import java.time.OffsetDateTime
109

1110
interface PersonService {
1211
suspend fun edit(draft: Person.Draft)
@@ -17,10 +16,15 @@ interface PersonService {
1716
fun getFiltered(page: Page, filter: Filter): Flow<Person>
1817

1918
data class Filter(
20-
val firstName: Regex,
21-
val lastName: Regex,
19+
val firstName: Regex?,
20+
val lastName: Regex?,
2221
val height: IntRange,
2322
val birthday: ClosedRange<LocalDate>,
24-
val faculty: Set<Faculty.Id>,
23+
val facultyId: Faculty.Id?,
24+
val updated: ClosedRange<OffsetDateTime>,
25+
val area: Area?,
26+
val picturesCount: ClosedRange<Int>,
27+
val zodiac: Person.Zodiac?,
28+
val topicIds: Set<Topic.Id>,
2529
)
2630
}

backend/people/src/main/kotlin/ru/ifmo/se/dating/people/logic/basic/BasicPersonService.kt

+1-11
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,6 @@ package ru.ifmo.se.dating.people.logic.basic
33
import kotlinx.coroutines.CoroutineScope
44
import kotlinx.coroutines.Dispatchers
55
import kotlinx.coroutines.flow.Flow
6-
import kotlinx.coroutines.flow.drop
7-
import kotlinx.coroutines.flow.filter
8-
import kotlinx.coroutines.flow.take
96
import kotlinx.coroutines.launch
107
import ru.ifmo.se.dating.exception.ConflictException
118
import ru.ifmo.se.dating.exception.InvalidValueException
@@ -72,14 +69,7 @@ class BasicPersonService(
7269
}.let { background.launch { outbox.process(id) } }.let { }
7370

7471
override fun getFiltered(page: Page, filter: PersonService.Filter): Flow<Person> =
75-
storage.selectAllReady()
76-
.drop(page.offset)
77-
.take(page.limit)
78-
.filter { filter.firstName.matches(it.firstName.text) }
79-
.filter { filter.lastName.matches(it.lastName.text) }
80-
.filter { filter.height.contains(it.height) }
81-
.filter { filter.birthday.contains(it.birthday) }
82-
.filter { filter.faculty.isEmpty() || filter.faculty.contains(it.facultyId) }
72+
storage.selectFilteredReady(page, filter)
8373

8474
@Suppress("ThrowsCount")
8575
private fun validate(draft: Person.Draft) {
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,26 @@
11
package ru.ifmo.se.dating.people.model
22

3-
data class Area(
3+
import kotlin.math.cos
4+
import kotlin.math.sqrt
5+
6+
class Area(
47
val center: Coordinates,
58
val radius: Double,
6-
)
9+
) {
10+
fun contains(coordinates: Coordinates): Boolean {
11+
val lat1Rad = Math.toRadians(center.latitude)
12+
val lat2Rad = Math.toRadians(coordinates.latitude)
13+
val lon1Rad = Math.toRadians(center.longitude)
14+
val lon2Rad = Math.toRadians(coordinates.longitude)
15+
16+
val x = (lon2Rad - lon1Rad) * cos((lat1Rad + lat2Rad) / 2)
17+
val y = lat2Rad - lat1Rad
18+
val distance: Double = sqrt(x * x + y * y) * EARTH_RADIUS
19+
20+
return distance <= radius
21+
}
22+
23+
companion object {
24+
const val EARTH_RADIUS = 6371
25+
}
26+
}

0 commit comments

Comments
 (0)