mirror of https://github.com/usbharu/Hideout.git
commit
42036dd8b0
|
@ -113,10 +113,6 @@ dependencies {
|
||||||
compileOnly("io.swagger.core.v3:swagger-annotations:2.2.6")
|
compileOnly("io.swagger.core.v3:swagger-annotations:2.2.6")
|
||||||
implementation("io.swagger.core.v3:swagger-models:2.2.6")
|
implementation("io.swagger.core.v3:swagger-models:2.2.6")
|
||||||
implementation("org.jetbrains.exposed:exposed-java-time:$exposed_version")
|
implementation("org.jetbrains.exposed:exposed-java-time:$exposed_version")
|
||||||
implementation("org.jetbrains.exposed:spring-transaction:$exposed_version")
|
|
||||||
implementation("org.springframework.data:spring-data-commons")
|
|
||||||
implementation("org.springframework.boot:spring-boot-starter-jdbc")
|
|
||||||
implementation("org.springframework.boot:spring-boot-starter-data-jdbc")
|
|
||||||
testImplementation("org.springframework.boot:spring-boot-test-autoconfigure")
|
testImplementation("org.springframework.boot:spring-boot-test-autoconfigure")
|
||||||
testImplementation("org.springframework.boot:spring-boot-starter-test")
|
testImplementation("org.springframework.boot:spring-boot-starter-test")
|
||||||
implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310")
|
implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310")
|
||||||
|
@ -149,7 +145,7 @@ dependencies {
|
||||||
implementation("org.drewcarlson:kjob-mongo:0.6.0")
|
implementation("org.drewcarlson:kjob-mongo:0.6.0")
|
||||||
testImplementation("org.slf4j:slf4j-simple:2.0.7")
|
testImplementation("org.slf4j:slf4j-simple:2.0.7")
|
||||||
|
|
||||||
detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:1.22.0")
|
detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:1.23.1")
|
||||||
}
|
}
|
||||||
|
|
||||||
detekt {
|
detekt {
|
||||||
|
|
|
@ -3,9 +3,11 @@ package dev.usbharu.hideout
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication
|
import org.springframework.boot.autoconfigure.SpringBootApplication
|
||||||
import org.springframework.boot.context.properties.ConfigurationPropertiesScan
|
import org.springframework.boot.context.properties.ConfigurationPropertiesScan
|
||||||
import org.springframework.boot.runApplication
|
import org.springframework.boot.runApplication
|
||||||
|
import org.springframework.cache.annotation.EnableCaching
|
||||||
|
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
@ConfigurationPropertiesScan
|
@ConfigurationPropertiesScan
|
||||||
|
@EnableCaching
|
||||||
class SpringApplication
|
class SpringApplication
|
||||||
|
|
||||||
@Suppress("SpreadOperator")
|
@Suppress("SpreadOperator")
|
||||||
|
|
|
@ -6,6 +6,7 @@ import dev.usbharu.hideout.query.UserQueryService
|
||||||
import dev.usbharu.hideout.service.core.Transaction
|
import dev.usbharu.hideout.service.core.Transaction
|
||||||
import io.ktor.client.*
|
import io.ktor.client.*
|
||||||
import io.ktor.client.engine.cio.*
|
import io.ktor.client.engine.cio.*
|
||||||
|
import io.ktor.client.plugins.cache.*
|
||||||
import io.ktor.client.plugins.logging.*
|
import io.ktor.client.plugins.logging.*
|
||||||
import org.springframework.context.annotation.Bean
|
import org.springframework.context.annotation.Bean
|
||||||
import org.springframework.context.annotation.Configuration
|
import org.springframework.context.annotation.Configuration
|
||||||
|
@ -20,7 +21,9 @@ class HttpClientConfig {
|
||||||
}
|
}
|
||||||
install(Logging) {
|
install(Logging) {
|
||||||
logger = Logger.DEFAULT
|
logger = Logger.DEFAULT
|
||||||
level = LogLevel.ALL
|
level = LogLevel.INFO
|
||||||
|
}
|
||||||
|
install(HttpCache) {
|
||||||
}
|
}
|
||||||
expectSuccess = true
|
expectSuccess = true
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package dev.usbharu.hideout.config
|
package dev.usbharu.hideout.config
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude
|
||||||
import com.nimbusds.jose.jwk.JWKSet
|
import com.nimbusds.jose.jwk.JWKSet
|
||||||
import com.nimbusds.jose.jwk.RSAKey
|
import com.nimbusds.jose.jwk.RSAKey
|
||||||
import com.nimbusds.jose.jwk.source.ImmutableJWKSet
|
import com.nimbusds.jose.jwk.source.ImmutableJWKSet
|
||||||
|
@ -8,12 +9,16 @@ import com.nimbusds.jose.proc.SecurityContext
|
||||||
import dev.usbharu.hideout.domain.model.UserDetailsImpl
|
import dev.usbharu.hideout.domain.model.UserDetailsImpl
|
||||||
import dev.usbharu.hideout.util.RsaUtil
|
import dev.usbharu.hideout.util.RsaUtil
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
|
||||||
|
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer
|
||||||
import org.springframework.boot.autoconfigure.security.servlet.PathRequest
|
import org.springframework.boot.autoconfigure.security.servlet.PathRequest
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties
|
import org.springframework.boot.context.properties.ConfigurationProperties
|
||||||
import org.springframework.context.annotation.Bean
|
import org.springframework.context.annotation.Bean
|
||||||
import org.springframework.context.annotation.Configuration
|
import org.springframework.context.annotation.Configuration
|
||||||
|
import org.springframework.context.annotation.Primary
|
||||||
import org.springframework.core.annotation.Order
|
import org.springframework.core.annotation.Order
|
||||||
import org.springframework.http.HttpMethod
|
import org.springframework.http.HttpMethod
|
||||||
|
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder
|
||||||
|
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter
|
||||||
import org.springframework.security.config.Customizer
|
import org.springframework.security.config.Customizer
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
|
||||||
|
@ -155,6 +160,21 @@ class SecurityConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@Primary
|
||||||
|
fun jackson2ObjectMapperBuilderCustomizer(): Jackson2ObjectMapperBuilderCustomizer {
|
||||||
|
return Jackson2ObjectMapperBuilderCustomizer {
|
||||||
|
it.serializationInclusion(JsonInclude.Include.ALWAYS).serializers()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
fun mappingJackson2HttpMessageConverter(): MappingJackson2HttpMessageConverter {
|
||||||
|
val builder = Jackson2ObjectMapperBuilder()
|
||||||
|
.serializationInclusion(JsonInclude.Include.NON_NULL)
|
||||||
|
return MappingJackson2HttpMessageConverter(builder.build())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ConfigurationProperties("hideout.security.jwt")
|
@ConfigurationProperties("hideout.security.jwt")
|
||||||
|
|
|
@ -2,6 +2,7 @@ package dev.usbharu.hideout.controller
|
||||||
|
|
||||||
import dev.usbharu.hideout.service.ap.APService
|
import dev.usbharu.hideout.service.ap.APService
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
import org.springframework.http.HttpStatus
|
import org.springframework.http.HttpStatus
|
||||||
import org.springframework.http.ResponseEntity
|
import org.springframework.http.ResponseEntity
|
||||||
import org.springframework.web.bind.annotation.RequestBody
|
import org.springframework.web.bind.annotation.RequestBody
|
||||||
|
@ -11,7 +12,12 @@ import org.springframework.web.bind.annotation.RestController
|
||||||
class InboxControllerImpl(private val apService: APService) : InboxController {
|
class InboxControllerImpl(private val apService: APService) : InboxController {
|
||||||
override fun inbox(@RequestBody string: String): ResponseEntity<Unit> = runBlocking {
|
override fun inbox(@RequestBody string: String): ResponseEntity<Unit> = runBlocking {
|
||||||
val parseActivity = apService.parseActivity(string)
|
val parseActivity = apService.parseActivity(string)
|
||||||
|
LOGGER.info("INBOX Processing Activity Type: {}", parseActivity)
|
||||||
apService.processActivity(string, parseActivity)
|
apService.processActivity(string, parseActivity)
|
||||||
ResponseEntity(HttpStatus.ACCEPTED)
|
ResponseEntity(HttpStatus.ACCEPTED)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val LOGGER = LoggerFactory.getLogger(InboxControllerImpl::class.java)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,6 @@ import org.springframework.web.bind.annotation.RequestParam
|
||||||
@Controller
|
@Controller
|
||||||
class MastodonAppsApiController(private val appApiService: AppApiService) : AppApi {
|
class MastodonAppsApiController(private val appApiService: AppApiService) : AppApi {
|
||||||
override fun apiV1AppsPost(appsRequest: AppsRequest): ResponseEntity<Application> = runBlocking {
|
override fun apiV1AppsPost(appsRequest: AppsRequest): ResponseEntity<Application> = runBlocking {
|
||||||
println(appsRequest)
|
|
||||||
ResponseEntity(
|
ResponseEntity(
|
||||||
appApiService.createApp(appsRequest),
|
appApiService.createApp(appsRequest),
|
||||||
HttpStatus.OK
|
HttpStatus.OK
|
||||||
|
|
|
@ -13,7 +13,9 @@ import org.springframework.stereotype.Controller
|
||||||
|
|
||||||
@Controller
|
@Controller
|
||||||
class MastodonStatusesApiContoller(private val statusesApiService: StatusesApiService) : StatusApi {
|
class MastodonStatusesApiContoller(private val statusesApiService: StatusesApiService) : StatusApi {
|
||||||
override fun apiV1StatusesPost(devUsbharuHideoutDomainModelMastodonStatusesRequest: StatusesRequest): ResponseEntity<Status> {
|
override fun apiV1StatusesPost(
|
||||||
|
devUsbharuHideoutDomainModelMastodonStatusesRequest: StatusesRequest
|
||||||
|
): ResponseEntity<Status> {
|
||||||
return runBlocking {
|
return runBlocking {
|
||||||
val jwt = SecurityContextHolder.getContext().authentication.principal as Jwt
|
val jwt = SecurityContextHolder.getContext().authentication.principal as Jwt
|
||||||
|
|
||||||
|
|
|
@ -51,7 +51,6 @@ class UserDetailsDeserializer : JsonDeserializer<UserDetailsImpl>() {
|
||||||
override fun deserialize(p: JsonParser, ctxt: DeserializationContext): UserDetailsImpl {
|
override fun deserialize(p: JsonParser, ctxt: DeserializationContext): UserDetailsImpl {
|
||||||
val mapper = p.codec as ObjectMapper
|
val mapper = p.codec as ObjectMapper
|
||||||
val jsonNode: JsonNode = mapper.readTree(p)
|
val jsonNode: JsonNode = mapper.readTree(p)
|
||||||
println(jsonNode)
|
|
||||||
val authorities: Set<GrantedAuthority> = mapper.convertValue(
|
val authorities: Set<GrantedAuthority> = mapper.convertValue(
|
||||||
jsonNode["authorities"],
|
jsonNode["authorities"],
|
||||||
SIMPLE_GRANTED_AUTHORITY_SET
|
SIMPLE_GRANTED_AUTHORITY_SET
|
||||||
|
|
|
@ -54,7 +54,6 @@ open class Object : JsonLd {
|
||||||
|
|
||||||
class TypeSerializer : JsonSerializer<List<String>>() {
|
class TypeSerializer : JsonSerializer<List<String>>() {
|
||||||
override fun serialize(value: List<String>?, gen: JsonGenerator?, serializers: SerializerProvider?) {
|
override fun serialize(value: List<String>?, gen: JsonGenerator?, serializers: SerializerProvider?) {
|
||||||
println(value)
|
|
||||||
if (value?.size == 1) {
|
if (value?.size == 1) {
|
||||||
gen?.writeString(value[0])
|
gen?.writeString(value[0])
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -34,7 +34,6 @@ class ObjectDeserializer : JsonDeserializer<Object>() {
|
||||||
return when (activityType) {
|
return when (activityType) {
|
||||||
ExtendedActivityVocabulary.Follow -> {
|
ExtendedActivityVocabulary.Follow -> {
|
||||||
val readValue = p.codec.treeToValue(treeNode, Follow::class.java)
|
val readValue = p.codec.treeToValue(treeNode, Follow::class.java)
|
||||||
println(readValue)
|
|
||||||
readValue
|
readValue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
package dev.usbharu.hideout.domain.model.hideout.entity
|
package dev.usbharu.hideout.domain.model.hideout.entity
|
||||||
|
|
||||||
import org.springframework.data.annotation.Id
|
import org.springframework.data.annotation.Id
|
||||||
|
import org.springframework.data.mongodb.core.mapping.Document
|
||||||
|
|
||||||
|
@Document
|
||||||
data class Timeline(
|
data class Timeline(
|
||||||
@Id
|
@Id
|
||||||
val id: Long,
|
val id: Long,
|
||||||
|
|
|
@ -72,6 +72,6 @@ class StatusesRequest {
|
||||||
`public`,
|
`public`,
|
||||||
unlisted,
|
unlisted,
|
||||||
private,
|
private,
|
||||||
direct;
|
direct
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
package dev.usbharu.hideout.exception.ap
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.exception.FailedToGetResourcesException
|
||||||
|
|
||||||
|
class FailedToGetActivityPubResourceException : FailedToGetResourcesException {
|
||||||
|
constructor() : super()
|
||||||
|
constructor(s: String?) : super(s)
|
||||||
|
constructor(message: String?, cause: Throwable?) : super(message, cause)
|
||||||
|
constructor(cause: Throwable?) : super(cause)
|
||||||
|
}
|
|
@ -67,11 +67,9 @@ val httpSignaturePlugin: ClientPlugin<HttpSignaturePluginConfig> = createClientP
|
||||||
|
|
||||||
request.header("Date", format.format(Date()))
|
request.header("Date", format.format(Date()))
|
||||||
request.header("Host", request.url.host)
|
request.header("Host", request.url.host)
|
||||||
println(request.bodyType)
|
|
||||||
println(request.bodyType?.type)
|
|
||||||
if (request.bodyType?.type == String::class) {
|
if (request.bodyType?.type == String::class) {
|
||||||
println(body as String)
|
body as String
|
||||||
println("Digest !!")
|
|
||||||
// UserAuthService.sha256.reset()
|
// UserAuthService.sha256.reset()
|
||||||
val digest =
|
val digest =
|
||||||
Base64.getEncoder().encodeToString(UserAuthServiceImpl.sha256.digest(body.toByteArray(Charsets.UTF_8)))
|
Base64.getEncoder().encodeToString(UserAuthServiceImpl.sha256.digest(body.toByteArray(Charsets.UTF_8)))
|
||||||
|
|
|
@ -6,25 +6,24 @@ import dev.usbharu.hideout.repository.Posts
|
||||||
import dev.usbharu.hideout.repository.PostsMedia
|
import dev.usbharu.hideout.repository.PostsMedia
|
||||||
import dev.usbharu.hideout.repository.toPost
|
import dev.usbharu.hideout.repository.toPost
|
||||||
import dev.usbharu.hideout.util.singleOr
|
import dev.usbharu.hideout.util.singleOr
|
||||||
import org.jetbrains.exposed.sql.innerJoin
|
|
||||||
import org.jetbrains.exposed.sql.select
|
import org.jetbrains.exposed.sql.select
|
||||||
import org.springframework.stereotype.Repository
|
import org.springframework.stereotype.Repository
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
class PostQueryServiceImpl : PostQueryService {
|
class PostQueryServiceImpl : PostQueryService {
|
||||||
override suspend fun findById(id: Long): Post =
|
override suspend fun findById(id: Long): Post =
|
||||||
Posts.innerJoin(PostsMedia, onColumn = { Posts.id }, otherColumn = { PostsMedia.postId })
|
Posts.leftJoin(PostsMedia)
|
||||||
.select { Posts.id eq id }
|
.select { Posts.id eq id }
|
||||||
.singleOr { FailedToGetResourcesException("id: $id is duplicate or does not exist.", it) }.toPost()
|
.singleOr { FailedToGetResourcesException("id: $id is duplicate or does not exist.", it) }.toPost()
|
||||||
|
|
||||||
override suspend fun findByUrl(url: String): Post =
|
override suspend fun findByUrl(url: String): Post =
|
||||||
Posts.innerJoin(PostsMedia, onColumn = { Posts.id }, otherColumn = { PostsMedia.postId })
|
Posts.leftJoin(PostsMedia)
|
||||||
.select { Posts.url eq url }
|
.select { Posts.url eq url }
|
||||||
.toPost()
|
.toPost()
|
||||||
.singleOr { FailedToGetResourcesException("url: $url is duplicate or does not exist.", it) }
|
.singleOr { FailedToGetResourcesException("url: $url is duplicate or does not exist.", it) }
|
||||||
|
|
||||||
override suspend fun findByApId(string: String): Post =
|
override suspend fun findByApId(string: String): Post =
|
||||||
Posts.innerJoin(PostsMedia, onColumn = { Posts.id }, otherColumn = { PostsMedia.postId })
|
Posts.leftJoin(PostsMedia)
|
||||||
.select { Posts.apId eq string }
|
.select { Posts.apId eq string }
|
||||||
.toPost()
|
.toPost()
|
||||||
.singleOr { FailedToGetResourcesException("apId: $string is duplicate or does not exist.", it) }
|
.singleOr { FailedToGetResourcesException("apId: $string is duplicate or does not exist.", it) }
|
||||||
|
|
|
@ -50,16 +50,16 @@ class StatusQueryServiceImpl : StatusQueryService {
|
||||||
@Suppress("FunctionMaxLength")
|
@Suppress("FunctionMaxLength")
|
||||||
private suspend fun findByPostIdsWithMediaAttachments(ids: List<Long>): List<Status> {
|
private suspend fun findByPostIdsWithMediaAttachments(ids: List<Long>): List<Status> {
|
||||||
val pairs = Posts
|
val pairs = Posts
|
||||||
.innerJoin(PostsMedia, onColumn = { Posts.id }, otherColumn = { PostsMedia.postId })
|
.leftJoin(PostsMedia)
|
||||||
.innerJoin(Users, onColumn = { Posts.userId }, otherColumn = { id })
|
.leftJoin(Users)
|
||||||
.innerJoin(Media, onColumn = { PostsMedia.mediaId }, otherColumn = { id })
|
.leftJoin(Media)
|
||||||
.select { Posts.id inList ids }
|
.select { Posts.id inList ids }
|
||||||
.groupBy { it[Posts.id] }
|
.groupBy { it[Posts.id] }
|
||||||
.map { it.value }
|
.map { it.value }
|
||||||
.map {
|
.map {
|
||||||
toStatus(it.first()).copy(
|
toStatus(it.first()).copy(
|
||||||
mediaAttachments = it.map {
|
mediaAttachments = it.mapNotNull {
|
||||||
it.toMedia().let {
|
it.toMediaOrNull()?.let {
|
||||||
MediaAttachment(
|
MediaAttachment(
|
||||||
id = it.id.toString(),
|
id = it.id.toString(),
|
||||||
type = when (it.type) {
|
type = when (it.type) {
|
||||||
|
@ -132,7 +132,7 @@ private fun toStatus(it: ResultRow) = Status(
|
||||||
favouritesCount = 0,
|
favouritesCount = 0,
|
||||||
repliesCount = 0,
|
repliesCount = 0,
|
||||||
url = it[Posts.apId],
|
url = it[Posts.apId],
|
||||||
inReplyToId = it[Posts.replyId].toString(),
|
inReplyToId = it[Posts.replyId]?.toString(),
|
||||||
inReplyToAccountId = null,
|
inReplyToAccountId = null,
|
||||||
language = null,
|
language = null,
|
||||||
text = it[Posts.text],
|
text = it[Posts.text],
|
||||||
|
|
|
@ -69,6 +69,18 @@ fun ResultRow.toMedia(): EntityMedia {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun ResultRow.toMediaOrNull(): EntityMedia? {
|
||||||
|
return EntityMedia(
|
||||||
|
id = this.getOrNull(Media.id) ?: return null,
|
||||||
|
name = this.getOrNull(Media.name) ?: return null,
|
||||||
|
url = this.getOrNull(Media.url) ?: return null,
|
||||||
|
remoteUrl = this[Media.remoteUrl],
|
||||||
|
thumbnailUrl = this[Media.thumbnailUrl],
|
||||||
|
type = FileType.values().first { it.ordinal == this.getOrNull(Media.type) },
|
||||||
|
blurHash = this[Media.blurhash],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
object Media : Table("media") {
|
object Media : Table("media") {
|
||||||
val id = long("id")
|
val id = long("id")
|
||||||
val name = varchar("name", 255)
|
val name = varchar("name", 255)
|
||||||
|
|
|
@ -54,6 +54,11 @@ class PostRepositoryImpl(private val idGenerateService: IdGenerateService) : Pos
|
||||||
it[apId] = post.apId
|
it[apId] = post.apId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert(Posts.select { Posts.id eq post.id }.singleOrNull() != null) {
|
||||||
|
"Faild to insert"
|
||||||
|
}
|
||||||
|
|
||||||
return post
|
return post
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,5 +114,5 @@ fun ResultRow.toPost(): Post {
|
||||||
fun Query.toPost(): List<Post> {
|
fun Query.toPost(): List<Post> {
|
||||||
return this.groupBy { it[Posts.id] }
|
return this.groupBy { it[Posts.id] }
|
||||||
.map { it.value }
|
.map { it.value }
|
||||||
.map { it.first().toPost().copy(mediaIds = it.map { it[PostsMedia.mediaId] }) }
|
.map { it.first().toPost().copy(mediaIds = it.mapNotNull { it.getOrNull(PostsMedia.mediaId) }) }
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,8 +14,8 @@ class UserRepositoryImpl(private val idGenerateService: IdGenerateService) :
|
||||||
UserRepository {
|
UserRepository {
|
||||||
|
|
||||||
override suspend fun save(user: User): User {
|
override suspend fun save(user: User): User {
|
||||||
val singleOrNull = Users.select { Users.id eq user.id }.singleOrNull()
|
val singleOrNull = Users.select { Users.id eq user.id or (Users.url eq user.url) }.empty()
|
||||||
if (singleOrNull == null) {
|
if (singleOrNull) {
|
||||||
Users.insert {
|
Users.insert {
|
||||||
it[id] = user.id
|
it[id] = user.id
|
||||||
it[name] = user.name
|
it[name] = user.name
|
||||||
|
|
|
@ -10,6 +10,7 @@ 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.user.UserService
|
import dev.usbharu.hideout.service.user.UserService
|
||||||
import io.ktor.http.*
|
import io.ktor.http.*
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
|
|
||||||
interface APAcceptService {
|
interface APAcceptService {
|
||||||
|
@ -25,21 +26,32 @@ class APAcceptServiceImpl(
|
||||||
) : APAcceptService {
|
) : APAcceptService {
|
||||||
override suspend fun receiveAccept(accept: Accept): ActivityPubResponse {
|
override suspend fun receiveAccept(accept: Accept): ActivityPubResponse {
|
||||||
return transaction.transaction {
|
return transaction.transaction {
|
||||||
|
LOGGER.debug("START Follow")
|
||||||
|
LOGGER.trace("{}", accept)
|
||||||
val value = accept.`object` ?: throw IllegalActivityPubObjectException("object is null")
|
val value = accept.`object` ?: throw IllegalActivityPubObjectException("object is null")
|
||||||
if (value.type.contains("Follow").not()) {
|
if (value.type.contains("Follow").not()) {
|
||||||
|
LOGGER.warn("FAILED Activity type is not 'Follow'")
|
||||||
throw IllegalActivityPubObjectException("Invalid type ${value.type}")
|
throw IllegalActivityPubObjectException("Invalid type ${value.type}")
|
||||||
}
|
}
|
||||||
|
|
||||||
val follow = value as Follow
|
val follow = value as Follow
|
||||||
val userUrl = follow.`object` ?: throw IllegalActivityPubObjectException("object is null")
|
val userUrl = follow.`object` ?: throw IllegalActivityPubObjectException("object is null")
|
||||||
val followerUrl = follow.actor ?: throw IllegalActivityPubObjectException("actor is null")
|
val followerUrl = follow.actor ?: throw IllegalActivityPubObjectException("actor is null")
|
||||||
|
|
||||||
val user = userQueryService.findByUrl(userUrl)
|
val user = userQueryService.findByUrl(userUrl)
|
||||||
val follower = userQueryService.findByUrl(followerUrl)
|
val follower = userQueryService.findByUrl(followerUrl)
|
||||||
|
|
||||||
if (followerQueryService.alreadyFollow(user.id, follower.id)) {
|
if (followerQueryService.alreadyFollow(user.id, follower.id)) {
|
||||||
|
LOGGER.debug("END User already follow from ${follower.url} to ${user.url}")
|
||||||
return@transaction ActivityPubStringResponse(HttpStatusCode.OK, "accepted")
|
return@transaction ActivityPubStringResponse(HttpStatusCode.OK, "accepted")
|
||||||
}
|
}
|
||||||
userService.follow(user.id, follower.id)
|
userService.follow(user.id, follower.id)
|
||||||
|
LOGGER.debug("SUCCESS Follow from ${follower.url} to ${user.url}.")
|
||||||
ActivityPubStringResponse(HttpStatusCode.OK, "accepted")
|
ActivityPubStringResponse(HttpStatusCode.OK, "accepted")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val LOGGER = LoggerFactory.getLogger(APAcceptServiceImpl::class.java)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import dev.usbharu.hideout.domain.model.ap.Note
|
||||||
import dev.usbharu.hideout.exception.ap.IllegalActivityPubObjectException
|
import dev.usbharu.hideout.exception.ap.IllegalActivityPubObjectException
|
||||||
import dev.usbharu.hideout.service.core.Transaction
|
import dev.usbharu.hideout.service.core.Transaction
|
||||||
import io.ktor.http.*
|
import io.ktor.http.*
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
|
|
||||||
interface APCreateService {
|
interface APCreateService {
|
||||||
|
@ -19,15 +20,24 @@ class APCreateServiceImpl(
|
||||||
private val transaction: Transaction
|
private val transaction: Transaction
|
||||||
) : APCreateService {
|
) : APCreateService {
|
||||||
override suspend fun receiveCreate(create: Create): ActivityPubResponse {
|
override suspend fun receiveCreate(create: Create): ActivityPubResponse {
|
||||||
|
LOGGER.debug("START Create new remote note.")
|
||||||
|
LOGGER.trace("{}", create)
|
||||||
|
|
||||||
val value = create.`object` ?: throw IllegalActivityPubObjectException("object is null")
|
val value = create.`object` ?: throw IllegalActivityPubObjectException("object is null")
|
||||||
if (value.type.contains("Note").not()) {
|
if (value.type.contains("Note").not()) {
|
||||||
|
LOGGER.warn("FAILED Object type is not 'Note'")
|
||||||
throw IllegalActivityPubObjectException("object is not Note")
|
throw IllegalActivityPubObjectException("object is not Note")
|
||||||
}
|
}
|
||||||
|
|
||||||
return transaction.transaction {
|
return transaction.transaction {
|
||||||
val note = value as Note
|
val note = value as Note
|
||||||
apNoteService.fetchNote(note)
|
apNoteService.fetchNote(note)
|
||||||
|
LOGGER.debug("SUCCESS Create new remote note. ${note.id} by ${note.attributedTo}")
|
||||||
ActivityPubStringResponse(HttpStatusCode.OK, "Created")
|
ActivityPubStringResponse(HttpStatusCode.OK, "Created")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val LOGGER = LoggerFactory.getLogger(APCreateServiceImpl::class.java)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,11 +3,13 @@ package dev.usbharu.hideout.service.ap
|
||||||
import dev.usbharu.hideout.domain.model.ActivityPubResponse
|
import dev.usbharu.hideout.domain.model.ActivityPubResponse
|
||||||
import dev.usbharu.hideout.domain.model.ActivityPubStringResponse
|
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.FailedToGetActivityPubResourceException
|
||||||
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.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.*
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
|
|
||||||
interface APLikeService {
|
interface APLikeService {
|
||||||
|
@ -23,14 +25,27 @@ class APLikeServiceImpl(
|
||||||
private val transaction: Transaction
|
private val transaction: Transaction
|
||||||
) : APLikeService {
|
) : APLikeService {
|
||||||
override suspend fun receiveLike(like: Like): ActivityPubResponse {
|
override suspend fun receiveLike(like: Like): ActivityPubResponse {
|
||||||
|
LOGGER.debug("START Add Like")
|
||||||
|
LOGGER.trace("{}", like)
|
||||||
|
|
||||||
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(java.sql.Connection.TRANSACTION_SERIALIZABLE) {
|
transaction.transaction {
|
||||||
|
LOGGER.trace("FETCH Liked Person $actor")
|
||||||
val person = apUserService.fetchPersonWithEntity(actor)
|
val person = apUserService.fetchPersonWithEntity(actor)
|
||||||
apNoteService.fetchNote(like.`object` ?: return@transaction)
|
LOGGER.trace("{}", person.second)
|
||||||
|
|
||||||
|
LOGGER.trace("FETCH Liked Note ${like.`object`}")
|
||||||
|
try {
|
||||||
|
apNoteService.fetchNoteAsync(like.`object` ?: return@transaction).await()
|
||||||
|
} catch (e: FailedToGetActivityPubResourceException) {
|
||||||
|
LOGGER.debug("FAILED Failed to Get ${like.`object`}")
|
||||||
|
LOGGER.trace("", e)
|
||||||
|
return@transaction
|
||||||
|
}
|
||||||
val post = postQueryService.findByUrl(like.`object` ?: return@transaction)
|
val post = postQueryService.findByUrl(like.`object` ?: return@transaction)
|
||||||
|
LOGGER.trace("{}", post)
|
||||||
|
|
||||||
reactionService.receiveReaction(
|
reactionService.receiveReaction(
|
||||||
content,
|
content,
|
||||||
|
@ -38,7 +53,12 @@ class APLikeServiceImpl(
|
||||||
person.second.id,
|
person.second.id,
|
||||||
post.id
|
post.id
|
||||||
)
|
)
|
||||||
|
LOGGER.debug("SUCCESS Add Like($content) from ${person.second.url} to ${post.url}")
|
||||||
}
|
}
|
||||||
return ActivityPubStringResponse(HttpStatusCode.OK, "")
|
return ActivityPubStringResponse(HttpStatusCode.OK, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val LOGGER = LoggerFactory.getLogger(APLikeServiceImpl::class.java)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import dev.usbharu.hideout.domain.model.hideout.entity.Post
|
||||||
import dev.usbharu.hideout.domain.model.hideout.entity.Visibility
|
import dev.usbharu.hideout.domain.model.hideout.entity.Visibility
|
||||||
import dev.usbharu.hideout.domain.model.job.DeliverPostJob
|
import dev.usbharu.hideout.domain.model.job.DeliverPostJob
|
||||||
import dev.usbharu.hideout.exception.FailedToGetResourcesException
|
import dev.usbharu.hideout.exception.FailedToGetResourcesException
|
||||||
|
import dev.usbharu.hideout.exception.ap.FailedToGetActivityPubResourceException
|
||||||
import dev.usbharu.hideout.exception.ap.IllegalActivityPubObjectException
|
import dev.usbharu.hideout.exception.ap.IllegalActivityPubObjectException
|
||||||
import dev.usbharu.hideout.plugins.getAp
|
import dev.usbharu.hideout.plugins.getAp
|
||||||
import dev.usbharu.hideout.plugins.postAp
|
import dev.usbharu.hideout.plugins.postAp
|
||||||
|
@ -22,10 +23,17 @@ import dev.usbharu.hideout.service.job.JobQueueParentService
|
||||||
import dev.usbharu.hideout.service.post.PostCreateInterceptor
|
import dev.usbharu.hideout.service.post.PostCreateInterceptor
|
||||||
import dev.usbharu.hideout.service.post.PostService
|
import dev.usbharu.hideout.service.post.PostService
|
||||||
import io.ktor.client.*
|
import io.ktor.client.*
|
||||||
|
import io.ktor.client.plugins.*
|
||||||
import io.ktor.client.statement.*
|
import io.ktor.client.statement.*
|
||||||
import kjob.core.job.JobProps
|
import kjob.core.job.JobProps
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Deferred
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.async
|
||||||
|
import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import org.springframework.beans.factory.annotation.Qualifier
|
import org.springframework.beans.factory.annotation.Qualifier
|
||||||
|
import org.springframework.cache.annotation.Cacheable
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
|
@ -34,6 +42,15 @@ interface APNoteService {
|
||||||
suspend fun createNote(post: Post)
|
suspend fun createNote(post: Post)
|
||||||
suspend fun createNoteJob(props: JobProps<DeliverPostJob>)
|
suspend fun createNoteJob(props: JobProps<DeliverPostJob>)
|
||||||
|
|
||||||
|
@Cacheable("fetchNote")
|
||||||
|
fun fetchNoteAsync(url: String, targetActor: String? = null): Deferred<Note> {
|
||||||
|
return CoroutineScope(Dispatchers.IO).async {
|
||||||
|
newSuspendedTransaction {
|
||||||
|
fetchNote(url, targetActor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun fetchNote(url: String, targetActor: String? = null): Note
|
suspend fun fetchNote(url: String, targetActor: String? = null): Note
|
||||||
suspend fun fetchNote(note: Note, targetActor: String? = null): Note
|
suspend fun fetchNote(note: Note, targetActor: String? = null): Note
|
||||||
}
|
}
|
||||||
|
@ -62,7 +79,13 @@ class APNoteServiceImpl(
|
||||||
private val logger = LoggerFactory.getLogger(APNoteServiceImpl::class.java)
|
private val logger = LoggerFactory.getLogger(APNoteServiceImpl::class.java)
|
||||||
|
|
||||||
override suspend fun createNote(post: Post) {
|
override suspend fun createNote(post: Post) {
|
||||||
|
logger.info("CREATE Create Local Note ${post.url}")
|
||||||
|
logger.debug("START Create Local Note ${post.url}")
|
||||||
|
logger.trace("{}", post)
|
||||||
val followers = followerQueryService.findFollowersById(post.userId)
|
val followers = followerQueryService.findFollowersById(post.userId)
|
||||||
|
|
||||||
|
logger.debug("DELIVER Deliver Note Create ${followers.size} accounts.")
|
||||||
|
|
||||||
val userEntity = userQueryService.findById(post.userId)
|
val userEntity = userQueryService.findById(post.userId)
|
||||||
val note = objectMapper.writeValueAsString(post)
|
val note = objectMapper.writeValueAsString(post)
|
||||||
val mediaList = objectMapper.writeValueAsString(mediaQueryService.findByPostId(post.id))
|
val mediaList = objectMapper.writeValueAsString(mediaQueryService.findByPostId(post.id))
|
||||||
|
@ -74,6 +97,8 @@ class APNoteServiceImpl(
|
||||||
props[DeliverPostJob.media] = mediaList
|
props[DeliverPostJob.media] = mediaList
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.debug("SUCCESS Create Local Note ${post.url}")
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun createNoteJob(props: JobProps<DeliverPostJob>) {
|
override suspend fun createNoteJob(props: JobProps<DeliverPostJob>) {
|
||||||
|
@ -109,18 +134,32 @@ class APNoteServiceImpl(
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun fetchNote(url: String, targetActor: String?): Note {
|
override suspend fun fetchNote(url: String, targetActor: String?): Note {
|
||||||
|
logger.debug("START Fetch Note url: {}", url)
|
||||||
try {
|
try {
|
||||||
val post = postQueryService.findByUrl(url)
|
val post = postQueryService.findByUrl(url)
|
||||||
|
logger.debug("SUCCESS Found in local url: {}", url)
|
||||||
return postToNote(post)
|
return postToNote(post)
|
||||||
} catch (_: FailedToGetResourcesException) {
|
} catch (_: FailedToGetResourcesException) {
|
||||||
}
|
}
|
||||||
|
|
||||||
val response = httpClient.getAp(
|
logger.info("AP GET url: {}", url)
|
||||||
url,
|
val response = try {
|
||||||
targetActor?.let { "$targetActor#pubkey" }
|
httpClient.getAp(
|
||||||
)
|
url,
|
||||||
|
targetActor?.let { "$targetActor#pubkey" }
|
||||||
|
)
|
||||||
|
} catch (e: ClientRequestException) {
|
||||||
|
logger.warn(
|
||||||
|
"FAILED Failed to retrieve ActivityPub resource. HTTP Status Code: {} url: {}",
|
||||||
|
e.response.status,
|
||||||
|
url
|
||||||
|
)
|
||||||
|
throw FailedToGetActivityPubResourceException("Could not retrieve $url.", e)
|
||||||
|
}
|
||||||
val note = objectMapper.readValue<Note>(response.bodyAsText())
|
val note = objectMapper.readValue<Note>(response.bodyAsText())
|
||||||
return note(note, targetActor, url)
|
val savedNote = saveIfMissing(note, targetActor, url)
|
||||||
|
logger.debug("SUCCESS Fetch Note url: {}", url)
|
||||||
|
return savedNote
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun postToNote(post: Post): Note {
|
private suspend fun postToNote(post: Post): Note {
|
||||||
|
@ -139,7 +178,7 @@ class APNoteServiceImpl(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun note(
|
private suspend fun saveIfMissing(
|
||||||
note: Note,
|
note: Note,
|
||||||
targetActor: String?,
|
targetActor: String?,
|
||||||
url: String
|
url: String
|
||||||
|
@ -152,12 +191,12 @@ class APNoteServiceImpl(
|
||||||
val findByApId = try {
|
val findByApId = try {
|
||||||
postQueryService.findByApId(note.id!!)
|
postQueryService.findByApId(note.id!!)
|
||||||
} catch (_: FailedToGetResourcesException) {
|
} catch (_: FailedToGetResourcesException) {
|
||||||
return internalNote(note, targetActor, url)
|
return saveNote(note, targetActor, url)
|
||||||
}
|
}
|
||||||
return postToNote(findByApId)
|
return postToNote(findByApId)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun internalNote(note: Note, targetActor: String?, url: String): Note {
|
private suspend fun saveNote(note: Note, targetActor: String?, url: String): Note {
|
||||||
val person = apUserService.fetchPersonWithEntity(
|
val person = apUserService.fetchPersonWithEntity(
|
||||||
note.attributedTo ?: throw IllegalActivityPubObjectException("note.attributedTo is null"),
|
note.attributedTo ?: throw IllegalActivityPubObjectException("note.attributedTo is null"),
|
||||||
targetActor
|
targetActor
|
||||||
|
@ -197,7 +236,7 @@ class APNoteServiceImpl(
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun fetchNote(note: Note, targetActor: String?): Note =
|
override suspend fun fetchNote(note: Note, targetActor: String?): Note =
|
||||||
note(note, targetActor, note.id ?: throw IllegalArgumentException("note.id is null"))
|
saveIfMissing(note, targetActor, note.id ?: throw IllegalArgumentException("note.id is null"))
|
||||||
|
|
||||||
override suspend fun run(post: Post) {
|
override suspend fun run(post: Post) {
|
||||||
createNote(post)
|
createNote(post)
|
||||||
|
|
|
@ -203,16 +203,12 @@ class APServiceImpl(
|
||||||
|
|
||||||
@Suppress("CyclomaticComplexMethod", "NotImplementedDeclaration")
|
@Suppress("CyclomaticComplexMethod", "NotImplementedDeclaration")
|
||||||
override suspend fun processActivity(json: String, type: ActivityType): ActivityPubResponse {
|
override suspend fun processActivity(json: String, type: ActivityType): ActivityPubResponse {
|
||||||
logger.debug("proccess activity: {}", type)
|
logger.debug("process activity: {}", type)
|
||||||
return when (type) {
|
return when (type) {
|
||||||
ActivityType.Accept -> apAcceptService.receiveAccept(objectMapper.readValue(json))
|
ActivityType.Accept -> apAcceptService.receiveAccept(objectMapper.readValue(json))
|
||||||
ActivityType.Follow -> apReceiveFollowService.receiveFollow(
|
ActivityType.Follow ->
|
||||||
objectMapper.readValue(
|
apReceiveFollowService
|
||||||
json,
|
.receiveFollow(objectMapper.readValue(json, Follow::class.java))
|
||||||
Follow::class.java
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
ActivityType.Create -> apCreateService.receiveCreate(objectMapper.readValue(json))
|
ActivityType.Create -> apCreateService.receiveCreate(objectMapper.readValue(json))
|
||||||
ActivityType.Like -> apLikeService.receiveLike(objectMapper.readValue(json))
|
ActivityType.Like -> apLikeService.receiveLike(objectMapper.readValue(json))
|
||||||
ActivityType.Undo -> apUndoService.receiveUndo(objectMapper.readValue(json))
|
ActivityType.Undo -> apUndoService.receiveUndo(objectMapper.readValue(json))
|
||||||
|
@ -226,8 +222,6 @@ class APServiceImpl(
|
||||||
override suspend fun <T : HideoutJob> processActivity(job: JobContextWithProps<T>, hideoutJob: HideoutJob) {
|
override suspend fun <T : HideoutJob> processActivity(job: JobContextWithProps<T>, hideoutJob: HideoutJob) {
|
||||||
logger.debug("processActivity: ${hideoutJob.name}")
|
logger.debug("processActivity: ${hideoutJob.name}")
|
||||||
|
|
||||||
// println(apReceiveFollowService::class.java)
|
|
||||||
// apReceiveFollowService.receiveFollowJob(job.props as JobProps<ReceiveFollowJob>)
|
|
||||||
when (hideoutJob) {
|
when (hideoutJob) {
|
||||||
is ReceiveFollowJob -> {
|
is ReceiveFollowJob -> {
|
||||||
apReceiveFollowService.receiveFollowJob(
|
apReceiveFollowService.receiveFollowJob(
|
||||||
|
|
|
@ -39,7 +39,6 @@ class StatsesApiServiceImpl(
|
||||||
statusesRequest: dev.usbharu.hideout.domain.model.mastodon.StatusesRequest,
|
statusesRequest: dev.usbharu.hideout.domain.model.mastodon.StatusesRequest,
|
||||||
userId: Long
|
userId: Long
|
||||||
): Status = transaction.transaction {
|
): Status = transaction.transaction {
|
||||||
println("Post status media ids " + statusesRequest.media_ids)
|
|
||||||
val visibility = when (statusesRequest.visibility) {
|
val visibility = when (statusesRequest.visibility) {
|
||||||
StatusesRequest.Visibility.public -> Visibility.PUBLIC
|
StatusesRequest.Visibility.public -> Visibility.PUBLIC
|
||||||
StatusesRequest.Visibility.unlisted -> Visibility.UNLISTED
|
StatusesRequest.Visibility.unlisted -> Visibility.UNLISTED
|
||||||
|
|
|
@ -6,7 +6,7 @@ import org.springframework.stereotype.Service
|
||||||
@Service
|
@Service
|
||||||
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(transactionIsolation = java.sql.Connection.TRANSACTION_SERIALIZABLE) {
|
return newSuspendedTransaction {
|
||||||
block()
|
block()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
package dev.usbharu.hideout.service.core
|
||||||
|
|
||||||
|
import jakarta.servlet.Filter
|
||||||
|
import jakarta.servlet.FilterChain
|
||||||
|
import jakarta.servlet.ServletRequest
|
||||||
|
import jakarta.servlet.ServletResponse
|
||||||
|
import org.slf4j.MDC
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class MdcXrequestIdFilter : Filter {
|
||||||
|
override fun doFilter(request: ServletRequest?, response: ServletResponse?, chain: FilterChain) {
|
||||||
|
val uuid = UUID.randomUUID()
|
||||||
|
try {
|
||||||
|
MDC.put(KEY, uuid.toString())
|
||||||
|
chain.doFilter(request, response)
|
||||||
|
} finally {
|
||||||
|
MDC.remove(KEY)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val KEY = "x-request-id"
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,8 +3,10 @@ package dev.usbharu.hideout.service.post
|
||||||
import dev.usbharu.hideout.domain.model.hideout.dto.PostCreateDto
|
import dev.usbharu.hideout.domain.model.hideout.dto.PostCreateDto
|
||||||
import dev.usbharu.hideout.domain.model.hideout.entity.Post
|
import dev.usbharu.hideout.domain.model.hideout.entity.Post
|
||||||
import dev.usbharu.hideout.exception.UserNotFoundException
|
import dev.usbharu.hideout.exception.UserNotFoundException
|
||||||
|
import dev.usbharu.hideout.query.PostQueryService
|
||||||
import dev.usbharu.hideout.repository.PostRepository
|
import dev.usbharu.hideout.repository.PostRepository
|
||||||
import dev.usbharu.hideout.repository.UserRepository
|
import dev.usbharu.hideout.repository.UserRepository
|
||||||
|
import org.jetbrains.exposed.exceptions.ExposedSQLException
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
@ -13,7 +15,8 @@ import java.util.*
|
||||||
class PostServiceImpl(
|
class PostServiceImpl(
|
||||||
private val postRepository: PostRepository,
|
private val postRepository: PostRepository,
|
||||||
private val userRepository: UserRepository,
|
private val userRepository: UserRepository,
|
||||||
private val timelineService: TimelineService
|
private val timelineService: TimelineService,
|
||||||
|
private val postQueryService: PostQueryService
|
||||||
) : PostService {
|
) : PostService {
|
||||||
private val interceptors = Collections.synchronizedList(mutableListOf<PostCreateInterceptor>())
|
private val interceptors = Collections.synchronizedList(mutableListOf<PostCreateInterceptor>())
|
||||||
|
|
||||||
|
@ -30,8 +33,13 @@ class PostServiceImpl(
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun internalCreate(post: Post, isLocal: Boolean): Post {
|
private suspend fun internalCreate(post: Post, isLocal: Boolean): Post {
|
||||||
timelineService.publishTimeline(post, isLocal)
|
val save = try {
|
||||||
return postRepository.save(post)
|
postRepository.save(post)
|
||||||
|
} catch (_: ExposedSQLException) {
|
||||||
|
postQueryService.findByApId(post.apId)
|
||||||
|
}
|
||||||
|
timelineService.publishTimeline(save, isLocal)
|
||||||
|
return save
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun internalCreate(post: PostCreateDto, isLocal: Boolean): Post {
|
private suspend fun internalCreate(post: PostCreateDto, isLocal: Boolean): Post {
|
||||||
|
|
|
@ -4,6 +4,8 @@ import dev.usbharu.hideout.domain.model.hideout.entity.Reaction
|
||||||
import dev.usbharu.hideout.query.ReactionQueryService
|
import dev.usbharu.hideout.query.ReactionQueryService
|
||||||
import dev.usbharu.hideout.repository.ReactionRepository
|
import dev.usbharu.hideout.repository.ReactionRepository
|
||||||
import dev.usbharu.hideout.service.ap.APReactionService
|
import dev.usbharu.hideout.service.ap.APReactionService
|
||||||
|
import org.jetbrains.exposed.exceptions.ExposedSQLException
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
|
@ -14,9 +16,14 @@ class ReactionServiceImpl(
|
||||||
) : ReactionService {
|
) : ReactionService {
|
||||||
override suspend fun receiveReaction(name: String, domain: String, userId: Long, postId: Long) {
|
override suspend fun receiveReaction(name: String, domain: String, userId: Long, postId: Long) {
|
||||||
if (reactionQueryService.reactionAlreadyExist(postId, userId, 0).not()) {
|
if (reactionQueryService.reactionAlreadyExist(postId, userId, 0).not()) {
|
||||||
reactionRepository.save(
|
try {
|
||||||
Reaction(reactionRepository.generateId(), 0, postId, userId)
|
reactionRepository.save(
|
||||||
)
|
Reaction(reactionRepository.generateId(), 0, postId, userId)
|
||||||
|
)
|
||||||
|
} catch (e: ExposedSQLException) {
|
||||||
|
LOGGER.warn("FAILED Failure to persist reaction information.")
|
||||||
|
LOGGER.debug("FAILED", e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,4 +41,8 @@ class ReactionServiceImpl(
|
||||||
override suspend fun removeReaction(userId: Long, postId: Long) {
|
override suspend fun removeReaction(userId: Long, postId: Long) {
|
||||||
reactionQueryService.deleteByPostIdAndUserId(postId, userId)
|
reactionQueryService.deleteByPostIdAndUserId(postId, userId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val LOGGER = LoggerFactory.getLogger(ReactionServiceImpl::class.java)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import dev.usbharu.hideout.query.FollowerQueryService
|
||||||
import dev.usbharu.hideout.query.UserQueryService
|
import dev.usbharu.hideout.query.UserQueryService
|
||||||
import dev.usbharu.hideout.repository.UserRepository
|
import dev.usbharu.hideout.repository.UserRepository
|
||||||
import dev.usbharu.hideout.service.ap.APSendFollowService
|
import dev.usbharu.hideout.service.ap.APSendFollowService
|
||||||
|
import org.jetbrains.exposed.exceptions.ExposedSQLException
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
|
@ -64,7 +65,11 @@ class UserServiceImpl(
|
||||||
publicKey = user.publicKey,
|
publicKey = user.publicKey,
|
||||||
createdAt = Instant.now()
|
createdAt = Instant.now()
|
||||||
)
|
)
|
||||||
return userRepository.save(userEntity)
|
return try {
|
||||||
|
userRepository.save(userEntity)
|
||||||
|
} catch (_: ExposedSQLException) {
|
||||||
|
userQueryService.findByUrl(user.url)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO APのフォロー処理を作る
|
// TODO APのフォロー処理を作る
|
||||||
|
|
|
@ -20,7 +20,6 @@ object HttpUtil {
|
||||||
subType: String,
|
subType: String,
|
||||||
parameter: String
|
parameter: String
|
||||||
): Boolean {
|
): Boolean {
|
||||||
println("$contentType/$subType $parameter")
|
|
||||||
if (contentType != "application") {
|
if (contentType != "application") {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,7 +49,7 @@ class ExposedLockRepository(
|
||||||
val lock = Lock(id, now)
|
val lock = Lock(id, now)
|
||||||
query {
|
query {
|
||||||
if (locks.select(locks.id eq id).limit(1)
|
if (locks.select(locks.id eq id).limit(1)
|
||||||
.map { Lock(it[locks.id].value, Instant.ofEpochMilli(it[locks.expiresAt])) }.isEmpty()
|
.map { Lock(it[locks.id].value, Instant.ofEpochMilli(it[locks.expiresAt])) }.isEmpty()
|
||||||
) {
|
) {
|
||||||
locks.insert {
|
locks.insert {
|
||||||
it[locks.id] = id
|
it[locks.id] = id
|
||||||
|
|
|
@ -16,6 +16,7 @@ spring:
|
||||||
jackson:
|
jackson:
|
||||||
serialization:
|
serialization:
|
||||||
WRITE_DATES_AS_TIMESTAMPS: false
|
WRITE_DATES_AS_TIMESTAMPS: false
|
||||||
|
default-property-inclusion: always
|
||||||
datasource:
|
datasource:
|
||||||
driver-class-name: org.h2.Driver
|
driver-class-name: org.h2.Driver
|
||||||
url: "jdbc:h2:./test-dev2;MODE=POSTGRESQL"
|
url: "jdbc:h2:./test-dev2;MODE=POSTGRESQL"
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
<configuration>
|
<configuration>
|
||||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
<encoder>
|
<encoder>
|
||||||
<pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
<pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%X{x-request-id}] %logger{36} - %msg%n</pattern>
|
||||||
</encoder>
|
</encoder>
|
||||||
</appender>
|
</appender>
|
||||||
<root level="DEBUG">
|
<root level="INFO">
|
||||||
<appender-ref ref="STDOUT"/>
|
<appender-ref ref="STDOUT"/>
|
||||||
</root>
|
</root>
|
||||||
<logger name="org.eclipse.jetty" level="INFO"/>
|
<logger name="org.eclipse.jetty" level="INFO"/>
|
||||||
|
@ -12,6 +12,5 @@
|
||||||
<logger name="kjob.core.internal.scheduler.JobServiceImpl" level="INFO"/>
|
<logger name="kjob.core.internal.scheduler.JobServiceImpl" level="INFO"/>
|
||||||
<logger name="Exposed" level="INFO"/>
|
<logger name="Exposed" level="INFO"/>
|
||||||
<logger name="io.ktor.server.plugins.contentnegotiation" level="INFO"/>
|
<logger name="io.ktor.server.plugins.contentnegotiation" level="INFO"/>
|
||||||
<logger name="org.springframework.security" level="DEBUG"/>
|
<logger name="org.springframework.web.filter.CommonsRequestLoggingFilter" level="INFO"/>
|
||||||
<logger name="org.springframework.web.filter.CommonsRequestLoggingFilter" level="DEBUG"/>
|
|
||||||
</configuration>
|
</configuration>
|
||||||
|
|
Loading…
Reference in New Issue