mirror of https://github.com/usbharu/Hideout.git
feat: フォローされたときにAcceptを返すように
This commit is contained in:
parent
2faef66dc2
commit
839aa693ce
|
@ -19,6 +19,9 @@ import dev.usbharu.hideout.service.job.JobQueueParentService
|
|||
import dev.usbharu.hideout.service.job.KJobJobQueueParentService
|
||||
import dev.usbharu.hideout.service.signature.HttpSignatureVerifyService
|
||||
import dev.usbharu.hideout.service.signature.HttpSignatureVerifyServiceImpl
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.engine.cio.*
|
||||
import io.ktor.client.plugins.logging.*
|
||||
import io.ktor.server.application.*
|
||||
import org.jetbrains.exposed.sql.Database
|
||||
import org.koin.ktor.ext.inject
|
||||
|
@ -54,14 +57,23 @@ fun Application.module() {
|
|||
single<IUserAuthRepository> { UserAuthRepository(get()) }
|
||||
single<IUserAuthService> { UserAuthService(get(), get()) }
|
||||
single<HttpSignatureVerifyService> { HttpSignatureVerifyServiceImpl(get()) }
|
||||
single<JobQueueParentService> { val kJobJobQueueService = KJobJobQueueParentService(get())
|
||||
single<JobQueueParentService> {
|
||||
val kJobJobQueueService = KJobJobQueueParentService(get())
|
||||
kJobJobQueueService.init(listOf())
|
||||
kJobJobQueueService
|
||||
}
|
||||
single<ActivityPubFollowService>{ ActivityPubFollowServiceImpl(get()) }
|
||||
single<HttpClient> {
|
||||
HttpClient(CIO).config {
|
||||
install(Logging) {
|
||||
logger = Logger.DEFAULT
|
||||
level = LogLevel.ALL
|
||||
}
|
||||
}
|
||||
}
|
||||
single<ActivityPubFollowService> { ActivityPubFollowServiceImpl(get(), get(), get()) }
|
||||
single<ActivityPubService> { ActivityPubServiceImpl(get()) }
|
||||
single<UserService> { UserService(get()) }
|
||||
single<ActivityPubUserService> { ActivityPubUserServiceImpl(get(), get()) }
|
||||
single<ActivityPubUserService> { ActivityPubUserServiceImpl(get(), get(),get()) }
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -2,16 +2,36 @@ package dev.usbharu.hideout.domain.model
|
|||
|
||||
import org.jetbrains.exposed.dao.id.LongIdTable
|
||||
|
||||
data class User(val name: String,val domain: String, val screenName: String, val description: String)
|
||||
data class User(
|
||||
val name: String,
|
||||
val domain: String,
|
||||
val screenName: String,
|
||||
val description: String,
|
||||
val inbox: String,
|
||||
val outbox: String,
|
||||
val url: String
|
||||
)
|
||||
|
||||
data class UserEntity(
|
||||
val id: Long,
|
||||
val name: String,
|
||||
val domain: String,
|
||||
val screenName: String,
|
||||
val description: String
|
||||
val description: String,
|
||||
val inbox: String,
|
||||
val outbox: String,
|
||||
val url: String
|
||||
) {
|
||||
constructor(id: Long, user: User) : this(id, user.name,user.domain, user.screenName, user.description)
|
||||
constructor(id: Long, user: User) : this(
|
||||
id,
|
||||
user.name,
|
||||
user.domain,
|
||||
user.screenName,
|
||||
user.description,
|
||||
user.inbox,
|
||||
user.outbox,
|
||||
user.url
|
||||
)
|
||||
}
|
||||
|
||||
object Users : LongIdTable("users") {
|
||||
|
@ -19,6 +39,10 @@ object Users : LongIdTable("users") {
|
|||
val domain = varchar("domain", length = 255)
|
||||
val screenName = varchar("screen_name", length = 64)
|
||||
val description = varchar("description", length = 600)
|
||||
val inbox = varchar("inbox", length = 255).uniqueIndex()
|
||||
val outbox = varchar("outbox", length = 255).uniqueIndex()
|
||||
val url = varchar("url", length = 255).uniqueIndex()
|
||||
|
||||
init {
|
||||
uniqueIndex(name, domain)
|
||||
}
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
package dev.usbharu.hideout.domain.model.job
|
||||
|
||||
import kjob.core.Job
|
||||
|
||||
object AcceptFollowJob : Job("AcceptFollowJob")
|
|
@ -0,0 +1,9 @@
|
|||
package dev.usbharu.hideout.domain.model.job
|
||||
|
||||
import kjob.core.Job
|
||||
|
||||
object ReceiveFollowJob : Job("ReceiveFollowJob"){
|
||||
val actor = string("actor")
|
||||
val follow = string("follow")
|
||||
val targetActor = string("targetActor")
|
||||
}
|
|
@ -10,6 +10,8 @@ interface IUserRepository {
|
|||
|
||||
suspend fun findByName(name: String): UserEntity?
|
||||
|
||||
suspend fun findByUrl(url:String):UserEntity?
|
||||
|
||||
suspend fun update(userEntity: UserEntity)
|
||||
|
||||
suspend fun delete(id: Long)
|
||||
|
|
|
@ -25,7 +25,10 @@ class UserRepository(private val database: Database) : IUserRepository {
|
|||
this[Users.name],
|
||||
this[Users.domain],
|
||||
this[Users.screenName],
|
||||
this[Users.description]
|
||||
this[Users.description],
|
||||
this[Users.inbox],
|
||||
this[Users.outbox],
|
||||
this[Users.url]
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -35,7 +38,10 @@ class UserRepository(private val database: Database) : IUserRepository {
|
|||
this[Users.name],
|
||||
this[Users.domain],
|
||||
this[Users.screenName],
|
||||
this[Users.description]
|
||||
this[Users.description],
|
||||
this[Users.inbox],
|
||||
this[Users.outbox],
|
||||
this[Users.url],
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -78,6 +84,12 @@ class UserRepository(private val database: Database) : IUserRepository {
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun findByUrl(url: String): UserEntity? {
|
||||
return query {
|
||||
Users.select { Users.url eq url }.singleOrNull()?.toUserEntity()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun findFollowersById(id: Long): List<UserEntity> {
|
||||
return query {
|
||||
val followers = Users.alias("FOLLOWERS")
|
||||
|
@ -91,7 +103,13 @@ class UserRepository(private val database: Database) : IUserRepository {
|
|||
onColumn = { UsersFollowers.followerId },
|
||||
otherColumn = { followers[Users.id] })
|
||||
|
||||
.slice(followers.get(Users.id), followers.get(Users.name), followers.get(Users.domain), followers.get(Users.screenName), followers.get(Users.description))
|
||||
.slice(
|
||||
followers.get(Users.id),
|
||||
followers.get(Users.name),
|
||||
followers.get(Users.domain),
|
||||
followers.get(Users.screenName),
|
||||
followers.get(Users.description)
|
||||
)
|
||||
.select { Users.id eq id }
|
||||
.map {
|
||||
UserEntity(
|
||||
|
@ -100,6 +118,9 @@ class UserRepository(private val database: Database) : IUserRepository {
|
|||
domain = it[followers[Users.domain]],
|
||||
screenName = it[followers[Users.screenName]],
|
||||
description = it[followers[Users.description]],
|
||||
inbox = it[followers[Users.inbox]],
|
||||
outbox = it[followers[Users.outbox]],
|
||||
url = it[followers[Users.url]],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,40 @@
|
|||
package dev.usbharu.hideout.service.activitypub
|
||||
|
||||
import com.fasterxml.jackson.module.kotlin.readValue
|
||||
import dev.usbharu.hideout.ap.Follow
|
||||
import dev.usbharu.hideout.config.Config
|
||||
import dev.usbharu.hideout.domain.model.ActivityPubResponse
|
||||
import dev.usbharu.hideout.domain.model.ActivityPubStringResponse
|
||||
import dev.usbharu.hideout.domain.model.job.AcceptFollowJob
|
||||
import dev.usbharu.hideout.domain.model.job.ReceiveFollowJob
|
||||
import dev.usbharu.hideout.plugins.postAp
|
||||
import dev.usbharu.hideout.service.job.JobQueueParentService
|
||||
import io.ktor.client.*
|
||||
import io.ktor.http.*
|
||||
import kjob.core.job.JobProps
|
||||
|
||||
class ActivityPubFollowServiceImpl(private val jobQueueParentService: JobQueueParentService) : ActivityPubFollowService {
|
||||
class ActivityPubFollowServiceImpl(
|
||||
private val jobQueueParentService: JobQueueParentService,
|
||||
private val activityPubUserService: ActivityPubUserService,
|
||||
private val httpClient: HttpClient
|
||||
) : ActivityPubFollowService {
|
||||
override suspend fun receiveFollow(follow: Follow): ActivityPubResponse {
|
||||
// TODO: Verify HTTP Signature
|
||||
jobQueueParentService.schedule(AcceptFollowJob)
|
||||
jobQueueParentService.schedule(ReceiveFollowJob) {
|
||||
props[it.actor] = follow.actor
|
||||
props[it.follow] = Config.configData.objectMapper.writeValueAsString(follow)
|
||||
props[it.targetActor] = follow.`object`
|
||||
}
|
||||
return ActivityPubStringResponse(HttpStatusCode.OK, "{}", ContentType.Application.Json)
|
||||
}
|
||||
|
||||
suspend fun receiveFollowJob(props: JobProps<ReceiveFollowJob>) {
|
||||
val actor = props[ReceiveFollowJob.actor]
|
||||
val person = activityPubUserService.fetchPerson(actor)
|
||||
val follow = Config.configData.objectMapper.readValue<Follow>(props[ReceiveFollowJob.follow])
|
||||
httpClient.postAp(
|
||||
urlString = person.inbox ?: throw IllegalArgumentException("inbox is not found"),
|
||||
username = "${props[ReceiveFollowJob.targetActor]}#pubkey",
|
||||
jsonLd = follow
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,4 +4,6 @@ import dev.usbharu.hideout.ap.Person
|
|||
|
||||
interface ActivityPubUserService {
|
||||
suspend fun getPersonByName(name:String):Person
|
||||
|
||||
suspend fun fetchPerson(url:String):Person
|
||||
}
|
||||
|
|
|
@ -1,15 +1,23 @@
|
|||
package dev.usbharu.hideout.service.activitypub
|
||||
|
||||
import com.fasterxml.jackson.module.kotlin.readValue
|
||||
import dev.usbharu.hideout.ap.Image
|
||||
import dev.usbharu.hideout.ap.Key
|
||||
import dev.usbharu.hideout.ap.Person
|
||||
import dev.usbharu.hideout.config.Config
|
||||
import dev.usbharu.hideout.exception.UserNotFoundException
|
||||
import dev.usbharu.hideout.service.IUserAuthService
|
||||
import dev.usbharu.hideout.service.impl.UserService
|
||||
import dev.usbharu.hideout.util.HttpUtil.Activity
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.request.*
|
||||
import io.ktor.client.statement.*
|
||||
import io.ktor.http.*
|
||||
|
||||
class ActivityPubUserServiceImpl(private val userService: UserService, private val userAuthService: IUserAuthService) :
|
||||
class ActivityPubUserServiceImpl(private val userService: UserService, private val userAuthService: IUserAuthService,private val httpClient: HttpClient) :
|
||||
ActivityPubUserService {
|
||||
override suspend fun getPersonByName(name: String): Person {
|
||||
// TODO: JOINで書き直し
|
||||
val userEntity = userService.findByName(name)
|
||||
val userAuthEntity = userAuthService.findByUserId(userEntity.id)
|
||||
val userUrl = "${Config.configData.url}/users/$name"
|
||||
|
@ -37,4 +45,41 @@ class ActivityPubUserServiceImpl(private val userService: UserService, private v
|
|||
)
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun fetchPerson(url: String): Person {
|
||||
return try {
|
||||
val userEntity = userService.findByUrl(url)
|
||||
val userAuthEntity = userAuthService.findByUsername(userEntity.name)
|
||||
return Person(
|
||||
type = emptyList(),
|
||||
name = userEntity.name,
|
||||
id = url,
|
||||
preferredUsername = userEntity.name,
|
||||
summary = userEntity.description,
|
||||
inbox = "$url/inbox",
|
||||
outbox = "$url/outbox",
|
||||
url = url,
|
||||
icon = Image(
|
||||
type = emptyList(),
|
||||
name = "$url/icon.png",
|
||||
mediaType = "image/png",
|
||||
url = "$url/icon.png"
|
||||
),
|
||||
publicKey = Key(
|
||||
type = emptyList(),
|
||||
name = "Public Key",
|
||||
id = "$url#pubkey",
|
||||
owner = url,
|
||||
publicKeyPem = userAuthEntity.publicKey
|
||||
)
|
||||
)
|
||||
|
||||
} catch (e:UserNotFoundException){
|
||||
val httpResponse = httpClient.get(url) {
|
||||
accept(ContentType.Application.Activity)
|
||||
}
|
||||
Config.configData.objectMapper.readValue<Person>(httpResponse.bodyAsText())
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,11 +9,7 @@ import dev.usbharu.hideout.repository.IUserAuthRepository
|
|||
import dev.usbharu.hideout.repository.IUserRepository
|
||||
import dev.usbharu.hideout.service.IUserAuthService
|
||||
import io.ktor.util.*
|
||||
import java.security.KeyPair
|
||||
import java.security.KeyPairGenerator
|
||||
import java.security.MessageDigest
|
||||
import java.security.PrivateKey
|
||||
import java.security.PublicKey
|
||||
import java.security.*
|
||||
import java.security.interfaces.RSAPrivateKey
|
||||
import java.security.interfaces.RSAPublicKey
|
||||
import java.util.*
|
||||
|
@ -35,11 +31,15 @@ class UserAuthService(
|
|||
}
|
||||
|
||||
override suspend fun registerAccount(username: String, hash: String) {
|
||||
val url = "${Config.configData.url}/users/$username"
|
||||
val registerUser = User(
|
||||
name = username,
|
||||
domain = Config.configData.domain,
|
||||
screenName = username,
|
||||
description = ""
|
||||
description = "",
|
||||
inbox = "$url/inbox",
|
||||
outbox = "$url/outbox",
|
||||
url = url
|
||||
)
|
||||
val createdUser = userRepository.create(registerUser)
|
||||
|
||||
|
@ -83,8 +83,6 @@ class UserAuthService(
|
|||
}
|
||||
|
||||
|
||||
|
||||
|
||||
companion object {
|
||||
val sha256: MessageDigest = MessageDigest.getInstance("SHA-256")
|
||||
}
|
||||
|
|
|
@ -26,6 +26,10 @@ class UserService(private val userRepository: IUserRepository) {
|
|||
?: throw UserNotFoundException("$name was not found.")
|
||||
}
|
||||
|
||||
suspend fun findByUrl(url: String): UserEntity {
|
||||
return userRepository.findByUrl(url) ?: throw UserNotFoundException("$url was not found.")
|
||||
}
|
||||
|
||||
suspend fun create(user: User): UserEntity {
|
||||
return userRepository.create(user)
|
||||
}
|
||||
|
|
|
@ -66,8 +66,12 @@ class WebFingerService(
|
|||
userModel.preferredUsername ?: throw IllegalStateException(),
|
||||
domain,
|
||||
userName,
|
||||
userModel.summary.orEmpty()
|
||||
userModel.summary.orEmpty(),
|
||||
"",
|
||||
"",
|
||||
""
|
||||
)
|
||||
TODO()
|
||||
return userService.create(user)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue