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.job.KJobJobQueueParentService
|
||||||
import dev.usbharu.hideout.service.signature.HttpSignatureVerifyService
|
import dev.usbharu.hideout.service.signature.HttpSignatureVerifyService
|
||||||
import dev.usbharu.hideout.service.signature.HttpSignatureVerifyServiceImpl
|
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 io.ktor.server.application.*
|
||||||
import org.jetbrains.exposed.sql.Database
|
import org.jetbrains.exposed.sql.Database
|
||||||
import org.koin.ktor.ext.inject
|
import org.koin.ktor.ext.inject
|
||||||
|
@ -54,14 +57,23 @@ fun Application.module() {
|
||||||
single<IUserAuthRepository> { UserAuthRepository(get()) }
|
single<IUserAuthRepository> { UserAuthRepository(get()) }
|
||||||
single<IUserAuthService> { UserAuthService(get(), get()) }
|
single<IUserAuthService> { UserAuthService(get(), get()) }
|
||||||
single<HttpSignatureVerifyService> { HttpSignatureVerifyServiceImpl(get()) }
|
single<HttpSignatureVerifyService> { HttpSignatureVerifyServiceImpl(get()) }
|
||||||
single<JobQueueParentService> { val kJobJobQueueService = KJobJobQueueParentService(get())
|
single<JobQueueParentService> {
|
||||||
|
val kJobJobQueueService = KJobJobQueueParentService(get())
|
||||||
kJobJobQueueService.init(listOf())
|
kJobJobQueueService.init(listOf())
|
||||||
kJobJobQueueService
|
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<ActivityPubService> { ActivityPubServiceImpl(get()) }
|
||||||
single<UserService> { UserService(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
|
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(
|
data class UserEntity(
|
||||||
val id: Long,
|
val id: Long,
|
||||||
val name: String,
|
val name: String,
|
||||||
val domain:String,
|
val domain: String,
|
||||||
val screenName: 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") {
|
object Users : LongIdTable("users") {
|
||||||
|
@ -19,6 +39,10 @@ object Users : LongIdTable("users") {
|
||||||
val domain = varchar("domain", length = 255)
|
val domain = varchar("domain", length = 255)
|
||||||
val screenName = varchar("screen_name", length = 64)
|
val screenName = varchar("screen_name", length = 64)
|
||||||
val description = varchar("description", length = 600)
|
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 {
|
init {
|
||||||
uniqueIndex(name, domain)
|
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 findByName(name: String): UserEntity?
|
||||||
|
|
||||||
|
suspend fun findByUrl(url:String):UserEntity?
|
||||||
|
|
||||||
suspend fun update(userEntity: UserEntity)
|
suspend fun update(userEntity: UserEntity)
|
||||||
|
|
||||||
suspend fun delete(id: Long)
|
suspend fun delete(id: Long)
|
||||||
|
|
|
@ -25,7 +25,10 @@ class UserRepository(private val database: Database) : IUserRepository {
|
||||||
this[Users.name],
|
this[Users.name],
|
||||||
this[Users.domain],
|
this[Users.domain],
|
||||||
this[Users.screenName],
|
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.name],
|
||||||
this[Users.domain],
|
this[Users.domain],
|
||||||
this[Users.screenName],
|
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> {
|
override suspend fun findFollowersById(id: Long): List<UserEntity> {
|
||||||
return query {
|
return query {
|
||||||
val followers = Users.alias("FOLLOWERS")
|
val followers = Users.alias("FOLLOWERS")
|
||||||
|
@ -91,7 +103,13 @@ class UserRepository(private val database: Database) : IUserRepository {
|
||||||
onColumn = { UsersFollowers.followerId },
|
onColumn = { UsersFollowers.followerId },
|
||||||
otherColumn = { followers[Users.id] })
|
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 }
|
.select { Users.id eq id }
|
||||||
.map {
|
.map {
|
||||||
UserEntity(
|
UserEntity(
|
||||||
|
@ -100,6 +118,9 @@ class UserRepository(private val database: Database) : IUserRepository {
|
||||||
domain = it[followers[Users.domain]],
|
domain = it[followers[Users.domain]],
|
||||||
screenName = it[followers[Users.screenName]],
|
screenName = it[followers[Users.screenName]],
|
||||||
description = it[followers[Users.description]],
|
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
|
package dev.usbharu.hideout.service.activitypub
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.module.kotlin.readValue
|
||||||
import dev.usbharu.hideout.ap.Follow
|
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.ActivityPubResponse
|
||||||
import dev.usbharu.hideout.domain.model.ActivityPubStringResponse
|
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 dev.usbharu.hideout.service.job.JobQueueParentService
|
||||||
|
import io.ktor.client.*
|
||||||
import io.ktor.http.*
|
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 {
|
override suspend fun receiveFollow(follow: Follow): ActivityPubResponse {
|
||||||
// TODO: Verify HTTP Signature
|
// TODO: Verify HTTP Signature
|
||||||
jobQueueParentService.schedule(AcceptFollowJob)
|
jobQueueParentService.schedule(ReceiveFollowJob) {
|
||||||
return ActivityPubStringResponse(HttpStatusCode.OK,"{}",ContentType.Application.Json)
|
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 {
|
interface ActivityPubUserService {
|
||||||
suspend fun getPersonByName(name:String):Person
|
suspend fun getPersonByName(name:String):Person
|
||||||
|
|
||||||
|
suspend fun fetchPerson(url:String):Person
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,23 @@
|
||||||
package dev.usbharu.hideout.service.activitypub
|
package dev.usbharu.hideout.service.activitypub
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.module.kotlin.readValue
|
||||||
import dev.usbharu.hideout.ap.Image
|
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.exception.UserNotFoundException
|
||||||
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 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 {
|
ActivityPubUserService {
|
||||||
override suspend fun getPersonByName(name: String): Person {
|
override suspend fun getPersonByName(name: String): Person {
|
||||||
|
// TODO: JOINで書き直し
|
||||||
val userEntity = userService.findByName(name)
|
val userEntity = userService.findByName(name)
|
||||||
val userAuthEntity = userAuthService.findByUserId(userEntity.id)
|
val userAuthEntity = userAuthService.findByUserId(userEntity.id)
|
||||||
val userUrl = "${Config.configData.url}/users/$name"
|
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.repository.IUserRepository
|
||||||
import dev.usbharu.hideout.service.IUserAuthService
|
import dev.usbharu.hideout.service.IUserAuthService
|
||||||
import io.ktor.util.*
|
import io.ktor.util.*
|
||||||
import java.security.KeyPair
|
import java.security.*
|
||||||
import java.security.KeyPairGenerator
|
|
||||||
import java.security.MessageDigest
|
|
||||||
import java.security.PrivateKey
|
|
||||||
import java.security.PublicKey
|
|
||||||
import java.security.interfaces.RSAPrivateKey
|
import java.security.interfaces.RSAPrivateKey
|
||||||
import java.security.interfaces.RSAPublicKey
|
import java.security.interfaces.RSAPublicKey
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
@ -35,11 +31,15 @@ class UserAuthService(
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun registerAccount(username: String, hash: String) {
|
override suspend fun registerAccount(username: String, hash: String) {
|
||||||
|
val url = "${Config.configData.url}/users/$username"
|
||||||
val registerUser = User(
|
val registerUser = User(
|
||||||
name = username,
|
name = username,
|
||||||
domain = Config.configData.domain,
|
domain = Config.configData.domain,
|
||||||
screenName = username,
|
screenName = username,
|
||||||
description = ""
|
description = "",
|
||||||
|
inbox = "$url/inbox",
|
||||||
|
outbox = "$url/outbox",
|
||||||
|
url = url
|
||||||
)
|
)
|
||||||
val createdUser = userRepository.create(registerUser)
|
val createdUser = userRepository.create(registerUser)
|
||||||
|
|
||||||
|
@ -83,8 +83,6 @@ class UserAuthService(
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val sha256: MessageDigest = MessageDigest.getInstance("SHA-256")
|
val sha256: MessageDigest = MessageDigest.getInstance("SHA-256")
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,10 @@ class UserService(private val userRepository: IUserRepository) {
|
||||||
?: throw UserNotFoundException("$name was not found.")
|
?: 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 {
|
suspend fun create(user: User): UserEntity {
|
||||||
return userRepository.create(user)
|
return userRepository.create(user)
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,8 +66,12 @@ class WebFingerService(
|
||||||
userModel.preferredUsername ?: throw IllegalStateException(),
|
userModel.preferredUsername ?: throw IllegalStateException(),
|
||||||
domain,
|
domain,
|
||||||
userName,
|
userName,
|
||||||
userModel.summary.orEmpty()
|
userModel.summary.orEmpty(),
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
""
|
||||||
)
|
)
|
||||||
|
TODO()
|
||||||
return userService.create(user)
|
return userService.create(user)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue