feat: DBにfollowing,followersとkeyidを追加

This commit is contained in:
usbharu 2023-10-19 12:17:02 +09:00
parent 72289625b4
commit 34ed98c82b
11 changed files with 142 additions and 63 deletions

View File

@ -9,6 +9,8 @@ open class Person : Object {
private var icon: Image? = null private var icon: Image? = null
var publicKey: Key? = null var publicKey: Key? = null
var endpoints: Map<String, String> = emptyMap() var endpoints: Map<String, String> = emptyMap()
var following: String? = null
var followers: String? = null
protected constructor() : super() protected constructor() : super()
@ -24,7 +26,9 @@ open class Person : Object {
url: String?, url: String?,
icon: Image?, icon: Image?,
publicKey: Key?, publicKey: Key?,
endpoints: Map<String, String> = emptyMap() endpoints: Map<String, String> = emptyMap(),
followers: String?,
following: String?
) : super(add(type, "Person"), name, id = id) { ) : super(add(type, "Person"), name, id = id) {
this.preferredUsername = preferredUsername this.preferredUsername = preferredUsername
this.summary = summary this.summary = summary
@ -34,6 +38,8 @@ open class Person : Object {
this.icon = icon this.icon = icon
this.publicKey = publicKey this.publicKey = publicKey
this.endpoints = endpoints this.endpoints = endpoints
this.followers = followers
this.following = following
} }
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {

View File

@ -9,4 +9,7 @@ data class RemoteUserCreateDto(
val outbox: String, val outbox: String,
val url: String, val url: String,
val publicKey: String, val publicKey: String,
val keyId: String,
val followers: String?,
val following: String?
) )

View File

@ -16,15 +16,17 @@ data class User private constructor(
val url: String, val url: String,
val publicKey: String, val publicKey: String,
val privateKey: String? = null, val privateKey: String? = null,
val createdAt: Instant val createdAt: Instant,
val keyId: String,
val followers: String? = null,
val following: String? = null
) { ) {
override fun toString(): String { override fun toString(): String {
return "User(id=$id, name='$name', domain='$domain', screenName='$screenName', description='$description'," + return "User(id=$id, name='$name', domain='$domain', screenName='$screenName', description='$description', password=$password, inbox='$inbox', outbox='$outbox', url='$url', publicKey='$publicKey', privateKey=$privateKey, createdAt=$createdAt, keyId='$keyId', followers=$followers, following=$following)"
" password=****, inbox='$inbox', outbox='$outbox', url='$url', publicKey='$publicKey'," +
" privateKey=****, createdAt=$createdAt)"
} }
companion object { companion object {
private val logger = LoggerFactory.getLogger(User::class.java) private val logger = LoggerFactory.getLogger(User::class.java)
@Suppress("LongParameterList", "FunctionMinLength") @Suppress("LongParameterList", "FunctionMinLength")
@ -40,7 +42,10 @@ data class User private constructor(
url: String, url: String,
publicKey: String, publicKey: String,
privateKey: String? = null, privateKey: String? = null,
createdAt: Instant createdAt: Instant,
keyId: String,
following: String? = null,
followers: String? = null
): User { ): User {
val characterLimit = Config.configData.characterLimit val characterLimit = Config.configData.characterLimit
@ -115,6 +120,10 @@ data class User private constructor(
"outbox must not exceed ${characterLimit.general.url} characters." "outbox must not exceed ${characterLimit.general.url} characters."
} }
require(keyId.isNotBlank()) {
"keyId must contain non-blank characters."
}
return User( return User(
id = id, id = id,
name = limitedName, name = limitedName,
@ -127,7 +136,10 @@ data class User private constructor(
url = url, url = url,
publicKey = publicKey, publicKey = publicKey,
privateKey = privateKey, privateKey = privateKey,
createdAt = createdAt createdAt = createdAt,
keyId = keyId,
followers = followers,
following = following
) )
} }
} }

View File

@ -34,7 +34,10 @@ class FollowerQueryServiceImpl : FollowerQueryService {
followers[Users.url], followers[Users.url],
followers[Users.publicKey], followers[Users.publicKey],
followers[Users.privateKey], followers[Users.privateKey],
followers[Users.createdAt] followers[Users.createdAt],
followers[Users.keyId],
followers[Users.following],
followers[Users.followers]
) )
.select { Users.id eq id } .select { Users.id eq id }
.map { .map {
@ -50,7 +53,10 @@ class FollowerQueryServiceImpl : FollowerQueryService {
url = it[followers[Users.url]], url = it[followers[Users.url]],
publicKey = it[followers[Users.publicKey]], publicKey = it[followers[Users.publicKey]],
privateKey = it[followers[Users.privateKey]], privateKey = it[followers[Users.privateKey]],
createdAt = Instant.ofEpochMilli(it[followers[Users.createdAt]]) createdAt = Instant.ofEpochMilli(it[followers[Users.createdAt]]),
keyId = it[followers[Users.keyId]],
followers = it[followers[Users.followers]],
following = it[followers[Users.following]]
) )
} }
} }
@ -79,7 +85,10 @@ class FollowerQueryServiceImpl : FollowerQueryService {
followers[Users.url], followers[Users.url],
followers[Users.publicKey], followers[Users.publicKey],
followers[Users.privateKey], followers[Users.privateKey],
followers[Users.createdAt] followers[Users.createdAt],
followers[Users.keyId],
followers[Users.following],
followers[Users.followers]
) )
.select { Users.name eq name and (Users.domain eq domain) } .select { Users.name eq name and (Users.domain eq domain) }
.map { .map {
@ -95,7 +104,10 @@ class FollowerQueryServiceImpl : FollowerQueryService {
url = it[followers[Users.url]], url = it[followers[Users.url]],
publicKey = it[followers[Users.publicKey]], publicKey = it[followers[Users.publicKey]],
privateKey = it[followers[Users.privateKey]], privateKey = it[followers[Users.privateKey]],
createdAt = Instant.ofEpochMilli(it[followers[Users.createdAt]]) createdAt = Instant.ofEpochMilli(it[followers[Users.createdAt]]),
keyId = it[followers[Users.keyId]],
followers = it[followers[Users.followers]],
following = it[followers[Users.following]]
) )
} }
} }
@ -124,7 +136,10 @@ class FollowerQueryServiceImpl : FollowerQueryService {
followers[Users.url], followers[Users.url],
followers[Users.publicKey], followers[Users.publicKey],
followers[Users.privateKey], followers[Users.privateKey],
followers[Users.createdAt] followers[Users.createdAt],
followers[Users.keyId],
followers[Users.following],
followers[Users.followers]
) )
.select { followers[Users.id] eq id } .select { followers[Users.id] eq id }
.map { .map {
@ -140,7 +155,10 @@ class FollowerQueryServiceImpl : FollowerQueryService {
url = it[followers[Users.url]], url = it[followers[Users.url]],
publicKey = it[followers[Users.publicKey]], publicKey = it[followers[Users.publicKey]],
privateKey = it[followers[Users.privateKey]], privateKey = it[followers[Users.privateKey]],
createdAt = Instant.ofEpochMilli(it[followers[Users.createdAt]]) createdAt = Instant.ofEpochMilli(it[followers[Users.createdAt]]),
keyId = it[followers[Users.keyId]],
followers = it[followers[Users.followers]],
following = it[followers[Users.following]]
) )
} }
} }
@ -169,7 +187,10 @@ class FollowerQueryServiceImpl : FollowerQueryService {
followers[Users.url], followers[Users.url],
followers[Users.publicKey], followers[Users.publicKey],
followers[Users.privateKey], followers[Users.privateKey],
followers[Users.createdAt] followers[Users.createdAt],
followers[Users.keyId],
followers[Users.following],
followers[Users.followers]
) )
.select { followers[Users.name] eq name and (followers[Users.domain] eq domain) } .select { followers[Users.name] eq name and (followers[Users.domain] eq domain) }
.map { .map {
@ -185,7 +206,10 @@ class FollowerQueryServiceImpl : FollowerQueryService {
url = it[followers[Users.url]], url = it[followers[Users.url]],
publicKey = it[followers[Users.publicKey]], publicKey = it[followers[Users.publicKey]],
privateKey = it[followers[Users.privateKey]], privateKey = it[followers[Users.privateKey]],
createdAt = Instant.ofEpochMilli(it[followers[Users.createdAt]]) createdAt = Instant.ofEpochMilli(it[followers[Users.createdAt]]),
keyId = it[followers[Users.keyId]],
followers = it[followers[Users.followers]],
following = it[followers[Users.following]]
) )
} }
} }

View File

@ -89,6 +89,9 @@ object Users : Table("users") {
length = Config.configData.characterLimit.general.privateKey length = Config.configData.characterLimit.general.privateKey
).nullable() ).nullable()
val createdAt: Column<Long> = long("created_at") val createdAt: Column<Long> = long("created_at")
val keyId = varchar("key_id", length = Config.configData.characterLimit.general.url)
val following = varchar("following", length = Config.configData.characterLimit.general.url).nullable()
val followers = varchar("followers", length = Config.configData.characterLimit.general.url).nullable()
override val primaryKey: PrimaryKey = PrimaryKey(id) override val primaryKey: PrimaryKey = PrimaryKey(id)
@ -110,7 +113,10 @@ fun ResultRow.toUser(): User {
url = this[Users.url], url = this[Users.url],
publicKey = this[Users.publicKey], publicKey = this[Users.publicKey],
privateKey = this[Users.privateKey], privateKey = this[Users.privateKey],
createdAt = Instant.ofEpochMilli((this[Users.createdAt])) createdAt = Instant.ofEpochMilli((this[Users.createdAt])),
keyId = this[Users.keyId],
followers = this[Users.followers],
following = this[Users.following]
) )
} }

View File

@ -64,11 +64,13 @@ class APUserServiceImpl(
publicKey = Key( publicKey = Key(
type = emptyList(), type = emptyList(),
name = "Public Key", name = "Public Key",
id = "$userUrl#pubkey", id = userEntity.keyId,
owner = userUrl, owner = userUrl,
publicKeyPem = userEntity.publicKey publicKeyPem = userEntity.publicKey
), ),
endpoints = mapOf("sharedInbox" to "${applicationConfig.url}/inbox") endpoints = mapOf("sharedInbox" to "${applicationConfig.url}/inbox"),
followers = userEntity.followers,
following = userEntity.following
) )
} }
@ -96,11 +98,13 @@ class APUserServiceImpl(
publicKey = Key( publicKey = Key(
type = emptyList(), type = emptyList(),
name = "Public Key", name = "Public Key",
id = "$url#pubkey", id = userEntity.keyId,
owner = url, owner = url,
publicKeyPem = userEntity.publicKey publicKeyPem = userEntity.publicKey
), ),
endpoints = mapOf("sharedInbox" to "${applicationConfig.url}/inbox") endpoints = mapOf("sharedInbox" to "${applicationConfig.url}/inbox"),
followers = userEntity.followers,
following = userEntity.following
) to userEntity ) to userEntity
} catch (ignore: FailedToGetResourcesException) { } catch (ignore: FailedToGetResourcesException) {
val person = apResourceResolveService.resolve<Person>(url, null as Long?) val person = apResourceResolveService.resolve<Person>(url, null as Long?)
@ -118,6 +122,9 @@ class APUserServiceImpl(
url = url, url = url,
publicKey = person.publicKey?.publicKeyPem publicKey = person.publicKey?.publicKeyPem
?: throw IllegalActivityPubObjectException("publicKey is null"), ?: throw IllegalActivityPubObjectException("publicKey is null"),
keyId = person.publicKey?.id ?: throw IllegalActivityPubObjectException("publicKey keyId is null"),
following = person.following,
followers = person.followers
) )
) )
} }

View File

@ -34,6 +34,7 @@ class UserServiceImpl(
val nextId = userRepository.nextId() val nextId = userRepository.nextId()
val hashedPassword = userAuthService.hash(user.password) val hashedPassword = userAuthService.hash(user.password)
val keyPair = userAuthService.generateKeyPair() val keyPair = userAuthService.generateKeyPair()
val userUrl = "${applicationConfig.url}/users/${user.name}"
val userEntity = User.of( val userEntity = User.of(
id = nextId, id = nextId,
name = user.name, name = user.name,
@ -41,12 +42,15 @@ class UserServiceImpl(
screenName = user.screenName, screenName = user.screenName,
description = user.description, description = user.description,
password = hashedPassword, password = hashedPassword,
inbox = "${applicationConfig.url}/users/${user.name}/inbox", inbox = "$userUrl/inbox",
outbox = "${applicationConfig.url}/users/${user.name}/outbox", outbox = "$userUrl/outbox",
url = "${applicationConfig.url}/users/${user.name}", url = userUrl,
publicKey = keyPair.public.toPem(), publicKey = keyPair.public.toPem(),
privateKey = keyPair.private.toPem(), privateKey = keyPair.private.toPem(),
createdAt = Instant.now() createdAt = Instant.now(),
following = "$userUrl/following",
followers = "$userUrl/followers",
keyId = "$userUrl#pubkey"
) )
return userRepository.save(userEntity) return userRepository.save(userEntity)
} }
@ -63,7 +67,10 @@ class UserServiceImpl(
outbox = user.outbox, outbox = user.outbox,
url = user.url, url = user.url,
publicKey = user.publicKey, publicKey = user.publicKey,
createdAt = Instant.now() createdAt = Instant.now(),
followers = user.followers,
following = user.following,
keyId = user.keyId
) )
return try { return try {
userRepository.save(userEntity) userRepository.save(userEntity)

View File

@ -48,7 +48,8 @@ class APNoteServiceImplTest {
"https://follower.example.com", "https://follower.example.com",
"https://follower.example.com", "https://follower.example.com",
publicKey = "", publicKey = "",
createdAt = Instant.now() createdAt = Instant.now(),
keyId = "a"
), ),
User.of( User.of(
3L, 3L,
@ -61,7 +62,8 @@ class APNoteServiceImplTest {
"https://follower2.example.com", "https://follower2.example.com",
"https://follower2.example.com", "https://follower2.example.com",
publicKey = "", publicKey = "",
createdAt = Instant.now() createdAt = Instant.now(),
keyId = "a"
) )
) )
val userQueryService = mock<UserQueryService> { val userQueryService = mock<UserQueryService> {
@ -77,7 +79,8 @@ class APNoteServiceImplTest {
"https://example.com", "https://example.com",
publicKey = "", publicKey = "",
privateKey = "a", privateKey = "a",
createdAt = Instant.now() createdAt = Instant.now(),
keyId = "a"
) )
} }
val followerQueryService = mock<FollowerQueryService> { val followerQueryService = mock<FollowerQueryService> {

View File

@ -102,7 +102,9 @@ class APReceiveFollowServiceImplTest {
id = "https://follower.example.com#main-key", id = "https://follower.example.com#main-key",
owner = "https://follower.example.com", owner = "https://follower.example.com",
publicKeyPem = "BEGIN PUBLIC KEY...END PUBLIC KEY", publicKeyPem = "BEGIN PUBLIC KEY...END PUBLIC KEY",
) ),
followers = "",
following = ""
) )
val apUserService = mock<APUserService> { val apUserService = mock<APUserService> {
@ -111,30 +113,32 @@ class APReceiveFollowServiceImplTest {
val userQueryService = mock<UserQueryService> { val userQueryService = mock<UserQueryService> {
onBlocking { findByUrl(eq("https://example.com")) } doReturn onBlocking { findByUrl(eq("https://example.com")) } doReturn
User.of( User.of(
id = 1L, id = 1L,
name = "test", name = "test",
domain = "example.com", domain = "example.com",
screenName = "testUser", screenName = "testUser",
description = "This user is test user.", description = "This user is test user.",
inbox = "https://example.com/inbox", inbox = "https://example.com/inbox",
outbox = "https://example.com/outbox", outbox = "https://example.com/outbox",
url = "https://example.com", url = "https://example.com",
publicKey = "", publicKey = "",
createdAt = Instant.now() createdAt = Instant.now(),
) keyId = "a"
)
onBlocking { findByUrl(eq("https://follower.example.com")) } doReturn onBlocking { findByUrl(eq("https://follower.example.com")) } doReturn
User.of( User.of(
id = 2L, id = 2L,
name = "follower", name = "follower",
domain = "follower.example.com", domain = "follower.example.com",
screenName = "followerUser", screenName = "followerUser",
description = "This user is test follower user.", description = "This user is test follower user.",
inbox = "https://follower.example.com/inbox", inbox = "https://follower.example.com/inbox",
outbox = "https://follower.example.com/outbox", outbox = "https://follower.example.com/outbox",
url = "https://follower.example.com", url = "https://follower.example.com",
publicKey = "", publicKey = "",
createdAt = Instant.now() createdAt = Instant.now(),
) keyId = "a"
)
} }
val userService = mock<UserService> { val userService = mock<UserService> {

View File

@ -47,7 +47,8 @@ class APResourceResolveServiceImplTest {
"https://follower.example.com", "https://follower.example.com",
"https://follower.example.com", "https://follower.example.com",
publicKey = "", publicKey = "",
createdAt = Instant.now() createdAt = Instant.now(),
keyId = ""
) )
) )
@ -82,7 +83,8 @@ class APResourceResolveServiceImplTest {
"https://follower.example.com", "https://follower.example.com",
"https://follower.example.com", "https://follower.example.com",
publicKey = "", publicKey = "",
createdAt = Instant.now() createdAt = Instant.now(),
keyId = ""
) )
) )
@ -120,7 +122,8 @@ class APResourceResolveServiceImplTest {
"https://follower.example.com", "https://follower.example.com",
"https://follower.example.com", "https://follower.example.com",
publicKey = "", publicKey = "",
createdAt = Instant.now() createdAt = Instant.now(),
keyId = ""
) )
) )
@ -169,7 +172,8 @@ class APResourceResolveServiceImplTest {
"https://follower.example.com", "https://follower.example.com",
"https://follower.example.com", "https://follower.example.com",
publicKey = "", publicKey = "",
createdAt = Instant.now() createdAt = Instant.now(),
keyId = ""
) )
) )

View File

@ -58,14 +58,17 @@ class UserServiceTest {
} }
val userService = UserServiceImpl(userRepository, mock(), mock(), mock(), mock(), testApplicationConfig) val userService = UserServiceImpl(userRepository, mock(), mock(), mock(), mock(), testApplicationConfig)
val user = RemoteUserCreateDto( val user = RemoteUserCreateDto(
"test", name = "test",
"example.com", domain = "example.com",
"testUser", screenName = "testUser",
"test user", description = "test user",
"https://example.com/inbox", inbox = "https://example.com/inbox",
"https://example.com/outbox", outbox = "https://example.com/outbox",
"https://example.com", url = "https://example.com",
"-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----" publicKey = "-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----",
keyId = "a",
following = "",
followers = ""
) )
userService.createRemoteUser(user) userService.createRemoteUser(user)
verify(userRepository, times(1)).save(any()) verify(userRepository, times(1)).save(any())