mirror of https://github.com/usbharu/Hideout.git
feat: Userにドメイン知識を追加
This commit is contained in:
parent
5a56c61ee4
commit
269289966c
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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]],
|
||||
|
|
|
@ -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],
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -53,7 +53,7 @@ class SecurityKtTest {
|
|||
}
|
||||
val metaService = mock<MetaService>()
|
||||
val userQueryService = mock<UserQueryService> {
|
||||
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",
|
||||
|
|
|
@ -172,7 +172,7 @@ class UsersAPTest {
|
|||
config = ApplicationConfig("empty.conf")
|
||||
}
|
||||
val userService = mock<UserQueryService> {
|
||||
onBlocking { findByNameAndDomain(eq("test"), anyString()) } doReturn User(
|
||||
onBlocking { findByNameAndDomain(eq("test"), anyString()) } doReturn User.of(
|
||||
1L,
|
||||
"test",
|
||||
"example.com",
|
||||
|
|
|
@ -80,7 +80,7 @@ class UsersTest {
|
|||
val userCreateDto = UserCreate("test", "XXXXXXX")
|
||||
val userService = mock<UserService> {
|
||||
onBlocking { usernameAlreadyUse(any()) } doReturn false
|
||||
onBlocking { createLocalUser(any()) } doReturn User(
|
||||
onBlocking { createLocalUser(any()) } doReturn User.of(
|
||||
id = 12345,
|
||||
name = "test",
|
||||
domain = "example.com",
|
||||
|
|
|
@ -29,7 +29,7 @@ class APNoteServiceImplTest {
|
|||
@Test
|
||||
fun `createPost 新しい投稿`() = runTest {
|
||||
val followers = listOf<User>(
|
||||
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<UserQueryService> {
|
||||
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()
|
||||
)
|
||||
}
|
||||
|
|
|
@ -101,7 +101,7 @@ class APReceiveFollowServiceImplTest {
|
|||
}
|
||||
val userQueryService = mock<UserQueryService> {
|
||||
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",
|
||||
|
|
|
@ -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<UserQueryService> {
|
||||
onBlocking { findById(1L) } doReturn User(
|
||||
onBlocking { findById(1L) } doReturn User.of(
|
||||
id = 1L,
|
||||
name = "test",
|
||||
domain = "example.com",
|
||||
|
|
|
@ -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<UserRepository> {
|
||||
onBlocking { nextId() } doReturn 113345L
|
||||
|
|
Loading…
Reference in New Issue