feat: フォローテーブルにフォロー情報を記録するように

This commit is contained in:
usbharu 2023-04-10 16:49:11 +09:00
parent a9200eaeef
commit 5846dde42f
13 changed files with 130 additions and 16 deletions

View File

@ -81,7 +81,7 @@ fun Application.parent() {
} }
} }
} }
single<ActivityPubFollowService> { ActivityPubFollowServiceImpl(get(), get(), get()) } single<ActivityPubFollowService> { ActivityPubFollowServiceImpl(get(), get(), get(),get()) }
single<ActivityPubService> { ActivityPubServiceImpl(get()) } single<ActivityPubService> { ActivityPubServiceImpl(get()) }
single<UserService> { UserService(get()) } single<UserService> { UserService(get()) }
single<ActivityPubUserService> { ActivityPubUserServiceImpl(get(), get(), get()) } single<ActivityPubUserService> { ActivityPubUserServiceImpl(get(), get(), get()) }

View File

@ -1,9 +1,9 @@
package dev.usbharu.hideout.ap package dev.usbharu.hideout.ap
open class Key : Object{ open class Key : Object{
private var id:String? = null var id:String? = null
private var owner:String? = null var owner:String? = null
private var publicKeyPem:String? = null var publicKeyPem:String? = null
protected constructor() : super() protected constructor() : super()
constructor( constructor(
type: List<String>, type: List<String>,

View File

@ -5,10 +5,10 @@ open class Person : Object {
var preferredUsername:String? = null var preferredUsername:String? = null
var summary:String? = null var summary:String? = null
var inbox:String? = null var inbox:String? = null
private var outbox:String? = null var outbox:String? = null
private var url:String? = null private var url:String? = null
private var icon:Image? = null private var icon:Image? = null
private var publicKey:Key? = null var publicKey:Key? = null
protected constructor() : super() protected constructor() : super()
constructor( constructor(
type: List<String> = emptyList(), type: List<String> = emptyList(),

View File

@ -0,0 +1,8 @@
package dev.usbharu.hideout.exception.ap
class IllegalActivityPubObjectException : IllegalArgumentException {
constructor() : super()
constructor(s: String?) : super(s)
constructor(message: String?, cause: Throwable?) : super(message, cause)
constructor(cause: Throwable?) : super(cause)
}

View File

@ -8,10 +8,16 @@ interface IUserRepository {
suspend fun findById(id: Long): UserEntity? suspend fun findById(id: Long): UserEntity?
suspend fun findByIds(ids: List<Long>): List<UserEntity>
suspend fun findByName(name: String): UserEntity? suspend fun findByName(name: String): UserEntity?
suspend fun findByNameAndDomains(names: List<Pair<String,String>>): List<UserEntity>
suspend fun findByUrl(url:String):UserEntity? suspend fun findByUrl(url:String):UserEntity?
suspend fun findByUrls(urls: List<String>): List<UserEntity>
suspend fun update(userEntity: UserEntity) suspend fun update(userEntity: UserEntity)
suspend fun delete(id: Long) suspend fun delete(id: Long)

View File

@ -79,6 +79,14 @@ class UserRepository(private val database: Database) : IUserRepository {
} }
} }
override suspend fun findByIds(ids: List<Long>): List<UserEntity> {
return query {
Users.select { Users.id inList ids }.map {
it.toUserEntity()
}
}
}
override suspend fun findByName(name: String): UserEntity? { override suspend fun findByName(name: String): UserEntity? {
return query { return query {
Users.select { Users.name eq name }.map { Users.select { Users.name eq name }.map {
@ -87,12 +95,26 @@ class UserRepository(private val database: Database) : IUserRepository {
} }
} }
override suspend fun findByNameAndDomains(names: List<Pair<String, String>>): List<UserEntity> {
return query {
val selectAll = Users.selectAll()
names.forEach { (name, domain) ->
selectAll.orWhere { Users.name eq name and (Users.domain eq domain) }
}
selectAll.map { it.toUserEntity() }
}
}
override suspend fun findByUrl(url: String): UserEntity? { override suspend fun findByUrl(url: String): UserEntity? {
return query { return query {
Users.select { Users.url eq url }.singleOrNull()?.toUserEntity() Users.select { Users.url eq url }.singleOrNull()?.toUserEntity()
} }
} }
override suspend fun findByUrls(urls: List<String>): List<UserEntity> {
TODO("Not yet implemented")
}
override suspend fun findFollowersById(id: Long): List<UserEntity> { override suspend fun findFollowersById(id: Long): List<UserEntity> {
return query { return query {
val followers = Users.alias("FOLLOWERS") val followers = Users.alias("FOLLOWERS")

View File

@ -1,16 +1,18 @@
package dev.usbharu.hideout.service package dev.usbharu.hideout.service
import dev.usbharu.hideout.domain.model.UserAuthentication
import dev.usbharu.hideout.domain.model.UserAuthenticationEntity import dev.usbharu.hideout.domain.model.UserAuthenticationEntity
interface IUserAuthService { interface IUserAuthService {
fun hash(password:String): String fun hash(password: String): String
suspend fun usernameAlreadyUse(username: String):Boolean suspend fun usernameAlreadyUse(username: String): Boolean
suspend fun registerAccount(username: String, hash: String) suspend fun registerAccount(username: String, hash: String)
suspend fun verifyAccount(username: String,password: String): Boolean suspend fun verifyAccount(username: String, password: String): Boolean
suspend fun findByUserId(userId: Long):UserAuthenticationEntity suspend fun findByUserId(userId: Long): UserAuthenticationEntity
suspend fun findByUsername(username: String):UserAuthenticationEntity suspend fun findByUsername(username: String): UserAuthenticationEntity
suspend fun createAccount(userEntity: UserAuthentication): UserAuthenticationEntity
} }

View File

@ -8,6 +8,7 @@ import dev.usbharu.hideout.domain.model.ActivityPubResponse
import dev.usbharu.hideout.domain.model.ActivityPubStringResponse import dev.usbharu.hideout.domain.model.ActivityPubStringResponse
import dev.usbharu.hideout.domain.model.job.ReceiveFollowJob import dev.usbharu.hideout.domain.model.job.ReceiveFollowJob
import dev.usbharu.hideout.plugins.postAp import dev.usbharu.hideout.plugins.postAp
import dev.usbharu.hideout.service.impl.UserService
import dev.usbharu.hideout.service.job.JobQueueParentService import dev.usbharu.hideout.service.job.JobQueueParentService
import io.ktor.client.* import io.ktor.client.*
import io.ktor.http.* import io.ktor.http.*
@ -16,6 +17,7 @@ import kjob.core.job.JobProps
class ActivityPubFollowServiceImpl( class ActivityPubFollowServiceImpl(
private val jobQueueParentService: JobQueueParentService, private val jobQueueParentService: JobQueueParentService,
private val activityPubUserService: ActivityPubUserService, private val activityPubUserService: ActivityPubUserService,
private val userService: UserService,
private val httpClient: HttpClient private val httpClient: HttpClient
) : ActivityPubFollowService { ) : ActivityPubFollowService {
override suspend fun receiveFollow(follow: Follow): ActivityPubResponse { override suspend fun receiveFollow(follow: Follow): ActivityPubResponse {
@ -32,14 +34,19 @@ class ActivityPubFollowServiceImpl(
val actor = props[ReceiveFollowJob.actor] val actor = props[ReceiveFollowJob.actor]
val person = activityPubUserService.fetchPerson(actor) val person = activityPubUserService.fetchPerson(actor)
val follow = Config.configData.objectMapper.readValue<Follow>(props[ReceiveFollowJob.follow]) val follow = Config.configData.objectMapper.readValue<Follow>(props[ReceiveFollowJob.follow])
val targetActor = props[ReceiveFollowJob.targetActor]
httpClient.postAp( httpClient.postAp(
urlString = person.inbox ?: throw IllegalArgumentException("inbox is not found"), urlString = person.inbox ?: throw IllegalArgumentException("inbox is not found"),
username = "${props[ReceiveFollowJob.targetActor]}#pubkey", username = "$targetActor#pubkey",
jsonLd = Accept( jsonLd = Accept(
name = "Follow", name = "Follow",
`object` = follow, `object` = follow,
actor = props[ReceiveFollowJob.targetActor] actor = targetActor
) )
) )
val users =
userService.findByUrls(listOf(targetActor, follow.actor ?: throw IllegalArgumentException("actor is null")))
userService.addFollowers(users.first { it.url == targetActor }.id, users.first { it.url == follow.actor }.id)
} }
} }

View File

@ -5,7 +5,10 @@ import dev.usbharu.hideout.ap.Image
import dev.usbharu.hideout.ap.Key import dev.usbharu.hideout.ap.Key
import dev.usbharu.hideout.ap.Person import dev.usbharu.hideout.ap.Person
import dev.usbharu.hideout.config.Config import dev.usbharu.hideout.config.Config
import dev.usbharu.hideout.domain.model.User
import dev.usbharu.hideout.domain.model.UserAuthentication
import dev.usbharu.hideout.exception.UserNotFoundException import dev.usbharu.hideout.exception.UserNotFoundException
import dev.usbharu.hideout.exception.ap.IllegalActivityPubObjectException
import dev.usbharu.hideout.service.IUserAuthService import dev.usbharu.hideout.service.IUserAuthService
import dev.usbharu.hideout.service.impl.UserService import dev.usbharu.hideout.service.impl.UserService
import dev.usbharu.hideout.util.HttpUtil.Activity import dev.usbharu.hideout.util.HttpUtil.Activity
@ -14,7 +17,11 @@ import io.ktor.client.request.*
import io.ktor.client.statement.* import io.ktor.client.statement.*
import io.ktor.http.* import io.ktor.http.*
class ActivityPubUserServiceImpl(private val userService: UserService, private val userAuthService: IUserAuthService,private val httpClient: HttpClient) : class ActivityPubUserServiceImpl(
private val userService: UserService,
private val userAuthService: IUserAuthService,
private val httpClient: HttpClient
) :
ActivityPubUserService { ActivityPubUserService {
override suspend fun getPersonByName(name: String): Person { override suspend fun getPersonByName(name: String): Person {
// TODO: JOINで書き直し // TODO: JOINで書き直し
@ -74,11 +81,32 @@ class ActivityPubUserServiceImpl(private val userService: UserService, private v
) )
) )
} catch (e:UserNotFoundException){ } catch (e: UserNotFoundException) {
val httpResponse = httpClient.get(url) { val httpResponse = httpClient.get(url) {
accept(ContentType.Application.Activity) accept(ContentType.Application.Activity)
} }
Config.configData.objectMapper.readValue<Person>(httpResponse.bodyAsText()) val person = Config.configData.objectMapper.readValue<Person>(httpResponse.bodyAsText())
val userEntity = userService.create(
User(
name = person.preferredUsername
?: throw IllegalActivityPubObjectException("preferredUsername is null"),
domain = url.substringAfter(":").substringBeforeLast("/"),
screenName = person.name ?: throw IllegalActivityPubObjectException("name is null"),
description = person.summary ?: throw IllegalActivityPubObjectException("summary is null"),
inbox = person.inbox ?: throw IllegalActivityPubObjectException("inbox is null"),
outbox = person.outbox ?: throw IllegalActivityPubObjectException("outbox is null"),
url = url
)
)
userAuthService.createAccount(
UserAuthentication(
userEntity.id,
null,
person.publicKey?.publicKeyPem ?: throw IllegalActivityPubObjectException("publicKey is null"),
null
)
)
person
} }
} }

View File

@ -4,6 +4,7 @@ import dev.usbharu.hideout.config.Config
import dev.usbharu.hideout.domain.model.User import dev.usbharu.hideout.domain.model.User
import dev.usbharu.hideout.domain.model.UserAuthentication import dev.usbharu.hideout.domain.model.UserAuthentication
import dev.usbharu.hideout.domain.model.UserAuthenticationEntity import dev.usbharu.hideout.domain.model.UserAuthenticationEntity
import dev.usbharu.hideout.domain.model.UserEntity
import dev.usbharu.hideout.exception.UserNotFoundException import dev.usbharu.hideout.exception.UserNotFoundException
import dev.usbharu.hideout.repository.IUserAuthRepository import dev.usbharu.hideout.repository.IUserAuthRepository
import dev.usbharu.hideout.repository.IUserRepository import dev.usbharu.hideout.repository.IUserRepository
@ -76,6 +77,10 @@ class UserAuthService(
?: throw UserNotFoundException("$username auth data was not found") ?: throw UserNotFoundException("$username auth data was not found")
} }
override suspend fun createAccount(userEntity: UserAuthentication): UserAuthenticationEntity {
return userAuthRepository.create(userEntity)
}
private fun generateKeyPair(): KeyPair { private fun generateKeyPair(): KeyPair {
val keyPairGenerator = KeyPairGenerator.getInstance("RSA") val keyPairGenerator = KeyPairGenerator.getInstance("RSA")
keyPairGenerator.initialize(1024) keyPairGenerator.initialize(1024)

View File

@ -21,15 +21,27 @@ class UserService(private val userRepository: IUserRepository) {
return userRepository.findById(id) ?: throw UserNotFoundException("$id was not found.") return userRepository.findById(id) ?: throw UserNotFoundException("$id was not found.")
} }
suspend fun findByIds(ids: List<Long>): List<UserEntity> {
return userRepository.findByIds(ids)
}
suspend fun findByName(name: String): UserEntity { suspend fun findByName(name: String): UserEntity {
return userRepository.findByName(name) return userRepository.findByName(name)
?: throw UserNotFoundException("$name was not found.") ?: throw UserNotFoundException("$name was not found.")
} }
suspend fun findByNameAndDomains(names: List<Pair<String,String>>): List<UserEntity> {
return userRepository.findByNameAndDomains(names)
}
suspend fun findByUrl(url: String): UserEntity { suspend fun findByUrl(url: String): UserEntity {
return userRepository.findByUrl(url) ?: throw UserNotFoundException("$url was not found.") return userRepository.findByUrl(url) ?: throw UserNotFoundException("$url was not found.")
} }
suspend fun findByUrls(urls: List<String>): List<UserEntity> {
return userRepository.findByUrls(urls)
}
suspend fun create(user: User): UserEntity { suspend fun create(user: User): UserEntity {
return userRepository.create(user) return userRepository.create(user)
} }

View File

@ -31,14 +31,26 @@ class ActivityPubKtTest {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
override suspend fun findByIds(ids: List<Long>): List<UserEntity> {
TODO("Not yet implemented")
}
override suspend fun findByName(name: String): UserEntity? { override suspend fun findByName(name: String): UserEntity? {
return UserEntity(1, "test", "localhost", "test", "","","","") return UserEntity(1, "test", "localhost", "test", "","","","")
} }
override suspend fun findByNameAndDomains(names: List<Pair<String, String>>): List<UserEntity> {
TODO("Not yet implemented")
}
override suspend fun findByUrl(url: String): UserEntity? { override suspend fun findByUrl(url: String): UserEntity? {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
override suspend fun findByUrls(urls: List<String>): List<UserEntity> {
TODO("Not yet implemented")
}
override suspend fun update(userEntity: UserEntity) { override suspend fun update(userEntity: UserEntity) {
TODO("Not yet implemented") TODO("Not yet implemented")
} }

View File

@ -26,14 +26,26 @@ class KtorKeyMapTest {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
override suspend fun findByIds(ids: List<Long>): List<UserEntity> {
TODO("Not yet implemented")
}
override suspend fun findByName(name: String): UserEntity? { override suspend fun findByName(name: String): UserEntity? {
return UserEntity(1, "test", "localhost", "test", "","","","") return UserEntity(1, "test", "localhost", "test", "","","","")
} }
override suspend fun findByNameAndDomains(names: List<Pair<String, String>>): List<UserEntity> {
TODO("Not yet implemented")
}
override suspend fun findByUrl(url: String): UserEntity? { override suspend fun findByUrl(url: String): UserEntity? {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
override suspend fun findByUrls(urls: List<String>): List<UserEntity> {
TODO("Not yet implemented")
}
override suspend fun update(userEntity: UserEntity) { override suspend fun update(userEntity: UserEntity) {
TODO("Not yet implemented") TODO("Not yet implemented")
} }