mirror of https://github.com/usbharu/Hideout.git
Merge pull request #36 from usbharu/feature/character-limit
Feature/character limit
This commit is contained in:
commit
ad65094c3e
|
@ -5,6 +5,7 @@ import com.auth0.jwk.JwkProviderBuilder
|
||||||
import com.fasterxml.jackson.annotation.JsonInclude
|
import com.fasterxml.jackson.annotation.JsonInclude
|
||||||
import com.fasterxml.jackson.databind.DeserializationFeature
|
import com.fasterxml.jackson.databind.DeserializationFeature
|
||||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||||
|
import dev.usbharu.hideout.config.CharacterLimit
|
||||||
import dev.usbharu.hideout.config.Config
|
import dev.usbharu.hideout.config.Config
|
||||||
import dev.usbharu.hideout.config.ConfigData
|
import dev.usbharu.hideout.config.ConfigData
|
||||||
import dev.usbharu.hideout.domain.model.job.DeliverPostJob
|
import dev.usbharu.hideout.domain.model.job.DeliverPostJob
|
||||||
|
@ -45,6 +46,11 @@ val Application.property: Application.(propertyName: String) -> String
|
||||||
environment.config.property(it).getString()
|
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.
|
// application.conf references the main function. This annotation prevents the IDE from marking it as unused.
|
||||||
@Suppress("unused", "LongMethod")
|
@Suppress("unused", "LongMethod")
|
||||||
fun Application.parent() {
|
fun Application.parent() {
|
||||||
|
@ -52,7 +58,15 @@ fun Application.parent() {
|
||||||
url = property("hideout.url"),
|
url = property("hideout.url"),
|
||||||
objectMapper = jacksonObjectMapper().enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
|
objectMapper = jacksonObjectMapper().enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
|
||||||
.setSerializationInclusion(JsonInclude.Include.NON_EMPTY)
|
.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 {
|
val module = org.koin.dsl.module {
|
||||||
|
|
|
@ -10,5 +10,48 @@ object Config {
|
||||||
data class ConfigData(
|
data class ConfigData(
|
||||||
val url: String = "",
|
val url: String = "",
|
||||||
val domain: String = url.substringAfter("://").substringBeforeLast(":"),
|
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 {
|
||||||
|
@Suppress("FunctionMinLength")
|
||||||
|
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,6 +1,8 @@
|
||||||
package dev.usbharu.hideout.domain.model.hideout.entity
|
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 id: Long,
|
||||||
val userId: Long,
|
val userId: Long,
|
||||||
val overview: String? = null,
|
val overview: String? = null,
|
||||||
|
@ -12,4 +14,61 @@ data class Post(
|
||||||
val replyId: Long? = null,
|
val replyId: Long? = null,
|
||||||
val sensitive: Boolean = false,
|
val sensitive: Boolean = false,
|
||||||
val apId: String = url
|
val apId: String = url
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
@Suppress("FunctionMinLength", "LongParameterList")
|
||||||
|
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
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
package dev.usbharu.hideout.domain.model.hideout.entity
|
package dev.usbharu.hideout.domain.model.hideout.entity
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.config.Config
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
data class User(
|
data class User private constructor(
|
||||||
val id: Long,
|
val id: Long,
|
||||||
val name: String,
|
val name: String,
|
||||||
val domain: String,
|
val domain: String,
|
||||||
|
@ -21,4 +23,112 @@ data class User(
|
||||||
" password=****, inbox='$inbox', outbox='$outbox', url='$url', publicKey='$publicKey'," +
|
" password=****, inbox='$inbox', outbox='$outbox', url='$url', publicKey='$publicKey'," +
|
||||||
" privateKey=****, createdAt=$createdAt)"
|
" 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 }
|
.select { Users.id eq id }
|
||||||
.map {
|
.map {
|
||||||
User(
|
User.of(
|
||||||
id = it[followers[Users.id]],
|
id = it[followers[Users.id]],
|
||||||
name = it[followers[Users.name]],
|
name = it[followers[Users.name]],
|
||||||
domain = it[followers[Users.domain]],
|
domain = it[followers[Users.domain]],
|
||||||
|
@ -83,7 +83,7 @@ class FollowerQueryServiceImpl : FollowerQueryService {
|
||||||
)
|
)
|
||||||
.select { Users.name eq name and (Users.domain eq domain) }
|
.select { Users.name eq name and (Users.domain eq domain) }
|
||||||
.map {
|
.map {
|
||||||
User(
|
User.of(
|
||||||
id = it[followers[Users.id]],
|
id = it[followers[Users.id]],
|
||||||
name = it[followers[Users.name]],
|
name = it[followers[Users.name]],
|
||||||
domain = it[followers[Users.domain]],
|
domain = it[followers[Users.domain]],
|
||||||
|
@ -128,7 +128,7 @@ class FollowerQueryServiceImpl : FollowerQueryService {
|
||||||
)
|
)
|
||||||
.select { followers[Users.id] eq id }
|
.select { followers[Users.id] eq id }
|
||||||
.map {
|
.map {
|
||||||
User(
|
User.of(
|
||||||
id = it[followers[Users.id]],
|
id = it[followers[Users.id]],
|
||||||
name = it[followers[Users.name]],
|
name = it[followers[Users.name]],
|
||||||
domain = it[followers[Users.domain]],
|
domain = it[followers[Users.domain]],
|
||||||
|
@ -173,7 +173,7 @@ class FollowerQueryServiceImpl : FollowerQueryService {
|
||||||
)
|
)
|
||||||
.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 {
|
||||||
User(
|
User.of(
|
||||||
id = it[followers[Users.id]],
|
id = it[followers[Users.id]],
|
||||||
name = it[followers[Users.name]],
|
name = it[followers[Users.name]],
|
||||||
domain = it[followers[Users.domain]],
|
domain = it[followers[Users.domain]],
|
||||||
|
|
|
@ -53,7 +53,7 @@ class ReactionQueryServiceImpl : ReactionQueryService {
|
||||||
return Reactions
|
return Reactions
|
||||||
.leftJoin(Users, onColumn = { Reactions.userId }, otherColumn = { id })
|
.leftJoin(Users, onColumn = { Reactions.userId }, otherColumn = { id })
|
||||||
.select { Reactions.postId.eq(postId) }
|
.select { Reactions.postId.eq(postId) }
|
||||||
.groupBy { _: ResultRow -> ReactionResponse("❤", true, "", listOf()) }
|
.groupBy { _: ResultRow -> ReactionResponse("❤", true, "", emptyList()) }
|
||||||
.map { entry: Map.Entry<ReactionResponse, List<ResultRow>> ->
|
.map { entry: Map.Entry<ReactionResponse, List<ResultRow>> ->
|
||||||
entry.key.copy(accounts = entry.value.map { Account(it[Users.screenName], "", it[Users.url]) })
|
entry.key.copy(accounts = entry.value.map { Account(it[Users.screenName], "", it[Users.url]) })
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,9 +9,13 @@ import org.jetbrains.exposed.sql.and
|
||||||
import org.jetbrains.exposed.sql.select
|
import org.jetbrains.exposed.sql.select
|
||||||
import org.jetbrains.exposed.sql.selectAll
|
import org.jetbrains.exposed.sql.selectAll
|
||||||
import org.koin.core.annotation.Single
|
import org.koin.core.annotation.Single
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
|
||||||
@Single
|
@Single
|
||||||
class UserQueryServiceImpl : UserQueryService {
|
class UserQueryServiceImpl : UserQueryService {
|
||||||
|
|
||||||
|
private val logger = LoggerFactory.getLogger(UserQueryServiceImpl::class.java)
|
||||||
|
|
||||||
override suspend fun findAll(limit: Int, offset: Long): List<User> =
|
override suspend fun findAll(limit: Int, offset: Long): List<User> =
|
||||||
Users.selectAll().limit(limit, offset).map { it.toUser() }
|
Users.selectAll().limit(limit, offset).map { it.toUser() }
|
||||||
|
|
||||||
|
@ -28,9 +32,12 @@ class UserQueryServiceImpl : UserQueryService {
|
||||||
}
|
}
|
||||||
.toUser()
|
.toUser()
|
||||||
|
|
||||||
override suspend fun findByUrl(url: String): User = Users.select { Users.url eq url }
|
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) }
|
.singleOr { FailedToGetResourcesException("url: $url is duplicate or does not exist.", it) }
|
||||||
.toUser()
|
.toUser()
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun findByIds(ids: List<Long>): List<User> =
|
override suspend fun findByIds(ids: List<Long>): List<User> =
|
||||||
Users.select { Users.id inList ids }.map { it.toUser() }
|
Users.select { Users.id inList ids }.map { it.toUser() }
|
||||||
|
|
|
@ -78,7 +78,7 @@ object Posts : Table() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ResultRow.toPost(): Post {
|
fun ResultRow.toPost(): Post {
|
||||||
return Post(
|
return Post.of(
|
||||||
id = this[Posts.id],
|
id = this[Posts.id],
|
||||||
userId = this[Posts.userId],
|
userId = this[Posts.userId],
|
||||||
overview = this[Posts.overview],
|
overview = this[Posts.overview],
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package dev.usbharu.hideout.repository
|
package dev.usbharu.hideout.repository
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.config.Config
|
||||||
import dev.usbharu.hideout.domain.model.hideout.entity.User
|
import dev.usbharu.hideout.domain.model.hideout.entity.User
|
||||||
import dev.usbharu.hideout.service.core.IdGenerateService
|
import dev.usbharu.hideout.service.core.IdGenerateService
|
||||||
import org.jetbrains.exposed.dao.id.LongIdTable
|
import org.jetbrains.exposed.dao.id.LongIdTable
|
||||||
|
@ -82,16 +83,16 @@ class UserRepositoryImpl(private val database: Database, private val idGenerateS
|
||||||
|
|
||||||
object Users : Table("users") {
|
object Users : Table("users") {
|
||||||
val id = long("id")
|
val id = long("id")
|
||||||
val name = varchar("name", length = 64)
|
val name = varchar("name", length = Config.configData.characterLimit.account.id)
|
||||||
val domain = varchar("domain", length = 255)
|
val domain = varchar("domain", length = Config.configData.characterLimit.general.domain)
|
||||||
val screenName = varchar("screen_name", length = 64)
|
val screenName = varchar("screen_name", length = Config.configData.characterLimit.account.name)
|
||||||
val description = varchar("description", length = 600)
|
val description = varchar("description", length = Config.configData.characterLimit.account.description)
|
||||||
val password = varchar("password", length = 255).nullable()
|
val password = varchar("password", length = 255).nullable()
|
||||||
val inbox = varchar("inbox", length = 255).uniqueIndex()
|
val inbox = varchar("inbox", length = Config.configData.characterLimit.general.url).uniqueIndex()
|
||||||
val outbox = varchar("outbox", length = 255).uniqueIndex()
|
val outbox = varchar("outbox", length = Config.configData.characterLimit.general.url).uniqueIndex()
|
||||||
val url = varchar("url", length = 255).uniqueIndex()
|
val url = varchar("url", length = Config.configData.characterLimit.general.url).uniqueIndex()
|
||||||
val publicKey = varchar("public_key", length = 10000)
|
val publicKey = varchar("public_key", length = Config.configData.characterLimit.general.publicKey)
|
||||||
val privateKey = varchar("private_key", length = 10000).nullable()
|
val privateKey = varchar("private_key", length = Config.configData.characterLimit.general.privateKey).nullable()
|
||||||
val createdAt = long("created_at")
|
val createdAt = long("created_at")
|
||||||
|
|
||||||
override val primaryKey: PrimaryKey = PrimaryKey(id)
|
override val primaryKey: PrimaryKey = PrimaryKey(id)
|
||||||
|
@ -102,7 +103,7 @@ object Users : Table("users") {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ResultRow.toUser(): User {
|
fun ResultRow.toUser(): User {
|
||||||
return User(
|
return User.of(
|
||||||
id = this[Users.id],
|
id = this[Users.id],
|
||||||
name = this[Users.name],
|
name = this[Users.name],
|
||||||
domain = this[Users.domain],
|
domain = this[Users.domain],
|
||||||
|
|
|
@ -5,7 +5,6 @@ import dev.usbharu.hideout.domain.model.ActivityPubStringResponse
|
||||||
import dev.usbharu.hideout.domain.model.ap.Like
|
import dev.usbharu.hideout.domain.model.ap.Like
|
||||||
import dev.usbharu.hideout.exception.ap.IllegalActivityPubObjectException
|
import dev.usbharu.hideout.exception.ap.IllegalActivityPubObjectException
|
||||||
import dev.usbharu.hideout.query.PostQueryService
|
import dev.usbharu.hideout.query.PostQueryService
|
||||||
import dev.usbharu.hideout.query.UserQueryService
|
|
||||||
import dev.usbharu.hideout.service.core.Transaction
|
import dev.usbharu.hideout.service.core.Transaction
|
||||||
import dev.usbharu.hideout.service.reaction.ReactionService
|
import dev.usbharu.hideout.service.reaction.ReactionService
|
||||||
import io.ktor.http.*
|
import io.ktor.http.*
|
||||||
|
@ -20,7 +19,6 @@ class APLikeServiceImpl(
|
||||||
private val reactionService: ReactionService,
|
private val reactionService: ReactionService,
|
||||||
private val apUserService: APUserService,
|
private val apUserService: APUserService,
|
||||||
private val apNoteService: APNoteService,
|
private val apNoteService: APNoteService,
|
||||||
private val userQueryService: UserQueryService,
|
|
||||||
private val postQueryService: PostQueryService,
|
private val postQueryService: PostQueryService,
|
||||||
private val transaction: Transaction
|
private val transaction: Transaction
|
||||||
) : APLikeService {
|
) : APLikeService {
|
||||||
|
@ -28,18 +26,18 @@ class APLikeServiceImpl(
|
||||||
val actor = like.actor ?: throw IllegalActivityPubObjectException("actor is null")
|
val actor = like.actor ?: throw IllegalActivityPubObjectException("actor is null")
|
||||||
val content = like.content ?: throw IllegalActivityPubObjectException("content is null")
|
val content = like.content ?: throw IllegalActivityPubObjectException("content is null")
|
||||||
like.`object` ?: throw IllegalActivityPubObjectException("object is null")
|
like.`object` ?: throw IllegalActivityPubObjectException("object is null")
|
||||||
transaction.transaction {
|
transaction.transaction(java.sql.Connection.TRANSACTION_SERIALIZABLE) {
|
||||||
val person = apUserService.fetchPerson(actor)
|
val person = apUserService.fetchPersonWithEntity(actor)
|
||||||
apNoteService.fetchNote(like.`object`!!)
|
apNoteService.fetchNote(like.`object`!!)
|
||||||
|
|
||||||
val user = userQueryService.findByUrl(
|
|
||||||
person.url
|
|
||||||
?: throw IllegalActivityPubObjectException("actor is not found")
|
|
||||||
)
|
|
||||||
|
|
||||||
val post = postQueryService.findByUrl(like.`object`!!)
|
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, "")
|
return ActivityPubStringResponse(HttpStatusCode.OK, "")
|
||||||
}
|
}
|
||||||
|
|
|
@ -120,7 +120,8 @@ class APNoteServiceImpl(
|
||||||
url: String
|
url: String
|
||||||
): Note {
|
): Note {
|
||||||
if (note.id == null) {
|
if (note.id == null) {
|
||||||
return internalNote(note, targetActor, url)
|
throw IllegalArgumentException("id is null")
|
||||||
|
// return internalNote(note, targetActor, url)
|
||||||
}
|
}
|
||||||
|
|
||||||
val findByApId = try {
|
val findByApId = try {
|
||||||
|
@ -154,7 +155,7 @@ class APNoteServiceImpl(
|
||||||
}
|
}
|
||||||
|
|
||||||
postRepository.save(
|
postRepository.save(
|
||||||
Post(
|
Post.of(
|
||||||
id = postRepository.generateId(),
|
id = postRepository.generateId(),
|
||||||
userId = person.second.id,
|
userId = person.second.id,
|
||||||
overview = null,
|
overview = null,
|
||||||
|
|
|
@ -11,7 +11,6 @@ import dev.usbharu.hideout.plugins.postAp
|
||||||
import dev.usbharu.hideout.query.FollowerQueryService
|
import dev.usbharu.hideout.query.FollowerQueryService
|
||||||
import dev.usbharu.hideout.query.PostQueryService
|
import dev.usbharu.hideout.query.PostQueryService
|
||||||
import dev.usbharu.hideout.query.UserQueryService
|
import dev.usbharu.hideout.query.UserQueryService
|
||||||
import dev.usbharu.hideout.repository.PostRepository
|
|
||||||
import dev.usbharu.hideout.service.job.JobQueueParentService
|
import dev.usbharu.hideout.service.job.JobQueueParentService
|
||||||
import io.ktor.client.*
|
import io.ktor.client.*
|
||||||
import kjob.core.job.JobProps
|
import kjob.core.job.JobProps
|
||||||
|
@ -28,7 +27,6 @@ interface APReactionService {
|
||||||
@Single
|
@Single
|
||||||
class APReactionServiceImpl(
|
class APReactionServiceImpl(
|
||||||
private val jobQueueParentService: JobQueueParentService,
|
private val jobQueueParentService: JobQueueParentService,
|
||||||
private val postRepository: PostRepository,
|
|
||||||
private val httpClient: HttpClient,
|
private val httpClient: HttpClient,
|
||||||
private val userQueryService: UserQueryService,
|
private val userQueryService: UserQueryService,
|
||||||
private val followerQueryService: FollowerQueryService,
|
private val followerQueryService: FollowerQueryService,
|
||||||
|
|
|
@ -6,7 +6,13 @@ import org.koin.core.annotation.Single
|
||||||
@Single
|
@Single
|
||||||
class ExposedTransaction : Transaction {
|
class ExposedTransaction : Transaction {
|
||||||
override suspend fun <T> transaction(block: suspend () -> T): T {
|
override suspend fun <T> transaction(block: suspend () -> T): T {
|
||||||
return newSuspendedTransaction {
|
return newSuspendedTransaction(transactionIsolation = java.sql.Connection.TRANSACTION_SERIALIZABLE) {
|
||||||
|
block()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun <T> transaction(transactionLevel: Int, block: suspend () -> T): T {
|
||||||
|
return newSuspendedTransaction(transactionIsolation = transactionLevel) {
|
||||||
block()
|
block()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,4 +2,5 @@ package dev.usbharu.hideout.service.core
|
||||||
|
|
||||||
interface Transaction {
|
interface Transaction {
|
||||||
suspend fun <T> transaction(block: suspend () -> T): T
|
suspend fun <T> transaction(block: suspend () -> T): T
|
||||||
|
suspend fun <T> transaction(transactionLevel: Int, block: suspend () -> T): T
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ class PostServiceImpl(
|
||||||
override suspend fun createLocal(post: PostCreateDto): Post {
|
override suspend fun createLocal(post: PostCreateDto): Post {
|
||||||
val user = userRepository.findById(post.userId) ?: throw UserNotFoundException("${post.userId} was not found")
|
val user = userRepository.findById(post.userId) ?: throw UserNotFoundException("${post.userId} was not found")
|
||||||
val id = postRepository.generateId()
|
val id = postRepository.generateId()
|
||||||
val createPost = Post(
|
val createPost = Post.of(
|
||||||
id = id,
|
id = id,
|
||||||
userId = post.userId,
|
userId = post.userId,
|
||||||
overview = post.overview,
|
overview = post.overview,
|
||||||
|
|
|
@ -32,7 +32,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 userEntity = User(
|
val userEntity = User.of(
|
||||||
id = nextId,
|
id = nextId,
|
||||||
name = user.name,
|
name = user.name,
|
||||||
domain = Config.configData.domain,
|
domain = Config.configData.domain,
|
||||||
|
@ -51,7 +51,7 @@ class UserServiceImpl(
|
||||||
|
|
||||||
override suspend fun createRemoteUser(user: RemoteUserCreateDto): User {
|
override suspend fun createRemoteUser(user: RemoteUserCreateDto): User {
|
||||||
val nextId = userRepository.nextId()
|
val nextId = userRepository.nextId()
|
||||||
val userEntity = User(
|
val userEntity = User.of(
|
||||||
id = nextId,
|
id = nextId,
|
||||||
name = user.name,
|
name = user.name,
|
||||||
domain = user.domain,
|
domain = user.domain,
|
||||||
|
|
|
@ -19,11 +19,25 @@ hideout {
|
||||||
username = ""
|
username = ""
|
||||||
password = ""
|
password = ""
|
||||||
}
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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'"
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,19 +24,19 @@ class ActivityPubKtTest {
|
||||||
val keyPairGenerator = KeyPairGenerator.getInstance("RSA")
|
val keyPairGenerator = KeyPairGenerator.getInstance("RSA")
|
||||||
keyPairGenerator.initialize(1024)
|
keyPairGenerator.initialize(1024)
|
||||||
val generateKeyPair = keyPairGenerator.generateKeyPair()
|
val generateKeyPair = keyPairGenerator.generateKeyPair()
|
||||||
User(
|
User.of(
|
||||||
1,
|
id = 1,
|
||||||
"test",
|
name = "test",
|
||||||
"localhost",
|
domain = "localhost",
|
||||||
"test",
|
screenName = "test",
|
||||||
"",
|
description = "",
|
||||||
"",
|
password = "",
|
||||||
"",
|
inbox = "https://example.com/inbox",
|
||||||
"",
|
outbox = "https://example.com/outbox",
|
||||||
"",
|
url = "https://example.com",
|
||||||
"",
|
publicKey = "",
|
||||||
generateKeyPair.private.toPem(),
|
privateKey = generateKeyPair.private.toPem(),
|
||||||
Instant.now()
|
createdAt = Instant.now()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,16 +20,16 @@ class KtorKeyMapTest {
|
||||||
val keyPairGenerator = KeyPairGenerator.getInstance("RSA")
|
val keyPairGenerator = KeyPairGenerator.getInstance("RSA")
|
||||||
keyPairGenerator.initialize(1024)
|
keyPairGenerator.initialize(1024)
|
||||||
val generateKeyPair = keyPairGenerator.generateKeyPair()
|
val generateKeyPair = keyPairGenerator.generateKeyPair()
|
||||||
User(
|
User.of(
|
||||||
1,
|
1,
|
||||||
"test",
|
"test",
|
||||||
"localhost",
|
"localhost",
|
||||||
"test",
|
"test",
|
||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
"",
|
"https://example.com/inbox",
|
||||||
"",
|
"https://example.com/outbox",
|
||||||
"",
|
"https://example.com",
|
||||||
"",
|
"",
|
||||||
generateKeyPair.private.toPem(),
|
generateKeyPair.private.toPem(),
|
||||||
createdAt = Instant.now()
|
createdAt = Instant.now()
|
||||||
|
|
|
@ -53,7 +53,7 @@ class SecurityKtTest {
|
||||||
}
|
}
|
||||||
val metaService = mock<MetaService>()
|
val metaService = mock<MetaService>()
|
||||||
val userQueryService = mock<UserQueryService> {
|
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,
|
id = 1L,
|
||||||
name = "testUser",
|
name = "testUser",
|
||||||
domain = "example.com",
|
domain = "example.com",
|
||||||
|
|
|
@ -172,7 +172,7 @@ class UsersAPTest {
|
||||||
config = ApplicationConfig("empty.conf")
|
config = ApplicationConfig("empty.conf")
|
||||||
}
|
}
|
||||||
val userService = mock<UserQueryService> {
|
val userService = mock<UserQueryService> {
|
||||||
onBlocking { findByNameAndDomain(eq("test"), anyString()) } doReturn User(
|
onBlocking { findByNameAndDomain(eq("test"), anyString()) } doReturn User.of(
|
||||||
1L,
|
1L,
|
||||||
"test",
|
"test",
|
||||||
"example.com",
|
"example.com",
|
||||||
|
|
|
@ -80,7 +80,7 @@ class UsersTest {
|
||||||
val userCreateDto = UserCreate("test", "XXXXXXX")
|
val userCreateDto = UserCreate("test", "XXXXXXX")
|
||||||
val userService = mock<UserService> {
|
val userService = mock<UserService> {
|
||||||
onBlocking { usernameAlreadyUse(any()) } doReturn false
|
onBlocking { usernameAlreadyUse(any()) } doReturn false
|
||||||
onBlocking { createLocalUser(any()) } doReturn User(
|
onBlocking { createLocalUser(any()) } doReturn User.of(
|
||||||
id = 12345,
|
id = 12345,
|
||||||
name = "test",
|
name = "test",
|
||||||
domain = "example.com",
|
domain = "example.com",
|
||||||
|
|
|
@ -29,7 +29,7 @@ class APNoteServiceImplTest {
|
||||||
@Test
|
@Test
|
||||||
fun `createPost 新しい投稿`() = runTest {
|
fun `createPost 新しい投稿`() = runTest {
|
||||||
val followers = listOf<User>(
|
val followers = listOf<User>(
|
||||||
User(
|
User.of(
|
||||||
2L,
|
2L,
|
||||||
"follower",
|
"follower",
|
||||||
"follower.example.com",
|
"follower.example.com",
|
||||||
|
@ -38,11 +38,11 @@ class APNoteServiceImplTest {
|
||||||
"https://follower.example.com/inbox",
|
"https://follower.example.com/inbox",
|
||||||
"https://follower.example.com/outbox",
|
"https://follower.example.com/outbox",
|
||||||
"https://follower.example.com",
|
"https://follower.example.com",
|
||||||
"",
|
"https://follower.example.com",
|
||||||
publicKey = "",
|
publicKey = "",
|
||||||
createdAt = Instant.now()
|
createdAt = Instant.now()
|
||||||
),
|
),
|
||||||
User(
|
User.of(
|
||||||
3L,
|
3L,
|
||||||
"follower2",
|
"follower2",
|
||||||
"follower2.example.com",
|
"follower2.example.com",
|
||||||
|
@ -51,23 +51,24 @@ class APNoteServiceImplTest {
|
||||||
"https://follower2.example.com/inbox",
|
"https://follower2.example.com/inbox",
|
||||||
"https://follower2.example.com/outbox",
|
"https://follower2.example.com/outbox",
|
||||||
"https://follower2.example.com",
|
"https://follower2.example.com",
|
||||||
"",
|
"https://follower2.example.com",
|
||||||
publicKey = "",
|
publicKey = "",
|
||||||
createdAt = Instant.now()
|
createdAt = Instant.now()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
val userQueryService = mock<UserQueryService> {
|
val userQueryService = mock<UserQueryService> {
|
||||||
onBlocking { findById(eq(1L)) } doReturn User(
|
onBlocking { findById(eq(1L)) } doReturn User.of(
|
||||||
1L,
|
1L,
|
||||||
"test",
|
"test",
|
||||||
"example.com",
|
"example.com",
|
||||||
"testUser",
|
"testUser",
|
||||||
"test user",
|
"test user",
|
||||||
|
"a",
|
||||||
"https://example.com/inbox",
|
"https://example.com/inbox",
|
||||||
"https://example.com/outbox",
|
"https://example.com/outbox",
|
||||||
"https:.//example.com",
|
"https://example.com",
|
||||||
"",
|
|
||||||
publicKey = "",
|
publicKey = "",
|
||||||
|
privateKey = "a",
|
||||||
createdAt = Instant.now()
|
createdAt = Instant.now()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -85,7 +86,7 @@ class APNoteServiceImplTest {
|
||||||
followerQueryService,
|
followerQueryService,
|
||||||
mock()
|
mock()
|
||||||
)
|
)
|
||||||
val postEntity = Post(
|
val postEntity = Post.of(
|
||||||
1L,
|
1L,
|
||||||
1L,
|
1L,
|
||||||
null,
|
null,
|
||||||
|
|
|
@ -101,7 +101,7 @@ 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(
|
User.of(
|
||||||
id = 1L,
|
id = 1L,
|
||||||
name = "test",
|
name = "test",
|
||||||
domain = "example.com",
|
domain = "example.com",
|
||||||
|
@ -114,7 +114,7 @@ class APReceiveFollowServiceImplTest {
|
||||||
createdAt = Instant.now()
|
createdAt = Instant.now()
|
||||||
)
|
)
|
||||||
onBlocking { findByUrl(eq("https://follower.example.com")) } doReturn
|
onBlocking { findByUrl(eq("https://follower.example.com")) } doReturn
|
||||||
User(
|
User.of(
|
||||||
id = 2L,
|
id = 2L,
|
||||||
name = "follower",
|
name = "follower",
|
||||||
domain = "follower.example.com",
|
domain = "follower.example.com",
|
||||||
|
|
|
@ -54,7 +54,7 @@ class JwtServiceImplTest {
|
||||||
}
|
}
|
||||||
val jwtService = JwtServiceImpl(metaService, refreshTokenRepository, mock(), mock())
|
val jwtService = JwtServiceImpl(metaService, refreshTokenRepository, mock(), mock())
|
||||||
val token = jwtService.createToken(
|
val token = jwtService.createToken(
|
||||||
User(
|
User.of(
|
||||||
id = 1L,
|
id = 1L,
|
||||||
name = "test",
|
name = "test",
|
||||||
domain = "example.com",
|
domain = "example.com",
|
||||||
|
@ -108,7 +108,7 @@ class JwtServiceImplTest {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val userService = mock<UserQueryService> {
|
val userService = mock<UserQueryService> {
|
||||||
onBlocking { findById(1L) } doReturn User(
|
onBlocking { findById(1L) } doReturn User.of(
|
||||||
id = 1L,
|
id = 1L,
|
||||||
name = "test",
|
name = "test",
|
||||||
domain = "example.com",
|
domain = "example.com",
|
||||||
|
|
|
@ -49,7 +49,7 @@ class UserServiceTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `createRemoteUser リモートユーザーを作成できる`() = runTest {
|
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> {
|
val userRepository = mock<UserRepository> {
|
||||||
onBlocking { nextId() } doReturn 113345L
|
onBlocking { nextId() } doReturn 113345L
|
||||||
|
|
|
@ -4,4 +4,5 @@ import dev.usbharu.hideout.service.core.Transaction
|
||||||
|
|
||||||
object TestTransaction : Transaction {
|
object TestTransaction : Transaction {
|
||||||
override suspend fun <T> transaction(block: suspend () -> T): T = block()
|
override suspend fun <T> transaction(block: suspend () -> T): T = block()
|
||||||
|
override suspend fun <T> transaction(transactionLevel: Int, block: suspend () -> T): T = block()
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue