From 269289966c319b984d773bc88f1d5cb948c2678c Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Tue, 15 Aug 2023 13:30:35 +0900 Subject: [PATCH 1/5] =?UTF-8?q?feat:=20User=E3=81=AB=E3=83=89=E3=83=A1?= =?UTF-8?q?=E3=82=A4=E3=83=B3=E7=9F=A5=E8=AD=98=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/dev/usbharu/hideout/Application.kt | 16 ++- .../dev/usbharu/hideout/config/Config.kt | 44 ++++++- .../domain/model/hideout/entity/User.kt | 112 +++++++++++++++++- .../hideout/query/FollowerQueryServiceImpl.kt | 8 +- .../hideout/repository/UserRepositoryImpl.kt | 2 +- .../hideout/service/user/UserServiceImpl.kt | 4 +- src/main/resources/application.conf | 28 +++-- .../hideout/plugins/ActivityPubKtTest.kt | 26 ++-- .../usbharu/hideout/plugins/KtorKeyMapTest.kt | 8 +- .../usbharu/hideout/plugins/SecurityKtTest.kt | 2 +- .../routing/activitypub/UsersAPTest.kt | 2 +- .../routing/api/internal/v1/UsersTest.kt | 2 +- .../service/ap/APNoteServiceImplTest.kt | 15 +-- .../ap/APReceiveFollowServiceImplTest.kt | 4 +- .../service/auth/JwtServiceImplTest.kt | 4 +- .../hideout/service/user/UserServiceTest.kt | 2 +- 16 files changed, 230 insertions(+), 49 deletions(-) diff --git a/src/main/kotlin/dev/usbharu/hideout/Application.kt b/src/main/kotlin/dev/usbharu/hideout/Application.kt index 0acd75e5..6a745122 100644 --- a/src/main/kotlin/dev/usbharu/hideout/Application.kt +++ b/src/main/kotlin/dev/usbharu/hideout/Application.kt @@ -5,6 +5,7 @@ import com.auth0.jwk.JwkProviderBuilder import com.fasterxml.jackson.annotation.JsonInclude import com.fasterxml.jackson.databind.DeserializationFeature import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import dev.usbharu.hideout.config.CharacterLimit import dev.usbharu.hideout.config.Config import dev.usbharu.hideout.config.ConfigData import dev.usbharu.hideout.domain.model.job.DeliverPostJob @@ -45,6 +46,11 @@ val Application.property: Application.(propertyName: String) -> String environment.config.property(it).getString() } +val Application.propertyOrNull: Application.(propertyName: String) -> String? + get() = { + environment.config.propertyOrNull(it)?.getString() + } + // application.conf references the main function. This annotation prevents the IDE from marking it as unused. @Suppress("unused", "LongMethod") fun Application.parent() { @@ -52,7 +58,15 @@ fun Application.parent() { url = property("hideout.url"), objectMapper = jacksonObjectMapper().enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY) .setSerializationInclusion(JsonInclude.Include.NON_EMPTY) - .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false), + characterLimit = CharacterLimit( + general = CharacterLimit.General.of( + url = propertyOrNull("hideout.character-limit.general.url")?.toIntOrNull(), + domain = propertyOrNull("hideout.character-limit.general.domain")?.toIntOrNull(), + publicKey = propertyOrNull("hideout.character-limit.general.publicKey")?.toIntOrNull(), + privateKey = propertyOrNull("hideout.character-limit.general.privateKey")?.toIntOrNull() + ) + ) ) val module = org.koin.dsl.module { diff --git a/src/main/kotlin/dev/usbharu/hideout/config/Config.kt b/src/main/kotlin/dev/usbharu/hideout/config/Config.kt index 02358c41..62ca7112 100644 --- a/src/main/kotlin/dev/usbharu/hideout/config/Config.kt +++ b/src/main/kotlin/dev/usbharu/hideout/config/Config.kt @@ -10,5 +10,47 @@ object Config { data class ConfigData( val url: String = "", val domain: String = url.substringAfter("://").substringBeforeLast(":"), - val objectMapper: ObjectMapper = jacksonObjectMapper() + val objectMapper: ObjectMapper = jacksonObjectMapper(), + val characterLimit: CharacterLimit = CharacterLimit() ) + +data class CharacterLimit( + val general: General = General.of(), + val post: Post = Post(), + val account: Account = Account(), + val instance: Instance = Instance() +) { + data class General private constructor( + val url: Int, + val domain: Int, + val publicKey: Int, + val privateKey: Int + ) { + companion object { + fun of(url: Int? = null, domain: Int? = null, publicKey: Int? = null, privateKey: Int? = null): General { + return General( + url ?: 1000, + domain ?: 1000, + publicKey ?: 10000, + privateKey ?: 10000 + ) + } + } + } + + data class Post( + val text: Int = 3000, + val overview: Int = 3000 + ) + + data class Account( + val id: Int = 300, + val name: Int = 300, + val description: Int = 10000 + ) + + data class Instance( + val name: Int = 600, + val description: Int = 10000 + ) +} 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 6754df4f..9a2c4e4a 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 @@ -1,8 +1,10 @@ package dev.usbharu.hideout.domain.model.hideout.entity +import dev.usbharu.hideout.config.Config +import org.slf4j.LoggerFactory import java.time.Instant -data class User( +data class User private constructor( val id: Long, val name: String, val domain: String, @@ -21,4 +23,112 @@ data class User( " password=****, inbox='$inbox', outbox='$outbox', url='$url', publicKey='$publicKey'," + " privateKey=****, createdAt=$createdAt)" } + + companion object { + private val logger = LoggerFactory.getLogger(User::class.java) + + @Suppress("LongParameterList", "FunctionMinLength") + fun of( + id: Long, + name: String, + domain: String, + screenName: String, + description: String, + password: String? = null, + inbox: String, + outbox: String, + url: String, + publicKey: String, + privateKey: String? = null, + createdAt: Instant + ): User { + val characterLimit = Config.configData.characterLimit + + // idは0未満ではいけない + require(id >= 0) { "id must be greater than or equal to 0." } + + // nameは空文字以外を含める必要がある + require(name.isNotBlank()) { "name must contain non-blank characters." } + + // nameは指定された長さ以下である必要がある + val limitedName = if (name.length >= characterLimit.account.id) { + logger.warn("name must not exceed ${characterLimit.account.id} characters.") + name.substring(0, characterLimit.account.id) + } else { + name + } + + // domainは空文字以外を含める必要がある + require(domain.isNotBlank()) { "domain must contain non-blank characters." } + + // domainは指定された長さ以下である必要がある + require(domain.length <= characterLimit.general.domain) { + "domain must not exceed ${characterLimit.general.domain} characters." + } + + // screenNameは空文字以外を含める必要がある + require(screenName.isNotBlank()) { "screenName must contain non-blank characters." } + + // screenNameは指定された長さ以下である必要がある + val limitedScreenName = if (screenName.length >= characterLimit.account.name) { + logger.warn("screenName must not exceed ${characterLimit.account.name} characters.") + screenName.substring(0, characterLimit.account.name) + } else { + screenName + } + + // descriptionは指定された長さ以下である必要がある + val limitedDescription = if (description.length >= characterLimit.account.description) { + logger.warn("description must not exceed ${characterLimit.account.description} characters.") + description.substring(0, characterLimit.account.description) + } else { + description + } + + // ローカルユーザーはpasswordとprivateKeyをnullにしてはいけない + if (domain == Config.configData.domain) { + requireNotNull(password) { "password and privateKey must not be null for local users." } + requireNotNull(privateKey) { "password and privateKey must not be null for local users." } + } + + // urlは空文字以外を含める必要がある + require(url.isNotBlank()) { "url must contain non-blank characters." } + + // urlは指定された長さ以下である必要がある + require(url.length <= characterLimit.general.url) { + "url must not exceed ${characterLimit.general.url} characters." + } + + // inboxは空文字以外を含める必要がある + require(inbox.isNotBlank()) { "inbox must contain non-blank characters." } + + // inboxは指定された長さ以下である必要がある + require(inbox.length <= characterLimit.general.url) { + "inbox must not exceed ${characterLimit.general.url} characters." + } + + // outboxは空文字以外を含める必要がある + require(outbox.isNotBlank()) { "outbox must contain non-blank characters." } + + // outboxは指定された長さ以下である必要がある + require(outbox.length <= characterLimit.general.url) { + "outbox must not exceed ${characterLimit.general.url} characters." + } + + return User( + id = id, + name = limitedName, + domain = domain, + screenName = limitedScreenName, + description = limitedDescription, + password = password, + inbox = inbox, + outbox = outbox, + url = url, + publicKey = publicKey, + privateKey = privateKey, + createdAt = createdAt + ) + } + } } diff --git a/src/main/kotlin/dev/usbharu/hideout/query/FollowerQueryServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/query/FollowerQueryServiceImpl.kt index c9f0bdc8..3a938620 100644 --- a/src/main/kotlin/dev/usbharu/hideout/query/FollowerQueryServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/query/FollowerQueryServiceImpl.kt @@ -38,7 +38,7 @@ class FollowerQueryServiceImpl : FollowerQueryService { ) .select { Users.id eq id } .map { - User( + User.of( id = it[followers[Users.id]], name = it[followers[Users.name]], domain = it[followers[Users.domain]], @@ -83,7 +83,7 @@ class FollowerQueryServiceImpl : FollowerQueryService { ) .select { Users.name eq name and (Users.domain eq domain) } .map { - User( + User.of( id = it[followers[Users.id]], name = it[followers[Users.name]], domain = it[followers[Users.domain]], @@ -128,7 +128,7 @@ class FollowerQueryServiceImpl : FollowerQueryService { ) .select { followers[Users.id] eq id } .map { - User( + User.of( id = it[followers[Users.id]], name = it[followers[Users.name]], domain = it[followers[Users.domain]], @@ -173,7 +173,7 @@ class FollowerQueryServiceImpl : FollowerQueryService { ) .select { followers[Users.name] eq name and (followers[Users.domain] eq domain) } .map { - User( + User.of( id = it[followers[Users.id]], name = it[followers[Users.name]], domain = it[followers[Users.domain]], diff --git a/src/main/kotlin/dev/usbharu/hideout/repository/UserRepositoryImpl.kt b/src/main/kotlin/dev/usbharu/hideout/repository/UserRepositoryImpl.kt index 43ea37ef..86f0725a 100644 --- a/src/main/kotlin/dev/usbharu/hideout/repository/UserRepositoryImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/repository/UserRepositoryImpl.kt @@ -102,7 +102,7 @@ object Users : Table("users") { } fun ResultRow.toUser(): User { - return User( + return User.of( id = this[Users.id], name = this[Users.name], domain = this[Users.domain], 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 6558d770..3d2a93dd 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/user/UserServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/user/UserServiceImpl.kt @@ -32,7 +32,7 @@ class UserServiceImpl( val nextId = userRepository.nextId() val hashedPassword = userAuthService.hash(user.password) val keyPair = userAuthService.generateKeyPair() - val userEntity = User( + val userEntity = User.of( id = nextId, name = user.name, domain = Config.configData.domain, @@ -51,7 +51,7 @@ class UserServiceImpl( override suspend fun createRemoteUser(user: RemoteUserCreateDto): User { val nextId = userRepository.nextId() - val userEntity = User( + val userEntity = User.of( id = nextId, name = user.name, domain = user.domain, diff --git a/src/main/resources/application.conf b/src/main/resources/application.conf index db3cc28b..70c07785 100644 --- a/src/main/resources/application.conf +++ b/src/main/resources/application.conf @@ -19,11 +19,25 @@ hideout { username = "" password = "" } -} - -jwt { - privateKey = "MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEAtfJaLrzXILUg1U3N1KV8yJr92GHn5OtYZR7qWk1Mc4cy4JGjklYup7weMjBD9f3bBVoIsiUVX6xNcYIr0Ie0AQIDAQABAkEAg+FBquToDeYcAWBe1EaLVyC45HG60zwfG1S4S3IB+y4INz1FHuZppDjBh09jptQNd+kSMlG1LkAc/3znKTPJ7QIhANpyB0OfTK44lpH4ScJmCxjZV52mIrQcmnS3QzkxWQCDAiEA1Tn7qyoh+0rOO/9vJHP8U/beo51SiQMw0880a1UaiisCIQDNwY46EbhGeiLJR1cidr+JHl86rRwPDsolmeEF5AdzRQIgK3KXL3d0WSoS//K6iOkBX3KMRzaFXNnDl0U/XyeGMuUCIHaXv+n+Brz5BDnRbWS+2vkgIe9bUNlkiArpjWvX+2we" - issuer = "http://0.0.0.0:8080/" - audience = "http://0.0.0.0:8080/hello" - realm = "Access to 'hello'" + character-limit { + general { + url = 1000 + domain = 255 + publicKey = 10000 + privateKey = 10000 + } + post { + text = 3000 + overview = 3000 + } + account { + id = 300 + name = 300 + description = 10000 + } + instance { + name = 600 + description = 10000 + } + } } diff --git a/src/test/kotlin/dev/usbharu/hideout/plugins/ActivityPubKtTest.kt b/src/test/kotlin/dev/usbharu/hideout/plugins/ActivityPubKtTest.kt index e7106465..3fb3f006 100644 --- a/src/test/kotlin/dev/usbharu/hideout/plugins/ActivityPubKtTest.kt +++ b/src/test/kotlin/dev/usbharu/hideout/plugins/ActivityPubKtTest.kt @@ -24,19 +24,19 @@ class ActivityPubKtTest { val keyPairGenerator = KeyPairGenerator.getInstance("RSA") keyPairGenerator.initialize(1024) val generateKeyPair = keyPairGenerator.generateKeyPair() - User( - 1, - "test", - "localhost", - "test", - "", - "", - "", - "", - "", - "", - generateKeyPair.private.toPem(), - Instant.now() + User.of( + id = 1, + name = "test", + domain = "localhost", + screenName = "test", + description = "", + password = "", + inbox = "https://example.com/inbox", + outbox = "https://example.com/outbox", + url = "https://example.com", + publicKey = "", + privateKey = generateKeyPair.private.toPem(), + createdAt = Instant.now() ) } } diff --git a/src/test/kotlin/dev/usbharu/hideout/plugins/KtorKeyMapTest.kt b/src/test/kotlin/dev/usbharu/hideout/plugins/KtorKeyMapTest.kt index c7326013..0c7eb45e 100644 --- a/src/test/kotlin/dev/usbharu/hideout/plugins/KtorKeyMapTest.kt +++ b/src/test/kotlin/dev/usbharu/hideout/plugins/KtorKeyMapTest.kt @@ -20,16 +20,16 @@ class KtorKeyMapTest { val keyPairGenerator = KeyPairGenerator.getInstance("RSA") keyPairGenerator.initialize(1024) val generateKeyPair = keyPairGenerator.generateKeyPair() - User( + User.of( 1, "test", "localhost", "test", "", "", - "", - "", - "", + "https://example.com/inbox", + "https://example.com/outbox", + "https://example.com", "", generateKeyPair.private.toPem(), createdAt = Instant.now() diff --git a/src/test/kotlin/dev/usbharu/hideout/plugins/SecurityKtTest.kt b/src/test/kotlin/dev/usbharu/hideout/plugins/SecurityKtTest.kt index 0d91ce6f..a47cc283 100644 --- a/src/test/kotlin/dev/usbharu/hideout/plugins/SecurityKtTest.kt +++ b/src/test/kotlin/dev/usbharu/hideout/plugins/SecurityKtTest.kt @@ -53,7 +53,7 @@ class SecurityKtTest { } val metaService = mock() val userQueryService = mock { - onBlocking { findByNameAndDomain(eq("testUser"), eq("example.com")) } doReturn User( + onBlocking { findByNameAndDomain(eq("testUser"), eq("example.com")) } doReturn User.of( id = 1L, name = "testUser", domain = "example.com", diff --git a/src/test/kotlin/dev/usbharu/hideout/routing/activitypub/UsersAPTest.kt b/src/test/kotlin/dev/usbharu/hideout/routing/activitypub/UsersAPTest.kt index 8fbb324f..81b780c8 100644 --- a/src/test/kotlin/dev/usbharu/hideout/routing/activitypub/UsersAPTest.kt +++ b/src/test/kotlin/dev/usbharu/hideout/routing/activitypub/UsersAPTest.kt @@ -172,7 +172,7 @@ class UsersAPTest { config = ApplicationConfig("empty.conf") } val userService = mock { - onBlocking { findByNameAndDomain(eq("test"), anyString()) } doReturn User( + onBlocking { findByNameAndDomain(eq("test"), anyString()) } doReturn User.of( 1L, "test", "example.com", diff --git a/src/test/kotlin/dev/usbharu/hideout/routing/api/internal/v1/UsersTest.kt b/src/test/kotlin/dev/usbharu/hideout/routing/api/internal/v1/UsersTest.kt index 82a72db4..9bc0db7d 100644 --- a/src/test/kotlin/dev/usbharu/hideout/routing/api/internal/v1/UsersTest.kt +++ b/src/test/kotlin/dev/usbharu/hideout/routing/api/internal/v1/UsersTest.kt @@ -80,7 +80,7 @@ class UsersTest { val userCreateDto = UserCreate("test", "XXXXXXX") val userService = mock { onBlocking { usernameAlreadyUse(any()) } doReturn false - onBlocking { createLocalUser(any()) } doReturn User( + onBlocking { createLocalUser(any()) } doReturn User.of( id = 12345, name = "test", domain = "example.com", 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 df22d1e1..fd65cbba 100644 --- a/src/test/kotlin/dev/usbharu/hideout/service/ap/APNoteServiceImplTest.kt +++ b/src/test/kotlin/dev/usbharu/hideout/service/ap/APNoteServiceImplTest.kt @@ -29,7 +29,7 @@ class APNoteServiceImplTest { @Test fun `createPost 新しい投稿`() = runTest { val followers = listOf( - User( + User.of( 2L, "follower", "follower.example.com", @@ -38,11 +38,11 @@ class APNoteServiceImplTest { "https://follower.example.com/inbox", "https://follower.example.com/outbox", "https://follower.example.com", - "", + "https://follower.example.com", publicKey = "", createdAt = Instant.now() ), - User( + User.of( 3L, "follower2", "follower2.example.com", @@ -51,23 +51,24 @@ class APNoteServiceImplTest { "https://follower2.example.com/inbox", "https://follower2.example.com/outbox", "https://follower2.example.com", - "", + "https://follower2.example.com", publicKey = "", createdAt = Instant.now() ) ) val userQueryService = mock { - onBlocking { findById(eq(1L)) } doReturn User( + onBlocking { findById(eq(1L)) } doReturn User.of( 1L, "test", "example.com", "testUser", "test user", + "a", "https://example.com/inbox", "https://example.com/outbox", - "https:.//example.com", - "", + "https://example.com", publicKey = "", + privateKey = "a", createdAt = Instant.now() ) } 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 7b7f2c21..98222b11 100644 --- a/src/test/kotlin/dev/usbharu/hideout/service/ap/APReceiveFollowServiceImplTest.kt +++ b/src/test/kotlin/dev/usbharu/hideout/service/ap/APReceiveFollowServiceImplTest.kt @@ -101,7 +101,7 @@ class APReceiveFollowServiceImplTest { } val userQueryService = mock { onBlocking { findByUrl(eq("https://example.com")) } doReturn - User( + User.of( id = 1L, name = "test", domain = "example.com", @@ -114,7 +114,7 @@ class APReceiveFollowServiceImplTest { createdAt = Instant.now() ) onBlocking { findByUrl(eq("https://follower.example.com")) } doReturn - User( + User.of( id = 2L, name = "follower", domain = "follower.example.com", diff --git a/src/test/kotlin/dev/usbharu/hideout/service/auth/JwtServiceImplTest.kt b/src/test/kotlin/dev/usbharu/hideout/service/auth/JwtServiceImplTest.kt index 44fc2844..3df97e3d 100644 --- a/src/test/kotlin/dev/usbharu/hideout/service/auth/JwtServiceImplTest.kt +++ b/src/test/kotlin/dev/usbharu/hideout/service/auth/JwtServiceImplTest.kt @@ -54,7 +54,7 @@ class JwtServiceImplTest { } val jwtService = JwtServiceImpl(metaService, refreshTokenRepository, mock(), mock()) val token = jwtService.createToken( - User( + User.of( id = 1L, name = "test", domain = "example.com", @@ -108,7 +108,7 @@ class JwtServiceImplTest { ) } val userService = mock { - onBlocking { findById(1L) } doReturn User( + onBlocking { findById(1L) } doReturn User.of( id = 1L, name = "test", domain = "example.com", 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 e32ca6f1..deb5b5e9 100644 --- a/src/test/kotlin/dev/usbharu/hideout/service/user/UserServiceTest.kt +++ b/src/test/kotlin/dev/usbharu/hideout/service/user/UserServiceTest.kt @@ -49,7 +49,7 @@ class UserServiceTest { @Test fun `createRemoteUser リモートユーザーを作成できる`() = runTest { - Config.configData = ConfigData(domain = "example.com", url = "https://example.com") + Config.configData = ConfigData(domain = "remote.example.com", url = "https://remote.example.com") val userRepository = mock { onBlocking { nextId() } doReturn 113345L From 53936455c1a4f926df7ec2239fb6c5fc8d66b9a4 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Tue, 15 Aug 2023 15:10:44 +0900 Subject: [PATCH 2/5] =?UTF-8?q?feat:=20=E3=83=88=E3=83=A9=E3=83=B3?= =?UTF-8?q?=E3=82=B6=E3=82=AF=E3=82=B7=E3=83=A7=E3=83=B3=E3=82=92=E8=AA=BF?= =?UTF-8?q?=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../usbharu/hideout/query/UserQueryServiceImpl.kt | 13 ++++++++++--- .../usbharu/hideout/service/ap/APLikeService.kt | 15 ++++++++------- .../usbharu/hideout/service/ap/APNoteService.kt | 3 ++- .../hideout/service/core/ExposedTransaction.kt | 8 +++++++- .../usbharu/hideout/service/core/Transaction.kt | 1 + src/test/kotlin/utils/TestTransaction.kt | 1 + 6 files changed, 29 insertions(+), 12 deletions(-) diff --git a/src/main/kotlin/dev/usbharu/hideout/query/UserQueryServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/query/UserQueryServiceImpl.kt index 9d18d2cd..647b8d5f 100644 --- a/src/main/kotlin/dev/usbharu/hideout/query/UserQueryServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/query/UserQueryServiceImpl.kt @@ -9,9 +9,13 @@ import org.jetbrains.exposed.sql.and import org.jetbrains.exposed.sql.select import org.jetbrains.exposed.sql.selectAll import org.koin.core.annotation.Single +import org.slf4j.LoggerFactory @Single class UserQueryServiceImpl : UserQueryService { + + private val logger = LoggerFactory.getLogger(UserQueryServiceImpl::class.java) + override suspend fun findAll(limit: Int, offset: Long): List = Users.selectAll().limit(limit, offset).map { it.toUser() } @@ -28,9 +32,12 @@ class UserQueryServiceImpl : UserQueryService { } .toUser() - override suspend fun findByUrl(url: String): User = Users.select { Users.url eq url } - .singleOr { FailedToGetResourcesException("url: $url is duplicate or does not exist.", it) } - .toUser() + override suspend fun findByUrl(url: String): User { + logger.trace("findByUrl url: $url") + return Users.select { Users.url eq url } + .singleOr { FailedToGetResourcesException("url: $url is duplicate or does not exist.", it) } + .toUser() + } override suspend fun findByIds(ids: List): List = Users.select { Users.id inList ids }.map { it.toUser() } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/ap/APLikeService.kt b/src/main/kotlin/dev/usbharu/hideout/service/ap/APLikeService.kt index 0313acf8..6f88d87a 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/ap/APLikeService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/ap/APLikeService.kt @@ -28,18 +28,19 @@ class APLikeServiceImpl( val actor = like.actor ?: throw IllegalActivityPubObjectException("actor is null") val content = like.content ?: throw IllegalActivityPubObjectException("content is null") like.`object` ?: throw IllegalActivityPubObjectException("object is null") - transaction.transaction { - val person = apUserService.fetchPerson(actor) + transaction.transaction(java.sql.Connection.TRANSACTION_SERIALIZABLE) { + val person = apUserService.fetchPersonWithEntity(actor) apNoteService.fetchNote(like.`object`!!) - val user = userQueryService.findByUrl( - person.url - ?: throw IllegalActivityPubObjectException("actor is not found") - ) val post = postQueryService.findByUrl(like.`object`!!) - reactionService.receiveReaction(content, actor.substringAfter("://").substringBefore("/"), user.id, post.id) + reactionService.receiveReaction( + content, + actor.substringAfter("://").substringBefore("/"), + person.second.id, + post.id + ) } return ActivityPubStringResponse(HttpStatusCode.OK, "") } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/ap/APNoteService.kt b/src/main/kotlin/dev/usbharu/hideout/service/ap/APNoteService.kt index 255c9804..45583001 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/ap/APNoteService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/ap/APNoteService.kt @@ -120,7 +120,8 @@ class APNoteServiceImpl( url: String ): Note { if (note.id == null) { - return internalNote(note, targetActor, url) + throw IllegalArgumentException("id is null") +// return internalNote(note, targetActor, url) } val findByApId = try { diff --git a/src/main/kotlin/dev/usbharu/hideout/service/core/ExposedTransaction.kt b/src/main/kotlin/dev/usbharu/hideout/service/core/ExposedTransaction.kt index a58cfe04..c15327a4 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/core/ExposedTransaction.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/core/ExposedTransaction.kt @@ -6,7 +6,13 @@ import org.koin.core.annotation.Single @Single class ExposedTransaction : Transaction { override suspend fun transaction(block: suspend () -> T): T { - return newSuspendedTransaction { + return newSuspendedTransaction(transactionIsolation = java.sql.Connection.TRANSACTION_SERIALIZABLE) { + block() + } + } + + override suspend fun transaction(transactionLevel: Int, block: suspend () -> T): T { + return newSuspendedTransaction(transactionIsolation = transactionLevel) { block() } } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/core/Transaction.kt b/src/main/kotlin/dev/usbharu/hideout/service/core/Transaction.kt index 40911be1..105420ed 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/core/Transaction.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/core/Transaction.kt @@ -2,4 +2,5 @@ package dev.usbharu.hideout.service.core interface Transaction { suspend fun transaction(block: suspend () -> T): T + suspend fun transaction(transactionLevel: Int, block: suspend () -> T): T } diff --git a/src/test/kotlin/utils/TestTransaction.kt b/src/test/kotlin/utils/TestTransaction.kt index 425372bd..f8d1832c 100644 --- a/src/test/kotlin/utils/TestTransaction.kt +++ b/src/test/kotlin/utils/TestTransaction.kt @@ -4,4 +4,5 @@ import dev.usbharu.hideout.service.core.Transaction object TestTransaction : Transaction { override suspend fun transaction(block: suspend () -> T): T = block() + override suspend fun transaction(transactionLevel: Int, block: suspend () -> T): T = block() } From dff3397bab517996104959364ad08e8a350435d7 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Tue, 15 Aug 2023 15:37:56 +0900 Subject: [PATCH 3/5] =?UTF-8?q?feat:=20DB=E3=81=AE=E5=AE=9A=E7=BE=A9?= =?UTF-8?q?=E3=82=92=E3=82=B3=E3=83=B3=E3=83=95=E3=82=A3=E3=82=B0=E3=81=8B?= =?UTF-8?q?=E3=82=89=E7=94=9F=E6=88=90=E3=81=99=E3=82=8B=E3=82=88=E3=81=86?= =?UTF-8?q?=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hideout/repository/UserRepositoryImpl.kt | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/main/kotlin/dev/usbharu/hideout/repository/UserRepositoryImpl.kt b/src/main/kotlin/dev/usbharu/hideout/repository/UserRepositoryImpl.kt index 86f0725a..961a089c 100644 --- a/src/main/kotlin/dev/usbharu/hideout/repository/UserRepositoryImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/repository/UserRepositoryImpl.kt @@ -1,5 +1,6 @@ package dev.usbharu.hideout.repository +import dev.usbharu.hideout.config.Config import dev.usbharu.hideout.domain.model.hideout.entity.User import dev.usbharu.hideout.service.core.IdGenerateService import org.jetbrains.exposed.dao.id.LongIdTable @@ -82,16 +83,16 @@ class UserRepositoryImpl(private val database: Database, private val idGenerateS object Users : Table("users") { val id = long("id") - val name = varchar("name", length = 64) - val domain = varchar("domain", length = 255) - val screenName = varchar("screen_name", length = 64) - val description = varchar("description", length = 600) + val name = varchar("name", length = Config.configData.characterLimit.account.id) + val domain = varchar("domain", length = Config.configData.characterLimit.general.domain) + val screenName = varchar("screen_name", length = Config.configData.characterLimit.account.name) + val description = varchar("description", length = Config.configData.characterLimit.account.description) val password = varchar("password", length = 255).nullable() - val inbox = varchar("inbox", length = 255).uniqueIndex() - val outbox = varchar("outbox", length = 255).uniqueIndex() - val url = varchar("url", length = 255).uniqueIndex() - val publicKey = varchar("public_key", length = 10000) - val privateKey = varchar("private_key", length = 10000).nullable() + val inbox = varchar("inbox", length = Config.configData.characterLimit.general.url).uniqueIndex() + val outbox = varchar("outbox", length = Config.configData.characterLimit.general.url).uniqueIndex() + val url = varchar("url", length = Config.configData.characterLimit.general.url).uniqueIndex() + val publicKey = varchar("public_key", length = Config.configData.characterLimit.general.publicKey) + val privateKey = varchar("private_key", length = Config.configData.characterLimit.general.privateKey).nullable() val createdAt = long("created_at") override val primaryKey: PrimaryKey = PrimaryKey(id) From b07a0ffabb3e6b4a2dce8cb07a822105081c89a3 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Wed, 16 Aug 2023 17:46:02 +0900 Subject: [PATCH 4/5] =?UTF-8?q?feat:=20Post=E3=81=AE=E3=83=89=E3=83=A1?= =?UTF-8?q?=E3=82=A4=E3=83=B3=E7=9F=A5=E8=AD=98=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/model/hideout/entity/Post.kt | 62 ++++++++++++++++++- .../hideout/repository/PostRepositoryImpl.kt | 2 +- .../hideout/service/ap/APLikeService.kt | 3 - .../hideout/service/ap/APNoteService.kt | 2 +- .../hideout/service/ap/APReactionService.kt | 2 - .../hideout/service/post/PostServiceImpl.kt | 2 +- .../service/ap/APNoteServiceImplTest.kt | 2 +- 7 files changed, 64 insertions(+), 11 deletions(-) diff --git a/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/entity/Post.kt b/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/entity/Post.kt index d58870d6..858bea45 100644 --- a/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/entity/Post.kt +++ b/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/entity/Post.kt @@ -1,6 +1,8 @@ package dev.usbharu.hideout.domain.model.hideout.entity -data class Post( +import dev.usbharu.hideout.config.Config + +data class Post private constructor( val id: Long, val userId: Long, val overview: String? = null, @@ -12,4 +14,60 @@ data class Post( val replyId: Long? = null, val sensitive: Boolean = false, val apId: String = url -) +) { + companion object { + fun of( + id: Long, + userId: Long, + overview: String? = null, + text: String, + createdAt: Long, + visibility: Visibility, + url: String, + repostId: Long? = null, + replyId: Long? = null, + sensitive: Boolean = false, + apId: String = url + ): Post { + val characterLimit = Config.configData.characterLimit + + require(id >= 0) { "id must be greater than or equal to 0." } + + require(userId >= 0) { "userId must be greater than or equal to 0." } + + val limitedOverview = if ((overview?.length ?: 0) >= characterLimit.post.overview) { + overview?.substring(0, characterLimit.post.overview) + } else { + overview + } + + val limitedText = if (text.length >= characterLimit.post.text) { + text.substring(0, characterLimit.post.text) + } else { + text + } + + require(url.isNotBlank()) { "url must contain non-blank characters" } + require(url.length <= characterLimit.general.url) { + "url must not exceed ${characterLimit.general.url} characters." + } + + require((repostId ?: 0) >= 0) { "repostId must be greater then or equal to 0." } + require((replyId ?: 0) >= 0) { "replyId must be greater then or equal to 0." } + + return Post( + id = id, + userId = userId, + overview = limitedOverview, + text = limitedText, + createdAt = createdAt, + visibility = visibility, + url = url, + repostId = repostId, + replyId = replyId, + sensitive = sensitive, + apId = apId + ) + } + } +} diff --git a/src/main/kotlin/dev/usbharu/hideout/repository/PostRepositoryImpl.kt b/src/main/kotlin/dev/usbharu/hideout/repository/PostRepositoryImpl.kt index 764bf32d..79698826 100644 --- a/src/main/kotlin/dev/usbharu/hideout/repository/PostRepositoryImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/repository/PostRepositoryImpl.kt @@ -78,7 +78,7 @@ object Posts : Table() { } fun ResultRow.toPost(): Post { - return Post( + return Post.of( id = this[Posts.id], userId = this[Posts.userId], overview = this[Posts.overview], diff --git a/src/main/kotlin/dev/usbharu/hideout/service/ap/APLikeService.kt b/src/main/kotlin/dev/usbharu/hideout/service/ap/APLikeService.kt index 6f88d87a..6bf2132e 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/ap/APLikeService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/ap/APLikeService.kt @@ -5,7 +5,6 @@ import dev.usbharu.hideout.domain.model.ActivityPubStringResponse import dev.usbharu.hideout.domain.model.ap.Like import dev.usbharu.hideout.exception.ap.IllegalActivityPubObjectException import dev.usbharu.hideout.query.PostQueryService -import dev.usbharu.hideout.query.UserQueryService import dev.usbharu.hideout.service.core.Transaction import dev.usbharu.hideout.service.reaction.ReactionService import io.ktor.http.* @@ -20,7 +19,6 @@ class APLikeServiceImpl( private val reactionService: ReactionService, private val apUserService: APUserService, private val apNoteService: APNoteService, - private val userQueryService: UserQueryService, private val postQueryService: PostQueryService, private val transaction: Transaction ) : APLikeService { @@ -32,7 +30,6 @@ class APLikeServiceImpl( val person = apUserService.fetchPersonWithEntity(actor) apNoteService.fetchNote(like.`object`!!) - val post = postQueryService.findByUrl(like.`object`!!) reactionService.receiveReaction( diff --git a/src/main/kotlin/dev/usbharu/hideout/service/ap/APNoteService.kt b/src/main/kotlin/dev/usbharu/hideout/service/ap/APNoteService.kt index 45583001..9e1875b6 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/ap/APNoteService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/ap/APNoteService.kt @@ -155,7 +155,7 @@ class APNoteServiceImpl( } postRepository.save( - Post( + Post.of( id = postRepository.generateId(), userId = person.second.id, overview = null, diff --git a/src/main/kotlin/dev/usbharu/hideout/service/ap/APReactionService.kt b/src/main/kotlin/dev/usbharu/hideout/service/ap/APReactionService.kt index a7ca1533..ba7ff79c 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/ap/APReactionService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/ap/APReactionService.kt @@ -11,7 +11,6 @@ import dev.usbharu.hideout.plugins.postAp import dev.usbharu.hideout.query.FollowerQueryService import dev.usbharu.hideout.query.PostQueryService import dev.usbharu.hideout.query.UserQueryService -import dev.usbharu.hideout.repository.PostRepository import dev.usbharu.hideout.service.job.JobQueueParentService import io.ktor.client.* import kjob.core.job.JobProps @@ -28,7 +27,6 @@ interface APReactionService { @Single class APReactionServiceImpl( private val jobQueueParentService: JobQueueParentService, - private val postRepository: PostRepository, private val httpClient: HttpClient, private val userQueryService: UserQueryService, private val followerQueryService: FollowerQueryService, diff --git a/src/main/kotlin/dev/usbharu/hideout/service/post/PostServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/post/PostServiceImpl.kt index d184cbae..c1b58d1b 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/post/PostServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/post/PostServiceImpl.kt @@ -18,7 +18,7 @@ class PostServiceImpl( override suspend fun createLocal(post: PostCreateDto): Post { val user = userRepository.findById(post.userId) ?: throw UserNotFoundException("${post.userId} was not found") val id = postRepository.generateId() - val createPost = Post( + val createPost = Post.of( id = id, userId = post.userId, overview = post.overview, 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 fd65cbba..071c6625 100644 --- a/src/test/kotlin/dev/usbharu/hideout/service/ap/APNoteServiceImplTest.kt +++ b/src/test/kotlin/dev/usbharu/hideout/service/ap/APNoteServiceImplTest.kt @@ -86,7 +86,7 @@ class APNoteServiceImplTest { followerQueryService, mock() ) - val postEntity = Post( + val postEntity = Post.of( 1L, 1L, null, From 1a30407d83fe37202e7e2879534f17a84b682a08 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Fri, 18 Aug 2023 11:05:08 +0900 Subject: [PATCH 5/5] =?UTF-8?q?style:=20=E3=82=B9=E3=82=BF=E3=82=A4?= =?UTF-8?q?=E3=83=AB=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/kotlin/dev/usbharu/hideout/config/Config.kt | 1 + .../dev/usbharu/hideout/domain/model/hideout/entity/Post.kt | 1 + .../dev/usbharu/hideout/query/ReactionQueryServiceImpl.kt | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/dev/usbharu/hideout/config/Config.kt b/src/main/kotlin/dev/usbharu/hideout/config/Config.kt index 62ca7112..8acf1113 100644 --- a/src/main/kotlin/dev/usbharu/hideout/config/Config.kt +++ b/src/main/kotlin/dev/usbharu/hideout/config/Config.kt @@ -27,6 +27,7 @@ data class CharacterLimit( val privateKey: Int ) { companion object { + @Suppress("FunctionMinLength") fun of(url: Int? = null, domain: Int? = null, publicKey: Int? = null, privateKey: Int? = null): General { return General( url ?: 1000, diff --git a/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/entity/Post.kt b/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/entity/Post.kt index 858bea45..3ed6ac53 100644 --- a/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/entity/Post.kt +++ b/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/entity/Post.kt @@ -16,6 +16,7 @@ data class Post private constructor( val apId: String = url ) { companion object { + @Suppress("FunctionMinLength", "LongParameterList") fun of( id: Long, userId: Long, diff --git a/src/main/kotlin/dev/usbharu/hideout/query/ReactionQueryServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/query/ReactionQueryServiceImpl.kt index 00baae4e..de41d1cb 100644 --- a/src/main/kotlin/dev/usbharu/hideout/query/ReactionQueryServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/query/ReactionQueryServiceImpl.kt @@ -53,7 +53,7 @@ class ReactionQueryServiceImpl : ReactionQueryService { return Reactions .leftJoin(Users, onColumn = { Reactions.userId }, otherColumn = { id }) .select { Reactions.postId.eq(postId) } - .groupBy { _: ResultRow -> ReactionResponse("❤", true, "", listOf()) } + .groupBy { _: ResultRow -> ReactionResponse("❤", true, "", emptyList()) } .map { entry: Map.Entry> -> entry.key.copy(accounts = entry.value.map { Account(it[Users.screenName], "", it[Users.url]) }) }