Skip to content

Commit e857993

Browse files
authored
#82 Set person interests for topics (#83)
Signed-off-by: vityaman <vityaman.dev@yandex.ru>
1 parent 68e93ce commit e857993

19 files changed

+504
-21
lines changed

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

+18
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,24 @@ spring:
9797
- Method=DELETE
9898
- Path=/api/people/*/photos/*
9999

100+
- id: put-people-person-id-interests-topic-id
101+
uri: lb://people
102+
predicates:
103+
- Method=PUT
104+
- Path=/api/people/*/interests/*
105+
106+
- id: delete-people-person-id-interests-topic-id
107+
uri: lb://people
108+
predicates:
109+
- Method=DELETE
110+
- Path=/api/people/*/interests/*
111+
112+
- id: get-people-topics
113+
uri: lb://people
114+
predicates:
115+
- Method=GET
116+
- Path=/api/topics
117+
100118
- id: get-people-faculties
101119
uri: lb://people
102120
predicates:

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

+28
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,24 @@ import ru.ifmo.se.dating.pagging.Page
1212
import ru.ifmo.se.dating.people.api.generated.PeopleApiDelegate
1313
import ru.ifmo.se.dating.people.api.mapping.toMessage
1414
import ru.ifmo.se.dating.people.api.mapping.toModel
15+
import ru.ifmo.se.dating.people.logic.InterestService
1516
import ru.ifmo.se.dating.people.logic.PersonService
1617
import ru.ifmo.se.dating.people.logic.PictureService
1718
import ru.ifmo.se.dating.people.model.Faculty
19+
import ru.ifmo.se.dating.people.model.Person
1820
import ru.ifmo.se.dating.people.model.Picture
21+
import ru.ifmo.se.dating.people.model.Topic
1922
import ru.ifmo.se.dating.people.model.generated.*
2023
import ru.ifmo.se.dating.security.auth.User
2124
import ru.ifmo.se.dating.spring.security.auth.SpringSecurityContext
2225
import java.time.LocalDate
2326
import java.time.OffsetDateTime
2427

28+
@Suppress("TooManyFunctions")
2529
@Controller
2630
class HttpPeopleApi(
2731
private val personService: PersonService,
32+
private val interestService: InterestService,
2833
private val pictureService: PictureService,
2934
) : PeopleApiDelegate {
3035
override fun peopleGet(
@@ -109,6 +114,29 @@ class HttpPeopleApi(
109114
return ResponseEntity.ok(Unit)
110115
}
111116

117+
override suspend fun peoplePersonIdInterestsTopicIdDelete(
118+
personId: Long,
119+
topicId: Long,
120+
): ResponseEntity<Unit> {
121+
interestService.remove(User.Id(personId.toInt()), Topic.Id(topicId.toInt()))
122+
return ResponseEntity.ok(Unit)
123+
}
124+
125+
override suspend fun peoplePersonIdInterestsTopicIdPut(
126+
personId: Long,
127+
topicId: Long,
128+
interestPatchMessage: InterestPatchMessage,
129+
): ResponseEntity<Unit> {
130+
interestService.insert(
131+
id = User.Id(personId.toInt()),
132+
interest = Person.Interest(
133+
topicId = Topic.Id(topicId.toInt()),
134+
degree = interestPatchMessage.level.value.toInt(),
135+
)
136+
)
137+
return ResponseEntity.ok(Unit)
138+
}
139+
112140
override suspend fun peoplePersonIdPhotosPost(
113141
personId: Long,
114142
body: Resource?,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package ru.ifmo.se.dating.people.api
2+
3+
import kotlinx.coroutines.flow.Flow
4+
import kotlinx.coroutines.flow.map
5+
import org.springframework.http.ResponseEntity
6+
import org.springframework.stereotype.Controller
7+
import ru.ifmo.se.dating.people.api.generated.TopicsApiDelegate
8+
import ru.ifmo.se.dating.people.api.mapping.toMessage
9+
import ru.ifmo.se.dating.people.logic.InterestService
10+
import ru.ifmo.se.dating.people.model.generated.TopicMessage
11+
12+
@Controller
13+
class HttpTopicsApi(
14+
private val interestService: InterestService,
15+
) : TopicsApiDelegate {
16+
override fun topicsGet(): ResponseEntity<Flow<TopicMessage>> =
17+
interestService.getAllTopics()
18+
.map { it.toMessage() }
19+
.let { ResponseEntity.ok(it) }
20+
}

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

+16-6
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
package ru.ifmo.se.dating.people.api.mapping
22

3-
import ru.ifmo.se.dating.people.model.Faculty
4-
import ru.ifmo.se.dating.people.model.Location
5-
import ru.ifmo.se.dating.people.model.Person
6-
import ru.ifmo.se.dating.people.model.PersonVariant
3+
import ru.ifmo.se.dating.people.model.*
74
import ru.ifmo.se.dating.people.model.generated.*
85
import ru.ifmo.se.dating.security.auth.User
96

@@ -13,6 +10,7 @@ fun PersonPatchMessage.toModel(id: Int) = Person.Draft(
1310
lastName = lastName?.let { Person.Name(it) },
1411
height = height,
1512
birthday = birthday,
13+
interests = emptySet(),
1614
facultyId = facultyId?.let { Faculty.Id(it.toInt()) },
1715
locationId = locationId?.let { Location.Id(it.toInt()) },
1816
)
@@ -31,7 +29,7 @@ fun Person.toMessage() = PersonMessage(
3129
birthday = birthday,
3230
facultyId = facultyId.number.toLong(),
3331
locationId = locationId.number.toLong(),
34-
interests = emptySet(),
32+
interests = interests.map { it.toMessage() }.toSet(),
3533
zodiac = ZodiacSignMessage.leo,
3634
pictures = pictureIds.map { PictureMessage(id = it.number.toLong()) }.toSet()
3735
)
@@ -45,6 +43,18 @@ fun Person.Draft.toMessage() = PersonDraftMessage(
4543
birthday = birthday,
4644
facultyId = facultyId?.number?.toLong(),
4745
locationId = locationId?.number?.toLong(),
48-
interests = emptySet(),
46+
interests = interests.map { it.toMessage() }.toSet(),
4947
pictures = pictureIds.map { PictureMessage(id = it.number.toLong()) }.toSet()
5048
)
49+
50+
fun InterestMessage.toModel(): Person.Interest =
51+
Person.Interest(
52+
topicId = Topic.Id(topicId.toInt()),
53+
degree = level.value.toInt(),
54+
)
55+
56+
fun Person.Interest.toMessage(): InterestMessage =
57+
InterestMessage(
58+
topicId = topicId.number.toLong(),
59+
level = InterestLevelMessage.forValue(degree.toString()),
60+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package ru.ifmo.se.dating.people.api.mapping
2+
3+
import ru.ifmo.se.dating.people.model.Topic
4+
import ru.ifmo.se.dating.people.model.generated.TopicMessage
5+
6+
fun Topic.toMessage(): TopicMessage =
7+
TopicMessage(
8+
id = id.number.toLong(),
9+
name = name,
10+
color = color.hex,
11+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package ru.ifmo.se.dating.people.logic
2+
3+
import kotlinx.coroutines.flow.Flow
4+
import ru.ifmo.se.dating.people.model.Person
5+
import ru.ifmo.se.dating.people.model.Topic
6+
import ru.ifmo.se.dating.security.auth.User
7+
8+
interface InterestService {
9+
suspend fun insert(id: User.Id, interest: Person.Interest)
10+
suspend fun remove(id: User.Id, topicId: Topic.Id)
11+
fun getAllTopics(): Flow<Topic>
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package ru.ifmo.se.dating.people.logic.basic
2+
3+
import kotlinx.coroutines.flow.Flow
4+
import ru.ifmo.se.dating.people.logic.InterestService
5+
import ru.ifmo.se.dating.people.model.Person
6+
import ru.ifmo.se.dating.people.model.Topic
7+
import ru.ifmo.se.dating.people.storage.InterestStorage
8+
import ru.ifmo.se.dating.security.auth.User
9+
10+
class BasicInterestService(
11+
private val storage: InterestStorage,
12+
) : InterestService {
13+
override suspend fun insert(id: User.Id, interest: Person.Interest) =
14+
storage.upsert(id, interest)
15+
16+
override suspend fun remove(id: User.Id, topicId: Topic.Id) =
17+
storage.delete(id, topicId)
18+
19+
override fun getAllTopics(): Flow<Topic> =
20+
storage.selectAllTopics()
21+
}

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

+1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ class BasicPersonService(
4949

5050
if (
5151
variant is Person.Draft && expected.copy(
52+
interests = variant.interests,
5253
pictureIds = variant.pictureIds,
5354
version = variant.version,
5455
) != variant
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package ru.ifmo.se.dating.people.logic.logging
2+
3+
import kotlinx.coroutines.flow.Flow
4+
import ru.ifmo.se.dating.logging.Log.Companion.autoLog
5+
import ru.ifmo.se.dating.people.logic.InterestService
6+
import ru.ifmo.se.dating.people.model.Person
7+
import ru.ifmo.se.dating.people.model.Topic
8+
import ru.ifmo.se.dating.security.auth.User
9+
10+
class LoggingInterestService(private val origin: InterestService) : InterestService by origin {
11+
private val log = autoLog()
12+
13+
override suspend fun insert(id: User.Id, interest: Person.Interest) =
14+
runCatching { origin.insert(id, interest) }
15+
.onSuccess {
16+
buildString {
17+
append("Person with id $id is interested in ")
18+
append("${interest.topicId} at degree ${interest.degree}")
19+
}.let { log.info(it) }
20+
}
21+
.getOrThrow()
22+
23+
override suspend fun remove(id: User.Id, topicId: Topic.Id) =
24+
runCatching { origin.remove(id, topicId) }
25+
.onSuccess {
26+
log.info("Removed an interest in topic with id $topicId from user with id $id")
27+
}
28+
.onFailure { e ->
29+
buildString {
30+
append("Failed to remove an interest in ")
31+
append("topic with id $topicId from ")
32+
append("user with id $id: ${e.message}")
33+
}.let { log.warn(it) }
34+
}
35+
.getOrThrow()
36+
37+
override fun getAllTopics(): Flow<Topic> =
38+
runCatching { origin.getAllTopics() }
39+
.onSuccess { log.debug("Got all topics") }
40+
.onFailure { e -> log.warn("Failed to get all topics: ${e.message}") }
41+
.getOrThrow()
42+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package ru.ifmo.se.dating.people.logic.spring
2+
3+
import org.springframework.stereotype.Service
4+
import ru.ifmo.se.dating.people.logic.InterestService
5+
import ru.ifmo.se.dating.people.logic.basic.BasicInterestService
6+
import ru.ifmo.se.dating.people.logic.logging.LoggingInterestService
7+
import ru.ifmo.se.dating.people.storage.InterestStorage
8+
9+
@Service
10+
class SpringInterestService(
11+
storage: InterestStorage,
12+
) : InterestService by
13+
LoggingInterestService(
14+
BasicInterestService(storage)
15+
)

backend/people/src/main/kotlin/ru/ifmo/se/dating/people/model/Person.kt

+17
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package ru.ifmo.se.dating.people.model
22

3+
import ru.ifmo.se.dating.people.model.Person.Interest
34
import ru.ifmo.se.dating.security.auth.User
45
import ru.ifmo.se.dating.validation.expect
56
import ru.ifmo.se.dating.validation.expectInRange
@@ -8,6 +9,7 @@ import java.time.LocalDate
89

910
sealed class PersonVariant {
1011
abstract val id: User.Id
12+
abstract val interests: Set<Interest>
1113
abstract val pictureIds: List<Picture.Id>
1214
abstract val version: Person.Version
1315
}
@@ -20,6 +22,7 @@ data class Person(
2022
val birthday: LocalDate,
2123
val facultyId: Faculty.Id,
2224
val locationId: Location.Id,
25+
override val interests: Set<Interest>,
2326
override val pictureIds: List<Picture.Id>,
2427
override val version: Version,
2528
val isPublished: Boolean,
@@ -35,6 +38,19 @@ data class Person(
3538
}
3639
}
3740

41+
data class Interest(
42+
val topicId: Topic.Id,
43+
val degree: Int,
44+
) {
45+
init {
46+
expectInRange("Level", degree, degreeRange)
47+
}
48+
49+
companion object {
50+
private val degreeRange = 1..5
51+
}
52+
}
53+
3854
@JvmInline
3955
value class Version(val number: Int) {
4056
init {
@@ -48,6 +64,7 @@ data class Person(
4864
val lastName: Name? = null,
4965
val height: Int? = null,
5066
val birthday: LocalDate? = null,
67+
override val interests: Set<Interest> = emptySet(),
5168
val facultyId: Faculty.Id? = null,
5269
val locationId: Location.Id? = null,
5370
override val pictureIds: List<Picture.Id> = emptyList(),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package ru.ifmo.se.dating.people.model
2+
3+
import ru.ifmo.se.dating.validation.expectId
4+
import ru.ifmo.se.dating.validation.expectMatches
5+
6+
data class Topic(
7+
val id: Id,
8+
val name: String,
9+
val color: Color,
10+
) {
11+
@JvmInline
12+
value class Id(val number: Int) {
13+
init {
14+
expectId(number)
15+
}
16+
17+
override fun toString(): String = number.toString()
18+
}
19+
20+
@JvmInline
21+
value class Color(val hex: String) {
22+
init {
23+
expectMatches("Hex", hex, regex)
24+
}
25+
26+
override fun toString(): String = hex
27+
28+
companion object {
29+
private val regex = Regex("#[A-F0-9]{6}")
30+
}
31+
}
32+
33+
init {
34+
expectMatches("Name", name, nameRegex)
35+
}
36+
37+
companion object {
38+
private val nameRegex = Regex("[A-Z][a-z]{3,32}")
39+
}
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package ru.ifmo.se.dating.people.storage
2+
3+
import kotlinx.coroutines.flow.Flow
4+
import ru.ifmo.se.dating.people.model.Person
5+
import ru.ifmo.se.dating.people.model.Topic
6+
import ru.ifmo.se.dating.security.auth.User
7+
8+
interface InterestStorage {
9+
suspend fun upsert(id: User.Id, interest: Person.Interest)
10+
suspend fun delete(id: User.Id, topicId: Topic.Id)
11+
fun selectInterestsByPersonId(id: User.Id): Flow<Person.Interest>
12+
fun selectAllTopics(): Flow<Topic>
13+
}

0 commit comments

Comments
 (0)