feat: アカウント作成時に自動でホームタイムラインを作成するように

This commit is contained in:
usbharu 2024-07-20 17:16:28 +09:00
parent 8e84c8e59c
commit e728b0f990
Signed by: usbharu
GPG Key ID: 6556747BF94EEBC8
10 changed files with 124 additions and 25 deletions

View File

@ -63,6 +63,9 @@ class RegisterLocalActorApplicationService(
id = UserDetailId(idGenerateService.generateId()), id = UserDetailId(idGenerateService.generateId()),
actorId = actor.id, actorId = actor.id,
password = userDetailDomainService.hashPassword(command.password), password = userDetailDomainService.hashPassword(command.password),
autoAcceptFolloweeFollowRequest = false,
lastMigration = null,
homeTimelineId = null
) )
userDetailRepository.save(userDetail) userDetailRepository.save(userDetail)
return actor.url return actor.url

View File

@ -7,4 +7,4 @@ interface DomainEventSubscriber {
fun <T : DomainEventBody> subscribe(eventName: String, domainEventConsumer: DomainEventConsumer<T>) fun <T : DomainEventBody> subscribe(eventName: String, domainEventConsumer: DomainEventConsumer<T>)
} }
typealias DomainEventConsumer<T> = (DomainEvent<T>) -> Unit typealias DomainEventConsumer<T> = suspend (DomainEvent<T>) -> Unit

View File

@ -0,0 +1,57 @@
package dev.usbharu.hideout.core.application.domainevent.subscribers
import dev.usbharu.hideout.core.application.shared.DomainEventCommandExecutor
import dev.usbharu.hideout.core.application.shared.UserDetailGettableCommandExecutor
import dev.usbharu.hideout.core.application.timeline.AddTimelineRelationship
import dev.usbharu.hideout.core.application.timeline.UserAddTimelineRelationshipApplicationService
import dev.usbharu.hideout.core.domain.event.relationship.RelationshipEvent
import dev.usbharu.hideout.core.domain.event.relationship.RelationshipEventBody
import dev.usbharu.hideout.core.domain.model.timelinerelationship.TimelineRelationship
import dev.usbharu.hideout.core.domain.model.timelinerelationship.TimelineRelationshipId
import dev.usbharu.hideout.core.domain.model.timelinerelationship.Visible
import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailRepository
import dev.usbharu.hideout.core.domain.shared.id.IdGenerateService
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Component
@Component
class TimelineRelationshipFollowSubscriber(
private val userAddTimelineRelationshipApplicationService: UserAddTimelineRelationshipApplicationService,
private val idGenerateService: IdGenerateService,
private val userDetailRepository: UserDetailRepository,
domainEventSubscriber: DomainEventSubscriber
) : Subscriber {
init {
domainEventSubscriber.subscribe<RelationshipEventBody>(RelationshipEvent.FOLLOW.eventName) {
val relationship = it.body.getRelationship()
val userDetail = userDetailRepository.findByActorId(relationship.actorId.id) ?: throw Exception()
if (userDetail.homeTimelineId == null) {
logger.warn("Home timeline for ${relationship.actorId} is not found")
return@subscribe
}
userAddTimelineRelationshipApplicationService.execute(
AddTimelineRelationship(
TimelineRelationship(
TimelineRelationshipId(idGenerateService.generateId()),
userDetail.homeTimelineId,
relationship.targetActorId,
Visible.FOLLOWERS
)
), DomainEventCommandExecutor("", object : UserDetailGettableCommandExecutor {
override val userDetailId: Long
get() = userDetail.id.id
override val executor: String
get() = userDetail.id.id.toString()
})
)
}
}
companion object {
private val logger = LoggerFactory.getLogger(TimelineRelationshipFollowSubscriber::class.java)
}
}

View File

@ -23,3 +23,8 @@ interface CommandExecutor {
interface UserDetailGettableCommandExecutor : CommandExecutor { interface UserDetailGettableCommandExecutor : CommandExecutor {
val userDetailId: Long val userDetailId: Long
} }
data class DomainEventCommandExecutor(
override val executor: String,
val commandExecutor: CommandExecutor?
) : CommandExecutor

View File

@ -0,0 +1,7 @@
package dev.usbharu.hideout.core.application.timeline
import dev.usbharu.hideout.core.domain.model.timelinerelationship.TimelineRelationship
data class AddTimelineRelationship(
val timelineRelationship: TimelineRelationship
)

View File

@ -0,0 +1,26 @@
package dev.usbharu.hideout.core.application.timeline
import dev.usbharu.hideout.core.application.shared.AbstractApplicationService
import dev.usbharu.hideout.core.application.shared.CommandExecutor
import dev.usbharu.hideout.core.application.shared.Transaction
import dev.usbharu.hideout.core.domain.model.timelinerelationship.TimelineRelationshipRepository
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
@Service
class UserAddTimelineRelationshipApplicationService(
private val timelineRelationshipRepository: TimelineRelationshipRepository,
transaction: Transaction
) :
AbstractApplicationService<AddTimelineRelationship, Unit>(
transaction, logger
) {
override suspend fun internalExecute(command: AddTimelineRelationship, executor: CommandExecutor) {
timelineRelationshipRepository.save(command.timelineRelationship)
}
companion object {
private val logger = LoggerFactory.getLogger(UserAddTimelineRelationshipApplicationService::class.java)
}
}

View File

@ -25,7 +25,11 @@ class RelationshipEventFactory(private val relationship: Relationship) {
DomainEvent.create(relationshipEvent.eventName, RelationshipEventBody(relationship)) DomainEvent.create(relationshipEvent.eventName, RelationshipEventBody(relationship))
} }
class RelationshipEventBody(relationship: Relationship) : DomainEventBody(mapOf("relationship" to relationship)) class RelationshipEventBody(relationship: Relationship) : DomainEventBody(mapOf("relationship" to relationship)) {
fun getRelationship(): Relationship {
return toMap()["relationship"] as Relationship
}
}
enum class RelationshipEvent(val eventName: String) { enum class RelationshipEvent(val eventName: String) {
FOLLOW("RelationshipFollow"), FOLLOW("RelationshipFollow"),

View File

@ -17,6 +17,7 @@
package dev.usbharu.hideout.core.domain.model.userdetails package dev.usbharu.hideout.core.domain.model.userdetails
import dev.usbharu.hideout.core.domain.model.actor.ActorId import dev.usbharu.hideout.core.domain.model.actor.ActorId
import dev.usbharu.hideout.core.domain.model.timeline.TimelineId
import java.time.Instant import java.time.Instant
class UserDetail private constructor( class UserDetail private constructor(
@ -25,6 +26,7 @@ class UserDetail private constructor(
var password: UserDetailHashedPassword, var password: UserDetailHashedPassword,
var autoAcceptFolloweeFollowRequest: Boolean, var autoAcceptFolloweeFollowRequest: Boolean,
var lastMigration: Instant? = null, var lastMigration: Instant? = null,
val homeTimelineId: TimelineId?
) { ) {
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
@ -45,13 +47,15 @@ class UserDetail private constructor(
password: UserDetailHashedPassword, password: UserDetailHashedPassword,
autoAcceptFolloweeFollowRequest: Boolean = false, autoAcceptFolloweeFollowRequest: Boolean = false,
lastMigration: Instant? = null, lastMigration: Instant? = null,
homeTimelineId: TimelineId?
): UserDetail { ): UserDetail {
return UserDetail( return UserDetail(
id, id,
actorId, actorId,
password, password,
autoAcceptFolloweeFollowRequest, autoAcceptFolloweeFollowRequest,
lastMigration lastMigration,
homeTimelineId
) )
} }
} }

View File

@ -17,6 +17,7 @@
package dev.usbharu.hideout.core.infrastructure.exposedrepository package dev.usbharu.hideout.core.infrastructure.exposedrepository
import dev.usbharu.hideout.core.domain.model.actor.ActorId import dev.usbharu.hideout.core.domain.model.actor.ActorId
import dev.usbharu.hideout.core.domain.model.timeline.TimelineId
import dev.usbharu.hideout.core.domain.model.userdetails.UserDetail import dev.usbharu.hideout.core.domain.model.userdetails.UserDetail
import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailHashedPassword import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailHashedPassword
import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailId import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailId
@ -64,13 +65,7 @@ class UserDetailRepositoryImpl : UserDetailRepository, AbstractRepository() {
.selectAll().where { UserDetails.actorId eq actorId } .selectAll().where { UserDetails.actorId eq actorId }
.singleOrNull() .singleOrNull()
?.let { ?.let {
UserDetail.create( userDetail(it)
UserDetailId(it[UserDetails.id]),
ActorId(it[UserDetails.actorId]),
UserDetailHashedPassword(it[UserDetails.password]),
it[UserDetails.autoAcceptFolloweeFollowRequest],
it[UserDetails.lastMigration]
)
} }
} }
@ -79,13 +74,7 @@ class UserDetailRepositoryImpl : UserDetailRepository, AbstractRepository() {
.selectAll().where { UserDetails.id eq id } .selectAll().where { UserDetails.id eq id }
.singleOrNull() .singleOrNull()
?.let { ?.let {
UserDetail.create( userDetail(it)
UserDetailId(it[UserDetails.id]),
ActorId(it[UserDetails.actorId]),
UserDetailHashedPassword(it[UserDetails.password]),
it[UserDetails.autoAcceptFolloweeFollowRequest],
it[UserDetails.lastMigration]
)
} }
} }
@ -95,16 +84,19 @@ class UserDetailRepositoryImpl : UserDetailRepository, AbstractRepository() {
.selectAll() .selectAll()
.where { UserDetails.id inList idList.map { it.id } } .where { UserDetails.id inList idList.map { it.id } }
.map { .map {
UserDetail.create( userDetail(it)
}
}
}
private fun userDetail(it: ResultRow) = UserDetail.create(
UserDetailId(it[UserDetails.id]), UserDetailId(it[UserDetails.id]),
ActorId(it[UserDetails.actorId]), ActorId(it[UserDetails.actorId]),
UserDetailHashedPassword(it[UserDetails.password]), UserDetailHashedPassword(it[UserDetails.password]),
it[UserDetails.autoAcceptFolloweeFollowRequest], it[UserDetails.autoAcceptFolloweeFollowRequest],
it[UserDetails.lastMigration] it[UserDetails.lastMigration],
it[UserDetails.homeTimelineId]?.let { it1 -> TimelineId(it1) }
) )
}
}
}
companion object { companion object {
private val logger = LoggerFactory.getLogger(UserDetailRepositoryImpl::class.java) private val logger = LoggerFactory.getLogger(UserDetailRepositoryImpl::class.java)
@ -117,5 +109,6 @@ object UserDetails : Table("user_details") {
val password = varchar("password", 255) val password = varchar("password", 255)
val autoAcceptFolloweeFollowRequest = bool("auto_accept_followee_follow_request") val autoAcceptFolloweeFollowRequest = bool("auto_accept_followee_follow_request")
val lastMigration = timestamp("last_migration").nullable() val lastMigration = timestamp("last_migration").nullable()
val homeTimelineId = long("home_timeline_id").references(Timelines.id).nullable()
override val primaryKey: PrimaryKey = PrimaryKey(id) override val primaryKey: PrimaryKey = PrimaryKey(id)
} }

View File

@ -17,7 +17,7 @@ class SpringFrameworkDomainEventSubscriber : DomainEventSubscriber {
} }
@EventListener @EventListener
fun onDomainEventPublished(domainEvent: DomainEvent<*>) { suspend fun onDomainEventPublished(domainEvent: DomainEvent<*>) {
map[domainEvent.name]?.forEach { map[domainEvent.name]?.forEach {
try { try {
it.invoke(domainEvent) it.invoke(domainEvent)