Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#82 Set person interests for topics #83

Merged
merged 6 commits into from
Jan 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions backend/gateway/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,24 @@ spring:
- Method=DELETE
- Path=/api/people/*/photos/*

- id: put-people-person-id-interests-topic-id
uri: lb://people
predicates:
- Method=PUT
- Path=/api/people/*/interests/*

- id: delete-people-person-id-interests-topic-id
uri: lb://people
predicates:
- Method=DELETE
- Path=/api/people/*/interests/*

- id: get-people-topics
uri: lb://people
predicates:
- Method=GET
- Path=/api/topics

- id: get-people-faculties
uri: lb://people
predicates:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,24 @@ import ru.ifmo.se.dating.pagging.Page
import ru.ifmo.se.dating.people.api.generated.PeopleApiDelegate
import ru.ifmo.se.dating.people.api.mapping.toMessage
import ru.ifmo.se.dating.people.api.mapping.toModel
import ru.ifmo.se.dating.people.logic.InterestService
import ru.ifmo.se.dating.people.logic.PersonService
import ru.ifmo.se.dating.people.logic.PictureService
import ru.ifmo.se.dating.people.model.Faculty
import ru.ifmo.se.dating.people.model.Person
import ru.ifmo.se.dating.people.model.Picture
import ru.ifmo.se.dating.people.model.Topic
import ru.ifmo.se.dating.people.model.generated.*
import ru.ifmo.se.dating.security.auth.User
import ru.ifmo.se.dating.spring.security.auth.SpringSecurityContext
import java.time.LocalDate
import java.time.OffsetDateTime

@Suppress("TooManyFunctions")
@Controller
class HttpPeopleApi(
private val personService: PersonService,
private val interestService: InterestService,
private val pictureService: PictureService,
) : PeopleApiDelegate {
override fun peopleGet(
Expand Down Expand Up @@ -109,6 +114,29 @@ class HttpPeopleApi(
return ResponseEntity.ok(Unit)
}

override suspend fun peoplePersonIdInterestsTopicIdDelete(
personId: Long,
topicId: Long,
): ResponseEntity<Unit> {
interestService.remove(User.Id(personId.toInt()), Topic.Id(topicId.toInt()))
return ResponseEntity.ok(Unit)
}

override suspend fun peoplePersonIdInterestsTopicIdPut(
personId: Long,
topicId: Long,
interestPatchMessage: InterestPatchMessage,
): ResponseEntity<Unit> {
interestService.insert(
id = User.Id(personId.toInt()),
interest = Person.Interest(
topicId = Topic.Id(topicId.toInt()),
degree = interestPatchMessage.level.value.toInt(),
)
)
return ResponseEntity.ok(Unit)
}

override suspend fun peoplePersonIdPhotosPost(
personId: Long,
body: Resource?,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package ru.ifmo.se.dating.people.api

import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import org.springframework.http.ResponseEntity
import org.springframework.stereotype.Controller
import ru.ifmo.se.dating.people.api.generated.TopicsApiDelegate
import ru.ifmo.se.dating.people.api.mapping.toMessage
import ru.ifmo.se.dating.people.logic.InterestService
import ru.ifmo.se.dating.people.model.generated.TopicMessage

@Controller
class HttpTopicsApi(
private val interestService: InterestService,
) : TopicsApiDelegate {
override fun topicsGet(): ResponseEntity<Flow<TopicMessage>> =
interestService.getAllTopics()
.map { it.toMessage() }
.let { ResponseEntity.ok(it) }
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package ru.ifmo.se.dating.people.api.mapping

import ru.ifmo.se.dating.people.model.Faculty
import ru.ifmo.se.dating.people.model.Location
import ru.ifmo.se.dating.people.model.Person
import ru.ifmo.se.dating.people.model.PersonVariant
import ru.ifmo.se.dating.people.model.*
import ru.ifmo.se.dating.people.model.generated.*
import ru.ifmo.se.dating.security.auth.User

Expand All @@ -13,6 +10,7 @@ fun PersonPatchMessage.toModel(id: Int) = Person.Draft(
lastName = lastName?.let { Person.Name(it) },
height = height,
birthday = birthday,
interests = emptySet(),
facultyId = facultyId?.let { Faculty.Id(it.toInt()) },
locationId = locationId?.let { Location.Id(it.toInt()) },
)
Expand All @@ -31,7 +29,7 @@ fun Person.toMessage() = PersonMessage(
birthday = birthday,
facultyId = facultyId.number.toLong(),
locationId = locationId.number.toLong(),
interests = emptySet(),
interests = interests.map { it.toMessage() }.toSet(),
zodiac = ZodiacSignMessage.leo,
pictures = pictureIds.map { PictureMessage(id = it.number.toLong()) }.toSet()
)
Expand All @@ -45,6 +43,18 @@ fun Person.Draft.toMessage() = PersonDraftMessage(
birthday = birthday,
facultyId = facultyId?.number?.toLong(),
locationId = locationId?.number?.toLong(),
interests = emptySet(),
interests = interests.map { it.toMessage() }.toSet(),
pictures = pictureIds.map { PictureMessage(id = it.number.toLong()) }.toSet()
)

fun InterestMessage.toModel(): Person.Interest =
Person.Interest(
topicId = Topic.Id(topicId.toInt()),
degree = level.value.toInt(),
)

fun Person.Interest.toMessage(): InterestMessage =
InterestMessage(
topicId = topicId.number.toLong(),
level = InterestLevelMessage.forValue(degree.toString()),
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package ru.ifmo.se.dating.people.api.mapping

import ru.ifmo.se.dating.people.model.Topic
import ru.ifmo.se.dating.people.model.generated.TopicMessage

fun Topic.toMessage(): TopicMessage =
TopicMessage(
id = id.number.toLong(),
name = name,
color = color.hex,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package ru.ifmo.se.dating.people.logic

import kotlinx.coroutines.flow.Flow
import ru.ifmo.se.dating.people.model.Person
import ru.ifmo.se.dating.people.model.Topic
import ru.ifmo.se.dating.security.auth.User

interface InterestService {
suspend fun insert(id: User.Id, interest: Person.Interest)
suspend fun remove(id: User.Id, topicId: Topic.Id)
fun getAllTopics(): Flow<Topic>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package ru.ifmo.se.dating.people.logic.basic

import kotlinx.coroutines.flow.Flow
import ru.ifmo.se.dating.people.logic.InterestService
import ru.ifmo.se.dating.people.model.Person
import ru.ifmo.se.dating.people.model.Topic
import ru.ifmo.se.dating.people.storage.InterestStorage
import ru.ifmo.se.dating.security.auth.User

class BasicInterestService(
private val storage: InterestStorage,
) : InterestService {
override suspend fun insert(id: User.Id, interest: Person.Interest) =
storage.upsert(id, interest)

override suspend fun remove(id: User.Id, topicId: Topic.Id) =
storage.delete(id, topicId)

override fun getAllTopics(): Flow<Topic> =
storage.selectAllTopics()
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ class BasicPersonService(

if (
variant is Person.Draft && expected.copy(
interests = variant.interests,
pictureIds = variant.pictureIds,
version = variant.version,
) != variant
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package ru.ifmo.se.dating.people.logic.logging

import kotlinx.coroutines.flow.Flow
import ru.ifmo.se.dating.logging.Log.Companion.autoLog
import ru.ifmo.se.dating.people.logic.InterestService
import ru.ifmo.se.dating.people.model.Person
import ru.ifmo.se.dating.people.model.Topic
import ru.ifmo.se.dating.security.auth.User

class LoggingInterestService(private val origin: InterestService) : InterestService by origin {
private val log = autoLog()

override suspend fun insert(id: User.Id, interest: Person.Interest) =
runCatching { origin.insert(id, interest) }
.onSuccess {
buildString {
append("Person with id $id is interested in ")
append("${interest.topicId} at degree ${interest.degree}")
}.let { log.info(it) }
}
.getOrThrow()

override suspend fun remove(id: User.Id, topicId: Topic.Id) =
runCatching { origin.remove(id, topicId) }
.onSuccess {
log.info("Removed an interest in topic with id $topicId from user with id $id")
}
.onFailure { e ->
buildString {
append("Failed to remove an interest in ")
append("topic with id $topicId from ")
append("user with id $id: ${e.message}")
}.let { log.warn(it) }
}
.getOrThrow()

override fun getAllTopics(): Flow<Topic> =
runCatching { origin.getAllTopics() }
.onSuccess { log.debug("Got all topics") }
.onFailure { e -> log.warn("Failed to get all topics: ${e.message}") }
.getOrThrow()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package ru.ifmo.se.dating.people.logic.spring

import org.springframework.stereotype.Service
import ru.ifmo.se.dating.people.logic.InterestService
import ru.ifmo.se.dating.people.logic.basic.BasicInterestService
import ru.ifmo.se.dating.people.logic.logging.LoggingInterestService
import ru.ifmo.se.dating.people.storage.InterestStorage

@Service
class SpringInterestService(
storage: InterestStorage,
) : InterestService by
LoggingInterestService(
BasicInterestService(storage)
)
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package ru.ifmo.se.dating.people.model

import ru.ifmo.se.dating.people.model.Person.Interest
import ru.ifmo.se.dating.security.auth.User
import ru.ifmo.se.dating.validation.expect
import ru.ifmo.se.dating.validation.expectInRange
Expand All @@ -8,6 +9,7 @@ import java.time.LocalDate

sealed class PersonVariant {
abstract val id: User.Id
abstract val interests: Set<Interest>
abstract val pictureIds: List<Picture.Id>
abstract val version: Person.Version
}
Expand All @@ -20,6 +22,7 @@ data class Person(
val birthday: LocalDate,
val facultyId: Faculty.Id,
val locationId: Location.Id,
override val interests: Set<Interest>,
override val pictureIds: List<Picture.Id>,
override val version: Version,
val isPublished: Boolean,
Expand All @@ -35,6 +38,19 @@ data class Person(
}
}

data class Interest(
val topicId: Topic.Id,
val degree: Int,
) {
init {
expectInRange("Level", degree, degreeRange)
}

companion object {
private val degreeRange = 1..5
}
}

@JvmInline
value class Version(val number: Int) {
init {
Expand All @@ -48,6 +64,7 @@ data class Person(
val lastName: Name? = null,
val height: Int? = null,
val birthday: LocalDate? = null,
override val interests: Set<Interest> = emptySet(),
val facultyId: Faculty.Id? = null,
val locationId: Location.Id? = null,
override val pictureIds: List<Picture.Id> = emptyList(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package ru.ifmo.se.dating.people.model

import ru.ifmo.se.dating.validation.expectId
import ru.ifmo.se.dating.validation.expectMatches

data class Topic(
val id: Id,
val name: String,
val color: Color,
) {
@JvmInline
value class Id(val number: Int) {
init {
expectId(number)
}

override fun toString(): String = number.toString()
}

@JvmInline
value class Color(val hex: String) {
init {
expectMatches("Hex", hex, regex)
}

override fun toString(): String = hex

companion object {
private val regex = Regex("#[A-F0-9]{6}")
}
}

init {
expectMatches("Name", name, nameRegex)
}

companion object {
private val nameRegex = Regex("[A-Z][a-z]{3,32}")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package ru.ifmo.se.dating.people.storage

import kotlinx.coroutines.flow.Flow
import ru.ifmo.se.dating.people.model.Person
import ru.ifmo.se.dating.people.model.Topic
import ru.ifmo.se.dating.security.auth.User

interface InterestStorage {
suspend fun upsert(id: User.Id, interest: Person.Interest)
suspend fun delete(id: User.Id, topicId: Topic.Id)
fun selectInterestsByPersonId(id: User.Id): Flow<Person.Interest>
fun selectAllTopics(): Flow<Topic>
}
Loading
Loading