diff --git a/src/main/kotlin/dev/usbharu/hideout/domain/model/ap/Person.kt b/src/main/kotlin/dev/usbharu/hideout/domain/model/ap/Person.kt index 5c7f1176..e7625b44 100644 --- a/src/main/kotlin/dev/usbharu/hideout/domain/model/ap/Person.kt +++ b/src/main/kotlin/dev/usbharu/hideout/domain/model/ap/Person.kt @@ -9,6 +9,8 @@ open class Person : Object { private var icon: Image? = null var publicKey: Key? = null var endpoints: Map = emptyMap() + var following: String? = null + var followers: String? = null protected constructor() : super() @@ -24,7 +26,9 @@ open class Person : Object { url: String?, icon: Image?, publicKey: Key?, - endpoints: Map = emptyMap() + endpoints: Map = emptyMap(), + followers: String?, + following: String? ) : super(add(type, "Person"), name, id = id) { this.preferredUsername = preferredUsername this.summary = summary @@ -34,6 +38,8 @@ open class Person : Object { this.icon = icon this.publicKey = publicKey this.endpoints = endpoints + this.followers = followers + this.following = following } override fun equals(other: Any?): Boolean { diff --git a/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/dto/RemoteUserCreateDto.kt b/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/dto/RemoteUserCreateDto.kt index f36eed2a..2a6a34fb 100644 --- a/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/dto/RemoteUserCreateDto.kt +++ b/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/dto/RemoteUserCreateDto.kt @@ -9,4 +9,7 @@ data class RemoteUserCreateDto( val outbox: String, val url: String, val publicKey: String, + val keyId: String, + val followers: String?, + val following: String? ) diff --git a/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/entity/User.kt b/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/entity/User.kt index 9a2c4e4a..2beed536 100644 --- a/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/entity/User.kt +++ b/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/entity/User.kt @@ -16,15 +16,17 @@ data class User private constructor( val url: String, val publicKey: String, 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 { - return "User(id=$id, name='$name', domain='$domain', screenName='$screenName', description='$description'," + - " password=****, inbox='$inbox', outbox='$outbox', url='$url', publicKey='$publicKey'," + - " privateKey=****, createdAt=$createdAt)" + 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)" } companion object { + private val logger = LoggerFactory.getLogger(User::class.java) @Suppress("LongParameterList", "FunctionMinLength") @@ -40,7 +42,10 @@ data class User private constructor( url: String, publicKey: String, privateKey: String? = null, - createdAt: Instant + createdAt: Instant, + keyId: String, + following: String? = null, + followers: String? = null ): User { val characterLimit = Config.configData.characterLimit @@ -115,6 +120,10 @@ data class User private constructor( "outbox must not exceed ${characterLimit.general.url} characters." } + require(keyId.isNotBlank()) { + "keyId must contain non-blank characters." + } + return User( id = id, name = limitedName, @@ -127,7 +136,10 @@ data class User private constructor( url = url, publicKey = publicKey, privateKey = privateKey, - createdAt = createdAt + createdAt = createdAt, + keyId = keyId, + followers = followers, + following = following ) } } diff --git a/src/main/kotlin/dev/usbharu/hideout/query/FollowerQueryServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/query/FollowerQueryServiceImpl.kt index bb4f56fc..10be4068 100644 --- a/src/main/kotlin/dev/usbharu/hideout/query/FollowerQueryServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/query/FollowerQueryServiceImpl.kt @@ -34,7 +34,10 @@ class FollowerQueryServiceImpl : FollowerQueryService { followers[Users.url], followers[Users.publicKey], followers[Users.privateKey], - followers[Users.createdAt] + followers[Users.createdAt], + followers[Users.keyId], + followers[Users.following], + followers[Users.followers] ) .select { Users.id eq id } .map { @@ -50,7 +53,10 @@ class FollowerQueryServiceImpl : FollowerQueryService { url = it[followers[Users.url]], publicKey = it[followers[Users.publicKey]], 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.publicKey], 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) } .map { @@ -95,7 +104,10 @@ class FollowerQueryServiceImpl : FollowerQueryService { url = it[followers[Users.url]], publicKey = it[followers[Users.publicKey]], 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.publicKey], followers[Users.privateKey], - followers[Users.createdAt] + followers[Users.createdAt], + followers[Users.keyId], + followers[Users.following], + followers[Users.followers] ) .select { followers[Users.id] eq id } .map { @@ -140,7 +155,10 @@ class FollowerQueryServiceImpl : FollowerQueryService { url = it[followers[Users.url]], publicKey = it[followers[Users.publicKey]], 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.publicKey], 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) } .map { @@ -185,7 +206,10 @@ class FollowerQueryServiceImpl : FollowerQueryService { url = it[followers[Users.url]], publicKey = it[followers[Users.publicKey]], 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]] ) } } diff --git a/src/main/kotlin/dev/usbharu/hideout/repository/UserRepositoryImpl.kt b/src/main/kotlin/dev/usbharu/hideout/repository/UserRepositoryImpl.kt index 10fb3a2e..f8b1c7c3 100644 --- a/src/main/kotlin/dev/usbharu/hideout/repository/UserRepositoryImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/repository/UserRepositoryImpl.kt @@ -89,6 +89,9 @@ object Users : Table("users") { length = Config.configData.characterLimit.general.privateKey ).nullable() val createdAt: Column = 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) @@ -110,7 +113,10 @@ fun ResultRow.toUser(): User { url = this[Users.url], publicKey = this[Users.publicKey], 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] ) } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/ap/APUserService.kt b/src/main/kotlin/dev/usbharu/hideout/service/ap/APUserService.kt index 5909d3f5..23f7b57b 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/ap/APUserService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/ap/APUserService.kt @@ -64,11 +64,13 @@ class APUserServiceImpl( publicKey = Key( type = emptyList(), name = "Public Key", - id = "$userUrl#pubkey", + id = userEntity.keyId, owner = userUrl, 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( type = emptyList(), name = "Public Key", - id = "$url#pubkey", + id = userEntity.keyId, owner = url, 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 } catch (ignore: FailedToGetResourcesException) { val person = apResourceResolveService.resolve(url, null as Long?) @@ -118,6 +122,9 @@ class APUserServiceImpl( url = url, publicKey = person.publicKey?.publicKeyPem ?: throw IllegalActivityPubObjectException("publicKey is null"), + keyId = person.publicKey?.id ?: throw IllegalActivityPubObjectException("publicKey keyId is null"), + following = person.following, + followers = person.followers ) ) } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/user/UserServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/user/UserServiceImpl.kt index 0f865526..bb349ff3 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/user/UserServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/user/UserServiceImpl.kt @@ -34,6 +34,7 @@ class UserServiceImpl( val nextId = userRepository.nextId() val hashedPassword = userAuthService.hash(user.password) val keyPair = userAuthService.generateKeyPair() + val userUrl = "${applicationConfig.url}/users/${user.name}" val userEntity = User.of( id = nextId, name = user.name, @@ -41,12 +42,15 @@ class UserServiceImpl( screenName = user.screenName, description = user.description, password = hashedPassword, - inbox = "${applicationConfig.url}/users/${user.name}/inbox", - outbox = "${applicationConfig.url}/users/${user.name}/outbox", - url = "${applicationConfig.url}/users/${user.name}", + inbox = "$userUrl/inbox", + outbox = "$userUrl/outbox", + url = userUrl, publicKey = keyPair.public.toPem(), privateKey = keyPair.private.toPem(), - createdAt = Instant.now() + createdAt = Instant.now(), + following = "$userUrl/following", + followers = "$userUrl/followers", + keyId = "$userUrl#pubkey" ) return userRepository.save(userEntity) } @@ -63,7 +67,10 @@ class UserServiceImpl( outbox = user.outbox, url = user.url, publicKey = user.publicKey, - createdAt = Instant.now() + createdAt = Instant.now(), + followers = user.followers, + following = user.following, + keyId = user.keyId ) return try { userRepository.save(userEntity) diff --git a/src/test/kotlin/dev/usbharu/hideout/service/ap/APNoteServiceImplTest.kt b/src/test/kotlin/dev/usbharu/hideout/service/ap/APNoteServiceImplTest.kt index 9d6975f7..bf3b82d5 100644 --- a/src/test/kotlin/dev/usbharu/hideout/service/ap/APNoteServiceImplTest.kt +++ b/src/test/kotlin/dev/usbharu/hideout/service/ap/APNoteServiceImplTest.kt @@ -48,7 +48,8 @@ class APNoteServiceImplTest { "https://follower.example.com", "https://follower.example.com", publicKey = "", - createdAt = Instant.now() + createdAt = Instant.now(), + keyId = "a" ), User.of( 3L, @@ -61,7 +62,8 @@ class APNoteServiceImplTest { "https://follower2.example.com", "https://follower2.example.com", publicKey = "", - createdAt = Instant.now() + createdAt = Instant.now(), + keyId = "a" ) ) val userQueryService = mock { @@ -77,7 +79,8 @@ class APNoteServiceImplTest { "https://example.com", publicKey = "", privateKey = "a", - createdAt = Instant.now() + createdAt = Instant.now(), + keyId = "a" ) } val followerQueryService = mock { diff --git a/src/test/kotlin/dev/usbharu/hideout/service/ap/APReceiveFollowServiceImplTest.kt b/src/test/kotlin/dev/usbharu/hideout/service/ap/APReceiveFollowServiceImplTest.kt index d16fcc2e..d1d51fa7 100644 --- a/src/test/kotlin/dev/usbharu/hideout/service/ap/APReceiveFollowServiceImplTest.kt +++ b/src/test/kotlin/dev/usbharu/hideout/service/ap/APReceiveFollowServiceImplTest.kt @@ -102,7 +102,9 @@ class APReceiveFollowServiceImplTest { id = "https://follower.example.com#main-key", owner = "https://follower.example.com", publicKeyPem = "BEGIN PUBLIC KEY...END PUBLIC KEY", - ) + ), + followers = "", + following = "" ) val apUserService = mock { @@ -111,30 +113,32 @@ class APReceiveFollowServiceImplTest { val userQueryService = mock { onBlocking { findByUrl(eq("https://example.com")) } doReturn User.of( - id = 1L, - name = "test", - domain = "example.com", - screenName = "testUser", - description = "This user is test user.", - inbox = "https://example.com/inbox", - outbox = "https://example.com/outbox", - url = "https://example.com", - publicKey = "", - createdAt = Instant.now() - ) + id = 1L, + name = "test", + domain = "example.com", + screenName = "testUser", + description = "This user is test user.", + inbox = "https://example.com/inbox", + outbox = "https://example.com/outbox", + url = "https://example.com", + publicKey = "", + createdAt = Instant.now(), + keyId = "a" + ) onBlocking { findByUrl(eq("https://follower.example.com")) } doReturn User.of( - id = 2L, - name = "follower", - domain = "follower.example.com", - screenName = "followerUser", - description = "This user is test follower user.", - inbox = "https://follower.example.com/inbox", - outbox = "https://follower.example.com/outbox", - url = "https://follower.example.com", - publicKey = "", - createdAt = Instant.now() - ) + id = 2L, + name = "follower", + domain = "follower.example.com", + screenName = "followerUser", + description = "This user is test follower user.", + inbox = "https://follower.example.com/inbox", + outbox = "https://follower.example.com/outbox", + url = "https://follower.example.com", + publicKey = "", + createdAt = Instant.now(), + keyId = "a" + ) } val userService = mock { diff --git a/src/test/kotlin/dev/usbharu/hideout/service/ap/resource/APResourceResolveServiceImplTest.kt b/src/test/kotlin/dev/usbharu/hideout/service/ap/resource/APResourceResolveServiceImplTest.kt index fa244184..344a07d8 100644 --- a/src/test/kotlin/dev/usbharu/hideout/service/ap/resource/APResourceResolveServiceImplTest.kt +++ b/src/test/kotlin/dev/usbharu/hideout/service/ap/resource/APResourceResolveServiceImplTest.kt @@ -47,7 +47,8 @@ class APResourceResolveServiceImplTest { "https://follower.example.com", "https://follower.example.com", publicKey = "", - createdAt = Instant.now() + createdAt = Instant.now(), + keyId = "" ) ) @@ -82,7 +83,8 @@ class APResourceResolveServiceImplTest { "https://follower.example.com", "https://follower.example.com", publicKey = "", - createdAt = Instant.now() + createdAt = Instant.now(), + keyId = "" ) ) @@ -120,7 +122,8 @@ class APResourceResolveServiceImplTest { "https://follower.example.com", "https://follower.example.com", publicKey = "", - createdAt = Instant.now() + createdAt = Instant.now(), + keyId = "" ) ) @@ -169,7 +172,8 @@ class APResourceResolveServiceImplTest { "https://follower.example.com", "https://follower.example.com", publicKey = "", - createdAt = Instant.now() + createdAt = Instant.now(), + keyId = "" ) ) diff --git a/src/test/kotlin/dev/usbharu/hideout/service/user/UserServiceTest.kt b/src/test/kotlin/dev/usbharu/hideout/service/user/UserServiceTest.kt index 650fd81c..26e4ebf8 100644 --- a/src/test/kotlin/dev/usbharu/hideout/service/user/UserServiceTest.kt +++ b/src/test/kotlin/dev/usbharu/hideout/service/user/UserServiceTest.kt @@ -58,14 +58,17 @@ class UserServiceTest { } val userService = UserServiceImpl(userRepository, mock(), mock(), mock(), mock(), testApplicationConfig) val user = RemoteUserCreateDto( - "test", - "example.com", - "testUser", - "test user", - "https://example.com/inbox", - "https://example.com/outbox", - "https://example.com", - "-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----" + name = "test", + domain = "example.com", + screenName = "testUser", + description = "test user", + inbox = "https://example.com/inbox", + outbox = "https://example.com/outbox", + url = "https://example.com", + publicKey = "-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----", + keyId = "a", + following = "", + followers = "" ) userService.createRemoteUser(user) verify(userRepository, times(1)).save(any())