mirror of https://github.com/usbharu/Hideout.git
Compare commits
No commits in common. "b1c2b554b0204c0f3f262dc6ce13e28929bb7286" and "a29cf62cfba0fb7811a68044c4f0c14b7597c4bb" have entirely different histories.
b1c2b554b0
...
a29cf62cfb
|
@ -3,10 +3,7 @@ package dev.usbharu.hideout.activitypub.service.common
|
||||||
import dev.usbharu.hideout.activitypub.domain.model.objects.Object
|
import dev.usbharu.hideout.activitypub.domain.model.objects.Object
|
||||||
import dev.usbharu.hideout.core.domain.model.user.User
|
import dev.usbharu.hideout.core.domain.model.user.User
|
||||||
import dev.usbharu.hideout.core.domain.model.user.UserRepository
|
import dev.usbharu.hideout.core.domain.model.user.UserRepository
|
||||||
import dev.usbharu.hideout.core.service.resource.CacheManager
|
|
||||||
import dev.usbharu.hideout.core.service.resource.ResolveResponse
|
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
import java.io.InputStream
|
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
class APResourceResolveServiceImpl(
|
class APResourceResolveServiceImpl(
|
||||||
|
@ -28,7 +25,7 @@ class APResourceResolveServiceImpl(
|
||||||
cacheManager.putCache(key) {
|
cacheManager.putCache(key) {
|
||||||
runResolve(url, singerId?.let { userRepository.findById(it) }, clazz)
|
runResolve(url, singerId?.let { userRepository.findById(it) }, clazz)
|
||||||
}
|
}
|
||||||
return (cacheManager.getOrWait(key) as APResolveResponse<T>).objects
|
return cacheManager.getOrWait(key) as T
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun <T : Object> internalResolve(url: String, singer: User?, clazz: Class<T>): T {
|
private suspend fun <T : Object> internalResolve(url: String, singer: User?, clazz: Class<T>): T {
|
||||||
|
@ -36,12 +33,11 @@ class APResourceResolveServiceImpl(
|
||||||
cacheManager.putCache(key) {
|
cacheManager.putCache(key) {
|
||||||
runResolve(url, singer, clazz)
|
runResolve(url, singer, clazz)
|
||||||
}
|
}
|
||||||
return (cacheManager.getOrWait(key) as APResolveResponse<T>).objects
|
return cacheManager.getOrWait(key) as T
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun <T : Object> runResolve(url: String, singer: User?, clazz: Class<T>): ResolveResponse {
|
private suspend fun <T : Object> runResolve(url: String, singer: User?, clazz: Class<T>): Object =
|
||||||
return APResolveResponse(apRequestService.apGet(url, singer, clazz))
|
apRequestService.apGet(url, singer, clazz)
|
||||||
}
|
|
||||||
|
|
||||||
private fun genCacheKey(url: String, singerId: Long?): String {
|
private fun genCacheKey(url: String, singerId: Long?): String {
|
||||||
if (singerId != null) {
|
if (singerId != null) {
|
||||||
|
@ -49,30 +45,4 @@ class APResourceResolveServiceImpl(
|
||||||
}
|
}
|
||||||
return url
|
return url
|
||||||
}
|
}
|
||||||
|
|
||||||
private class APResolveResponse<T : Object>(val objects: T) : ResolveResponse {
|
|
||||||
override suspend fun body(): InputStream {
|
|
||||||
TODO("Not yet implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun bodyAsText(): String {
|
|
||||||
TODO("Not yet implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun bodyAsBytes(): ByteArray {
|
|
||||||
TODO("Not yet implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun header(): Map<String, List<String>> {
|
|
||||||
TODO("Not yet implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun status(): Int {
|
|
||||||
TODO("Not yet implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun statusMessage(): String {
|
|
||||||
TODO("Not yet implemented")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
package dev.usbharu.hideout.activitypub.service.common
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.activitypub.domain.model.objects.Object
|
||||||
|
|
||||||
|
interface CacheManager {
|
||||||
|
|
||||||
|
suspend fun putCache(key: String, block: suspend () -> Object)
|
||||||
|
suspend fun getOrWait(key: String): Object
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
package dev.usbharu.hideout.core.service.resource
|
package dev.usbharu.hideout.activitypub.service.common
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.activitypub.domain.model.objects.Object
|
||||||
import dev.usbharu.hideout.util.LruCache
|
import dev.usbharu.hideout.util.LruCache
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
@ -10,10 +11,10 @@ import java.time.Instant
|
||||||
@Service
|
@Service
|
||||||
class InMemoryCacheManager : CacheManager {
|
class InMemoryCacheManager : CacheManager {
|
||||||
private val cacheKey = LruCache<String, Long>(15)
|
private val cacheKey = LruCache<String, Long>(15)
|
||||||
private val valueStore = mutableMapOf<String, ResolveResponse>()
|
private val valueStore = mutableMapOf<String, Object>()
|
||||||
private val keyMutex = Mutex()
|
private val keyMutex = Mutex()
|
||||||
|
|
||||||
override suspend fun putCache(key: String, block: suspend () -> ResolveResponse) {
|
override suspend fun putCache(key: String, block: suspend () -> Object) {
|
||||||
val needRunBlock: Boolean
|
val needRunBlock: Boolean
|
||||||
keyMutex.withLock {
|
keyMutex.withLock {
|
||||||
cacheKey.filter { Instant.ofEpochMilli(it.value).plusSeconds(300) <= Instant.now() }
|
cacheKey.filter { Instant.ofEpochMilli(it.value).plusSeconds(300) <= Instant.now() }
|
||||||
|
@ -37,7 +38,7 @@ class InMemoryCacheManager : CacheManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getOrWait(key: String): ResolveResponse {
|
override suspend fun getOrWait(key: String): Object {
|
||||||
while (valueStore.contains(key).not()) {
|
while (valueStore.contains(key).not()) {
|
||||||
if (cacheKey.containsKey(key).not()) {
|
if (cacheKey.containsKey(key).not()) {
|
||||||
throw IllegalStateException("Invalid cache key.")
|
throw IllegalStateException("Invalid cache key.")
|
|
@ -124,8 +124,7 @@ class APUserServiceImpl(
|
||||||
?: throw IllegalActivityPubObjectException("publicKey is null"),
|
?: throw IllegalActivityPubObjectException("publicKey is null"),
|
||||||
keyId = person.publicKey?.id ?: throw IllegalActivityPubObjectException("publicKey keyId is null"),
|
keyId = person.publicKey?.id ?: throw IllegalActivityPubObjectException("publicKey keyId is null"),
|
||||||
following = person.following,
|
following = person.following,
|
||||||
followers = person.followers,
|
followers = person.followers
|
||||||
sharedInbox = person.endpoints["sharedInbox"]
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -182,7 +182,7 @@ class SecurityConfig {
|
||||||
).anonymous()
|
).anonymous()
|
||||||
it.requestMatchers(builder.pattern("/change-password")).authenticated()
|
it.requestMatchers(builder.pattern("/change-password")).authenticated()
|
||||||
it.requestMatchers(builder.pattern("/api/v1/accounts/verify_credentials"))
|
it.requestMatchers(builder.pattern("/api/v1/accounts/verify_credentials"))
|
||||||
.hasAnyAuthority("SCOPE_read:accounts")
|
.hasAnyAuthority("SCOPE_read", "SCOPE_read:accounts")
|
||||||
it.anyRequest().permitAll()
|
it.anyRequest().permitAll()
|
||||||
}
|
}
|
||||||
http.oauth2ResourceServer {
|
http.oauth2ResourceServer {
|
||||||
|
|
|
@ -4,12 +4,11 @@ import dev.usbharu.hideout.application.external.Transaction
|
||||||
import kotlinx.coroutines.slf4j.MDCContext
|
import kotlinx.coroutines.slf4j.MDCContext
|
||||||
import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction
|
import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
import java.sql.Connection
|
|
||||||
|
|
||||||
@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(MDCContext(), transactionIsolation = Connection.TRANSACTION_SERIALIZABLE) {
|
return newSuspendedTransaction(MDCContext()) {
|
||||||
block()
|
block()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ data class Instance(
|
||||||
val description: String,
|
val description: String,
|
||||||
val url: String,
|
val url: String,
|
||||||
val iconUrl: String,
|
val iconUrl: String,
|
||||||
val sharedInbox: String?,
|
val sharedInbox: String,
|
||||||
val software: String,
|
val software: String,
|
||||||
val version: String,
|
val version: String,
|
||||||
val isBlocked: Boolean,
|
val isBlocked: Boolean,
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package dev.usbharu.hideout.core.domain.model.instance
|
package dev.usbharu.hideout.core.domain.model.instance
|
||||||
|
|
||||||
interface InstanceRepository {
|
interface InstanceRepository {
|
||||||
suspend fun generateId(): Long
|
|
||||||
suspend fun save(instance: Instance): Instance
|
suspend fun save(instance: Instance): Instance
|
||||||
suspend fun findById(id: Long): Instance
|
suspend fun findById(id: Long): Instance
|
||||||
suspend fun delete(instance: Instance)
|
suspend fun delete(instance: Instance)
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
package dev.usbharu.hideout.core.domain.model.instance
|
|
||||||
|
|
||||||
class Nodeinfo {
|
|
||||||
|
|
||||||
var links: List<Links> = emptyList()
|
|
||||||
|
|
||||||
protected constructor()
|
|
||||||
}
|
|
||||||
|
|
||||||
class Links {
|
|
||||||
var rel: String? = null
|
|
||||||
var href: String? = null
|
|
||||||
|
|
||||||
protected constructor()
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
package dev.usbharu.hideout.core.domain.model.instance
|
|
||||||
|
|
||||||
class Nodeinfo2_0 {
|
|
||||||
var metadata: Metadata? = null
|
|
||||||
var software: Software? = null
|
|
||||||
|
|
||||||
protected constructor()
|
|
||||||
}
|
|
||||||
|
|
||||||
class Metadata {
|
|
||||||
var nodeName: String? = null
|
|
||||||
var nodeDescription: String? = null
|
|
||||||
|
|
||||||
protected constructor()
|
|
||||||
}
|
|
||||||
|
|
||||||
class Software {
|
|
||||||
var name: String? = null
|
|
||||||
var version: String? = null
|
|
||||||
|
|
||||||
protected constructor()
|
|
||||||
}
|
|
|
@ -21,15 +21,16 @@ data class User private constructor(
|
||||||
val createdAt: Instant,
|
val createdAt: Instant,
|
||||||
val keyId: String,
|
val keyId: String,
|
||||||
val followers: String? = null,
|
val followers: String? = null,
|
||||||
val following: String? = null,
|
val following: String? = null
|
||||||
val instance: Long? = null
|
|
||||||
) {
|
) {
|
||||||
override fun toString(): String =
|
override fun toString(): String =
|
||||||
"User(id=$id, name='$name', domain='$domain', screenName='$screenName', description='$description', password=$password, inbox='$inbox', outbox='$outbox', url='$url', publicKey='$publicKey', privateKey=$privateKey, createdAt=$createdAt, keyId='$keyId', followers=$followers, following=$following, instance=$instance)"
|
"User(id=$id, name='$name', domain='$domain', screenName='$screenName', description='$description'," +
|
||||||
|
" password=$password, inbox='$inbox', outbox='$outbox', url='$url', publicKey='$publicKey'," +
|
||||||
|
" privateKey=$privateKey, createdAt=$createdAt, keyId='$keyId', followers=$followers," +
|
||||||
|
" following=$following)"
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
class UserBuilder(private val characterLimit: CharacterLimit, private val applicationConfig: ApplicationConfig) {
|
class UserBuilder(private val characterLimit: CharacterLimit, private val applicationConfig: ApplicationConfig) {
|
||||||
|
|
||||||
private val logger = LoggerFactory.getLogger(UserBuilder::class.java)
|
private val logger = LoggerFactory.getLogger(UserBuilder::class.java)
|
||||||
|
|
||||||
@Suppress("LongParameterList", "FunctionMinLength", "LongMethod")
|
@Suppress("LongParameterList", "FunctionMinLength", "LongMethod")
|
||||||
|
@ -48,8 +49,7 @@ data class User private constructor(
|
||||||
createdAt: Instant,
|
createdAt: Instant,
|
||||||
keyId: String,
|
keyId: String,
|
||||||
following: String? = null,
|
following: String? = null,
|
||||||
followers: String? = null,
|
followers: String? = null
|
||||||
instance: Long? = null
|
|
||||||
): User {
|
): User {
|
||||||
// idは0未満ではいけない
|
// idは0未満ではいけない
|
||||||
require(id >= 0) { "id must be greater than or equal to 0." }
|
require(id >= 0) { "id must be greater than or equal to 0." }
|
||||||
|
@ -141,8 +141,7 @@ data class User private constructor(
|
||||||
createdAt = createdAt,
|
createdAt = createdAt,
|
||||||
keyId = keyId,
|
keyId = keyId,
|
||||||
followers = followers,
|
followers = followers,
|
||||||
following = following,
|
following = following
|
||||||
instance = instance
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,8 +25,7 @@ class UserResultRowMapper(private val userBuilder: User.UserBuilder) : ResultRow
|
||||||
createdAt = Instant.ofEpochMilli((resultRow[Users.createdAt])),
|
createdAt = Instant.ofEpochMilli((resultRow[Users.createdAt])),
|
||||||
keyId = resultRow[Users.keyId],
|
keyId = resultRow[Users.keyId],
|
||||||
followers = resultRow[Users.followers],
|
followers = resultRow[Users.followers],
|
||||||
following = resultRow[Users.following],
|
following = resultRow[Users.following]
|
||||||
instance = resultRow[Users.instance]
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,8 +38,7 @@ class FollowerQueryServiceImpl(private val userBuilder: User.UserBuilder) : Foll
|
||||||
followers[Users.createdAt],
|
followers[Users.createdAt],
|
||||||
followers[Users.keyId],
|
followers[Users.keyId],
|
||||||
followers[Users.following],
|
followers[Users.following],
|
||||||
followers[Users.followers],
|
followers[Users.followers]
|
||||||
followers[Users.instance]
|
|
||||||
)
|
)
|
||||||
.select { Users.id eq id }
|
.select { Users.id eq id }
|
||||||
.map {
|
.map {
|
||||||
|
@ -58,8 +57,7 @@ class FollowerQueryServiceImpl(private val userBuilder: User.UserBuilder) : Foll
|
||||||
createdAt = Instant.ofEpochMilli(it[followers[Users.createdAt]]),
|
createdAt = Instant.ofEpochMilli(it[followers[Users.createdAt]]),
|
||||||
keyId = it[followers[Users.keyId]],
|
keyId = it[followers[Users.keyId]],
|
||||||
followers = it[followers[Users.followers]],
|
followers = it[followers[Users.followers]],
|
||||||
following = it[followers[Users.following]],
|
following = it[followers[Users.following]]
|
||||||
instance = it[followers[Users.instance]]
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -91,8 +89,7 @@ class FollowerQueryServiceImpl(private val userBuilder: User.UserBuilder) : Foll
|
||||||
followers[Users.createdAt],
|
followers[Users.createdAt],
|
||||||
followers[Users.keyId],
|
followers[Users.keyId],
|
||||||
followers[Users.following],
|
followers[Users.following],
|
||||||
followers[Users.followers],
|
followers[Users.followers]
|
||||||
followers[Users.instance]
|
|
||||||
)
|
)
|
||||||
.select { Users.name eq name and (Users.domain eq domain) }
|
.select { Users.name eq name and (Users.domain eq domain) }
|
||||||
.map {
|
.map {
|
||||||
|
@ -111,8 +108,7 @@ class FollowerQueryServiceImpl(private val userBuilder: User.UserBuilder) : Foll
|
||||||
createdAt = Instant.ofEpochMilli(it[followers[Users.createdAt]]),
|
createdAt = Instant.ofEpochMilli(it[followers[Users.createdAt]]),
|
||||||
keyId = it[followers[Users.keyId]],
|
keyId = it[followers[Users.keyId]],
|
||||||
followers = it[followers[Users.followers]],
|
followers = it[followers[Users.followers]],
|
||||||
following = it[followers[Users.following]],
|
following = it[followers[Users.following]]
|
||||||
instance = it[followers[Users.instance]]
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -144,8 +140,7 @@ class FollowerQueryServiceImpl(private val userBuilder: User.UserBuilder) : Foll
|
||||||
followers[Users.createdAt],
|
followers[Users.createdAt],
|
||||||
followers[Users.keyId],
|
followers[Users.keyId],
|
||||||
followers[Users.following],
|
followers[Users.following],
|
||||||
followers[Users.followers],
|
followers[Users.followers]
|
||||||
followers[Users.instance]
|
|
||||||
)
|
)
|
||||||
.select { followers[Users.id] eq id }
|
.select { followers[Users.id] eq id }
|
||||||
.map {
|
.map {
|
||||||
|
@ -164,8 +159,7 @@ class FollowerQueryServiceImpl(private val userBuilder: User.UserBuilder) : Foll
|
||||||
createdAt = Instant.ofEpochMilli(it[followers[Users.createdAt]]),
|
createdAt = Instant.ofEpochMilli(it[followers[Users.createdAt]]),
|
||||||
keyId = it[followers[Users.keyId]],
|
keyId = it[followers[Users.keyId]],
|
||||||
followers = it[followers[Users.followers]],
|
followers = it[followers[Users.followers]],
|
||||||
following = it[followers[Users.following]],
|
following = it[followers[Users.following]]
|
||||||
instance = it[followers[Users.instance]]
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -197,8 +191,7 @@ class FollowerQueryServiceImpl(private val userBuilder: User.UserBuilder) : Foll
|
||||||
followers[Users.createdAt],
|
followers[Users.createdAt],
|
||||||
followers[Users.keyId],
|
followers[Users.keyId],
|
||||||
followers[Users.following],
|
followers[Users.following],
|
||||||
followers[Users.followers],
|
followers[Users.followers]
|
||||||
followers[Users.instance]
|
|
||||||
)
|
)
|
||||||
.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 {
|
||||||
|
@ -217,8 +210,7 @@ class FollowerQueryServiceImpl(private val userBuilder: User.UserBuilder) : Foll
|
||||||
createdAt = Instant.ofEpochMilli(it[followers[Users.createdAt]]),
|
createdAt = Instant.ofEpochMilli(it[followers[Users.createdAt]]),
|
||||||
keyId = it[followers[Users.keyId]],
|
keyId = it[followers[Users.keyId]],
|
||||||
followers = it[followers[Users.followers]],
|
followers = it[followers[Users.followers]],
|
||||||
following = it[followers[Users.following]],
|
following = it[followers[Users.following]]
|
||||||
instance = it[followers[Users.instance]]
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
package dev.usbharu.hideout.core.infrastructure.exposedquery
|
|
||||||
|
|
||||||
import dev.usbharu.hideout.core.domain.exception.FailedToGetResourcesException
|
|
||||||
import dev.usbharu.hideout.core.infrastructure.exposedrepository.Instance
|
|
||||||
import dev.usbharu.hideout.core.infrastructure.exposedrepository.toInstance
|
|
||||||
import dev.usbharu.hideout.core.query.InstanceQueryService
|
|
||||||
import dev.usbharu.hideout.util.singleOr
|
|
||||||
import org.jetbrains.exposed.sql.select
|
|
||||||
import org.springframework.stereotype.Repository
|
|
||||||
import dev.usbharu.hideout.core.domain.model.instance.Instance as InstanceEntity
|
|
||||||
|
|
||||||
@Repository
|
|
||||||
class InstanceQueryServiceImpl : InstanceQueryService {
|
|
||||||
override suspend fun findByUrl(url: String): InstanceEntity = Instance.select { Instance.url eq url }
|
|
||||||
.singleOr { FailedToGetResourcesException("url is doesn't exist") }.toInstance()
|
|
||||||
}
|
|
|
@ -1,119 +0,0 @@
|
||||||
package dev.usbharu.hideout.core.infrastructure.exposedrepository
|
|
||||||
|
|
||||||
import dev.usbharu.hideout.application.service.id.IdGenerateService
|
|
||||||
import dev.usbharu.hideout.core.domain.model.post.Visibility
|
|
||||||
import dev.usbharu.hideout.core.domain.model.timeline.Timeline
|
|
||||||
import dev.usbharu.hideout.core.domain.model.timeline.TimelineRepository
|
|
||||||
import org.jetbrains.exposed.sql.*
|
|
||||||
import org.springframework.beans.factory.annotation.Qualifier
|
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
|
|
||||||
import org.springframework.stereotype.Repository
|
|
||||||
|
|
||||||
@Repository
|
|
||||||
@Qualifier("jdbc")
|
|
||||||
@ConditionalOnProperty("hideout.use-mongodb", havingValue = "false", matchIfMissing = true)
|
|
||||||
class ExposedTimelineRepository(private val idGenerateService: IdGenerateService) : TimelineRepository {
|
|
||||||
override suspend fun generateId(): Long = idGenerateService.generateId()
|
|
||||||
|
|
||||||
override suspend fun save(timeline: Timeline): Timeline {
|
|
||||||
if (Timelines.select { Timelines.id eq timeline.id }.singleOrNull() == null) {
|
|
||||||
Timelines.insert {
|
|
||||||
it[id] = timeline.id
|
|
||||||
it[userId] = timeline.userId
|
|
||||||
it[timelineId] = timeline.timelineId
|
|
||||||
it[postId] = timeline.postId
|
|
||||||
it[postUserId] = timeline.postUserId
|
|
||||||
it[createdAt] = timeline.createdAt
|
|
||||||
it[replyId] = timeline.replyId
|
|
||||||
it[repostId] = timeline.repostId
|
|
||||||
it[visibility] = timeline.visibility.ordinal
|
|
||||||
it[sensitive] = timeline.sensitive
|
|
||||||
it[isLocal] = timeline.isLocal
|
|
||||||
it[isPureRepost] = timeline.isPureRepost
|
|
||||||
it[mediaIds] = timeline.mediaIds.joinToString(",")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Timelines.update({ Timelines.id eq timeline.id }) {
|
|
||||||
it[userId] = timeline.userId
|
|
||||||
it[timelineId] = timeline.timelineId
|
|
||||||
it[postId] = timeline.postId
|
|
||||||
it[postUserId] = timeline.postUserId
|
|
||||||
it[createdAt] = timeline.createdAt
|
|
||||||
it[replyId] = timeline.replyId
|
|
||||||
it[repostId] = timeline.repostId
|
|
||||||
it[visibility] = timeline.visibility.ordinal
|
|
||||||
it[sensitive] = timeline.sensitive
|
|
||||||
it[isLocal] = timeline.isLocal
|
|
||||||
it[isPureRepost] = timeline.isPureRepost
|
|
||||||
it[mediaIds] = timeline.mediaIds.joinToString(",")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return timeline
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun saveAll(timelines: List<Timeline>): List<Timeline> {
|
|
||||||
Timelines.batchInsert(timelines, true, false) {
|
|
||||||
this[Timelines.id] = it.id
|
|
||||||
this[Timelines.userId] = it.userId
|
|
||||||
this[Timelines.timelineId] = it.timelineId
|
|
||||||
this[Timelines.postId] = it.postId
|
|
||||||
this[Timelines.postUserId] = it.postUserId
|
|
||||||
this[Timelines.createdAt] = it.createdAt
|
|
||||||
this[Timelines.replyId] = it.replyId
|
|
||||||
this[Timelines.repostId] = it.repostId
|
|
||||||
this[Timelines.visibility] = it.visibility.ordinal
|
|
||||||
this[Timelines.sensitive] = it.sensitive
|
|
||||||
this[Timelines.isLocal] = it.isLocal
|
|
||||||
this[Timelines.isPureRepost] = it.isPureRepost
|
|
||||||
this[Timelines.mediaIds] = it.mediaIds.joinToString(",")
|
|
||||||
}
|
|
||||||
return timelines
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun findByUserId(id: Long): List<Timeline> =
|
|
||||||
Timelines.select { Timelines.userId eq id }.map { it.toTimeline() }
|
|
||||||
|
|
||||||
override suspend fun findByUserIdAndTimelineId(userId: Long, timelineId: Long): List<Timeline> =
|
|
||||||
Timelines.select { Timelines.userId eq userId and (Timelines.timelineId eq timelineId) }
|
|
||||||
.map { it.toTimeline() }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun ResultRow.toTimeline(): Timeline {
|
|
||||||
return Timeline(
|
|
||||||
id = this[Timelines.id],
|
|
||||||
userId = this[Timelines.userId],
|
|
||||||
timelineId = this[Timelines.timelineId],
|
|
||||||
postId = this[Timelines.postId],
|
|
||||||
postUserId = this[Timelines.postUserId],
|
|
||||||
createdAt = this[Timelines.createdAt],
|
|
||||||
replyId = this[Timelines.replyId],
|
|
||||||
repostId = this[Timelines.repostId],
|
|
||||||
visibility = Visibility.values().first { it.ordinal == this[Timelines.visibility] },
|
|
||||||
sensitive = this[Timelines.sensitive],
|
|
||||||
isLocal = this[Timelines.isLocal],
|
|
||||||
isPureRepost = this[Timelines.isPureRepost],
|
|
||||||
mediaIds = this[Timelines.mediaIds].split(",").mapNotNull { it.toLongOrNull() }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
object Timelines : Table("timelines") {
|
|
||||||
val id = long("id")
|
|
||||||
val userId = long("user_id")
|
|
||||||
val timelineId = long("timeline_id")
|
|
||||||
val postId = long("post_id")
|
|
||||||
val postUserId = long("post_user_id")
|
|
||||||
val createdAt = long("created_at")
|
|
||||||
val replyId = long("reply_id").nullable()
|
|
||||||
val repostId = long("repost_id").nullable()
|
|
||||||
val visibility = integer("visibility")
|
|
||||||
val sensitive = bool("sensitive")
|
|
||||||
val isLocal = bool("is_local")
|
|
||||||
val isPureRepost = bool("is_pure_repost")
|
|
||||||
val mediaIds = varchar("media_ids", 255)
|
|
||||||
|
|
||||||
override val primaryKey: PrimaryKey = PrimaryKey(id)
|
|
||||||
|
|
||||||
init {
|
|
||||||
uniqueIndex(userId, timelineId, postId)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +1,5 @@
|
||||||
package dev.usbharu.hideout.core.infrastructure.exposedrepository
|
package dev.usbharu.hideout.core.infrastructure.exposedrepository
|
||||||
|
|
||||||
import dev.usbharu.hideout.application.service.id.IdGenerateService
|
|
||||||
import dev.usbharu.hideout.core.domain.exception.FailedToGetResourcesException
|
import dev.usbharu.hideout.core.domain.exception.FailedToGetResourcesException
|
||||||
import dev.usbharu.hideout.core.domain.model.instance.InstanceRepository
|
import dev.usbharu.hideout.core.domain.model.instance.InstanceRepository
|
||||||
import dev.usbharu.hideout.util.singleOr
|
import dev.usbharu.hideout.util.singleOr
|
||||||
|
@ -11,9 +10,7 @@ import org.springframework.stereotype.Repository
|
||||||
import dev.usbharu.hideout.core.domain.model.instance.Instance as InstanceEntity
|
import dev.usbharu.hideout.core.domain.model.instance.Instance as InstanceEntity
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
class InstanceRepositoryImpl(private val idGenerateService: IdGenerateService) : InstanceRepository {
|
class InstanceRepositoryImpl : InstanceRepository {
|
||||||
override suspend fun generateId(): Long = idGenerateService.generateId()
|
|
||||||
|
|
||||||
override suspend fun save(instance: InstanceEntity): InstanceEntity {
|
override suspend fun save(instance: InstanceEntity): InstanceEntity {
|
||||||
if (Instance.select { Instance.id.eq(instance.id) }.firstOrNull() == null) {
|
if (Instance.select { Instance.id.eq(instance.id) }.firstOrNull() == null) {
|
||||||
Instance.insert {
|
Instance.insert {
|
||||||
|
@ -81,7 +78,7 @@ object Instance : Table("instance") {
|
||||||
val description = varchar("description", 5000)
|
val description = varchar("description", 5000)
|
||||||
val url = varchar("url", 255)
|
val url = varchar("url", 255)
|
||||||
val iconUrl = varchar("icon_url", 255)
|
val iconUrl = varchar("icon_url", 255)
|
||||||
val sharedInbox = varchar("shared_inbox", 255).nullable()
|
val sharedInbox = varchar("shared_inbox", 255)
|
||||||
val software = varchar("software", 255)
|
val software = varchar("software", 255)
|
||||||
val version = varchar("version", 255)
|
val version = varchar("version", 255)
|
||||||
val isBlocked = bool("is_blocked")
|
val isBlocked = bool("is_blocked")
|
||||||
|
|
|
@ -59,6 +59,11 @@ class PostRepositoryImpl(
|
||||||
it[apId] = post.apId
|
it[apId] = post.apId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert(Posts.select { Posts.id eq post.id }.singleOrNull() != null) {
|
||||||
|
"Faild to insert"
|
||||||
|
}
|
||||||
|
|
||||||
return singleOrNull == null
|
return singleOrNull == null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,6 @@ class UserRepositoryImpl(
|
||||||
it[keyId] = user.keyId
|
it[keyId] = user.keyId
|
||||||
it[following] = user.following
|
it[following] = user.following
|
||||||
it[followers] = user.followers
|
it[followers] = user.followers
|
||||||
it[instance] = user.instance
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Users.update({ Users.id eq user.id }) {
|
Users.update({ Users.id eq user.id }) {
|
||||||
|
@ -53,7 +52,6 @@ class UserRepositoryImpl(
|
||||||
it[keyId] = user.keyId
|
it[keyId] = user.keyId
|
||||||
it[following] = user.following
|
it[following] = user.following
|
||||||
it[followers] = user.followers
|
it[followers] = user.followers
|
||||||
it[instance] = user.instance
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return user
|
return user
|
||||||
|
@ -100,7 +98,6 @@ object Users : Table("users") {
|
||||||
val keyId = varchar("key_id", length = 1000)
|
val keyId = varchar("key_id", length = 1000)
|
||||||
val following = varchar("following", length = 1000).nullable()
|
val following = varchar("following", length = 1000).nullable()
|
||||||
val followers = varchar("followers", length = 1000).nullable()
|
val followers = varchar("followers", length = 1000).nullable()
|
||||||
val instance = long("instance").references(Instance.id).nullable()
|
|
||||||
|
|
||||||
override val primaryKey: PrimaryKey = PrimaryKey(id)
|
override val primaryKey: PrimaryKey = PrimaryKey(id)
|
||||||
|
|
||||||
|
|
|
@ -5,23 +5,21 @@ import kjob.core.Job
|
||||||
import kjob.core.KJob
|
import kjob.core.KJob
|
||||||
import kjob.core.dsl.ScheduleContext
|
import kjob.core.dsl.ScheduleContext
|
||||||
import kjob.core.kjob
|
import kjob.core.kjob
|
||||||
import org.jetbrains.exposed.sql.transactions.TransactionManager
|
import org.jetbrains.exposed.sql.Database
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@ConditionalOnProperty(name = ["hideout.use-mongodb"], havingValue = "false", matchIfMissing = true)
|
@ConditionalOnProperty(name = ["hideout.use-mongodb"], havingValue = "false", matchIfMissing = true)
|
||||||
class KJobJobQueueParentService() : JobQueueParentService {
|
class KJobJobQueueParentService(private val database: Database) : JobQueueParentService {
|
||||||
|
|
||||||
private val logger = LoggerFactory.getLogger(this::class.java)
|
private val logger = LoggerFactory.getLogger(this::class.java)
|
||||||
|
|
||||||
val kjob: KJob by lazy {
|
val kjob: KJob = kjob(ExposedKJob) {
|
||||||
kjob(ExposedKJob) {
|
connectionDatabase = database
|
||||||
connectionDatabase = TransactionManager.defaultDatabase
|
isWorker = false
|
||||||
isWorker = false
|
}.start()
|
||||||
}.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun init(jobDefines: List<Job>) = Unit
|
override fun init(jobDefines: List<Job>) = Unit
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ import dev.usbharu.hideout.core.service.job.JobQueueWorkerService
|
||||||
import kjob.core.dsl.JobRegisterContext
|
import kjob.core.dsl.JobRegisterContext
|
||||||
import kjob.core.dsl.KJobFunctions
|
import kjob.core.dsl.KJobFunctions
|
||||||
import kjob.core.kjob
|
import kjob.core.kjob
|
||||||
import org.jetbrains.exposed.sql.transactions.TransactionManager
|
import org.jetbrains.exposed.sql.Database
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
import dev.usbharu.hideout.core.external.job.HideoutJob as HJ
|
import dev.usbharu.hideout.core.external.job.HideoutJob as HJ
|
||||||
|
@ -12,11 +12,11 @@ import kjob.core.dsl.JobContextWithProps as JCWP
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@ConditionalOnProperty(name = ["hideout.use-mongodb"], havingValue = "false", matchIfMissing = true)
|
@ConditionalOnProperty(name = ["hideout.use-mongodb"], havingValue = "false", matchIfMissing = true)
|
||||||
class KJobJobQueueWorkerService() : JobQueueWorkerService {
|
class KJobJobQueueWorkerService(private val database: Database) : JobQueueWorkerService {
|
||||||
|
|
||||||
val kjob by lazy {
|
val kjob by lazy {
|
||||||
kjob(ExposedKJob) {
|
kjob(ExposedKJob) {
|
||||||
connectionDatabase = TransactionManager.defaultDatabase
|
connectionDatabase = database
|
||||||
nonBlockingMaxJobs = 10
|
nonBlockingMaxJobs = 10
|
||||||
blockingMaxJobs = 10
|
blockingMaxJobs = 10
|
||||||
jobExecutionPeriodInSeconds = 1
|
jobExecutionPeriodInSeconds = 1
|
||||||
|
|
|
@ -10,7 +10,7 @@ import org.springframework.stereotype.Repository
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
@Suppress("InjectDispatcher")
|
@Suppress("InjectDispatcher")
|
||||||
@ConditionalOnProperty("hideout.use-mongodb", havingValue = "true", matchIfMissing = false)
|
@ConditionalOnProperty("hideout.use-mongodb", havingValue = "", matchIfMissing = false)
|
||||||
class MongoTimelineRepositoryWrapper(
|
class MongoTimelineRepositoryWrapper(
|
||||||
private val mongoTimelineRepository: MongoTimelineRepository,
|
private val mongoTimelineRepository: MongoTimelineRepository,
|
||||||
private val idGenerateService: IdGenerateService
|
private val idGenerateService: IdGenerateService
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
package dev.usbharu.hideout.core.query
|
|
||||||
|
|
||||||
import dev.usbharu.hideout.core.domain.model.instance.Instance
|
|
||||||
|
|
||||||
interface InstanceQueryService {
|
|
||||||
suspend fun findByUrl(url: String): Instance
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
package dev.usbharu.hideout.core.service.instance
|
|
||||||
|
|
||||||
data class InstanceCreateDto(
|
|
||||||
val name: String?,
|
|
||||||
val description: String?,
|
|
||||||
val url: String,
|
|
||||||
val iconUrl: String,
|
|
||||||
val sharedInbox: String?,
|
|
||||||
val software: String?,
|
|
||||||
val version: String?,
|
|
||||||
)
|
|
|
@ -1,114 +0,0 @@
|
||||||
package dev.usbharu.hideout.core.service.instance
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
|
||||||
import dev.usbharu.hideout.core.domain.exception.FailedToGetResourcesException
|
|
||||||
import dev.usbharu.hideout.core.domain.model.instance.Instance
|
|
||||||
import dev.usbharu.hideout.core.domain.model.instance.InstanceRepository
|
|
||||||
import dev.usbharu.hideout.core.domain.model.instance.Nodeinfo
|
|
||||||
import dev.usbharu.hideout.core.domain.model.instance.Nodeinfo2_0
|
|
||||||
import dev.usbharu.hideout.core.query.InstanceQueryService
|
|
||||||
import dev.usbharu.hideout.core.service.resource.ResourceResolveService
|
|
||||||
import org.slf4j.LoggerFactory
|
|
||||||
import org.springframework.beans.factory.annotation.Qualifier
|
|
||||||
import org.springframework.stereotype.Service
|
|
||||||
import java.net.URL
|
|
||||||
import java.time.Instant
|
|
||||||
|
|
||||||
interface InstanceService {
|
|
||||||
suspend fun fetchInstance(url: String, sharedInbox: String? = null): Instance
|
|
||||||
suspend fun createNewInstance(instanceCreateDto: InstanceCreateDto): Instance
|
|
||||||
}
|
|
||||||
|
|
||||||
@Service
|
|
||||||
class InstanceServiceImpl(
|
|
||||||
private val instanceRepository: InstanceRepository,
|
|
||||||
private val resourceResolveService: ResourceResolveService,
|
|
||||||
@Qualifier("activitypub") private val objectMapper: ObjectMapper,
|
|
||||||
private val instanceQueryService: InstanceQueryService
|
|
||||||
) : InstanceService {
|
|
||||||
override suspend fun fetchInstance(url: String, sharedInbox: String?): Instance {
|
|
||||||
val u = URL(url)
|
|
||||||
val resolveInstanceUrl = u.protocol + "://" + u.host
|
|
||||||
|
|
||||||
try {
|
|
||||||
return instanceQueryService.findByUrl(url)
|
|
||||||
} catch (e: FailedToGetResourcesException) {
|
|
||||||
logger.info("Instance not found. try fetch instance info. url: {}", resolveInstanceUrl)
|
|
||||||
logger.debug("Failed to get resources. url: {}", resolveInstanceUrl, e)
|
|
||||||
}
|
|
||||||
|
|
||||||
val nodeinfoJson = resourceResolveService.resolve("$resolveInstanceUrl/.well-known/nodeinfo").bodyAsText()
|
|
||||||
val nodeinfo = objectMapper.readValue(nodeinfoJson, Nodeinfo::class.java)
|
|
||||||
val nodeinfoPathMap = nodeinfo.links.associate { it.rel to it.href }
|
|
||||||
|
|
||||||
for ((key, value) in nodeinfoPathMap) {
|
|
||||||
when (key) {
|
|
||||||
"http://nodeinfo.diaspora.software/ns/schema/2.0" -> {
|
|
||||||
val nodeinfo20 = objectMapper.readValue(
|
|
||||||
resourceResolveService.resolve(value!!).bodyAsText(),
|
|
||||||
Nodeinfo2_0::class.java
|
|
||||||
)
|
|
||||||
|
|
||||||
val instanceCreateDto = InstanceCreateDto(
|
|
||||||
nodeinfo20.metadata?.nodeName,
|
|
||||||
nodeinfo20.metadata?.nodeDescription,
|
|
||||||
resolveInstanceUrl,
|
|
||||||
resolveInstanceUrl + "/favicon.ico",
|
|
||||||
sharedInbox,
|
|
||||||
nodeinfo20.software?.name,
|
|
||||||
nodeinfo20.software?.version
|
|
||||||
)
|
|
||||||
return createNewInstance(instanceCreateDto)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: 多分2.0と2.1で互換性有るのでそのまま使うけどなおす
|
|
||||||
"http://nodeinfo.diaspora.software/ns/schema/2.1" -> {
|
|
||||||
val nodeinfo20 = objectMapper.readValue(
|
|
||||||
resourceResolveService.resolve(value!!).bodyAsText(),
|
|
||||||
Nodeinfo2_0::class.java
|
|
||||||
)
|
|
||||||
|
|
||||||
val instanceCreateDto = InstanceCreateDto(
|
|
||||||
nodeinfo20.metadata?.nodeName,
|
|
||||||
nodeinfo20.metadata?.nodeDescription,
|
|
||||||
resolveInstanceUrl,
|
|
||||||
resolveInstanceUrl + "/favicon.ico",
|
|
||||||
sharedInbox,
|
|
||||||
nodeinfo20.software?.name,
|
|
||||||
nodeinfo20.software?.version
|
|
||||||
)
|
|
||||||
return createNewInstance(instanceCreateDto)
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> {
|
|
||||||
TODO()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw IllegalStateException("Nodeinfo aren't found.")
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun createNewInstance(instanceCreateDto: InstanceCreateDto): Instance {
|
|
||||||
val instance = Instance(
|
|
||||||
instanceRepository.generateId(),
|
|
||||||
instanceCreateDto.name ?: instanceCreateDto.url,
|
|
||||||
instanceCreateDto.description ?: "",
|
|
||||||
instanceCreateDto.url,
|
|
||||||
instanceCreateDto.iconUrl,
|
|
||||||
instanceCreateDto.sharedInbox,
|
|
||||||
instanceCreateDto.software ?: "unknown",
|
|
||||||
instanceCreateDto.version ?: "unknown",
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
"",
|
|
||||||
Instant.now()
|
|
||||||
)
|
|
||||||
instanceRepository.save(instance)
|
|
||||||
return instance
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private val logger = LoggerFactory.getLogger(InstanceServiceImpl::class.java)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -43,13 +43,11 @@ class PostServiceImpl(
|
||||||
if (postRepository.save(post)) {
|
if (postRepository.save(post)) {
|
||||||
try {
|
try {
|
||||||
timelineService.publishTimeline(post, isLocal)
|
timelineService.publishTimeline(post, isLocal)
|
||||||
} catch (e: DuplicateKeyException) {
|
} catch (_: DuplicateKeyException) {
|
||||||
logger.trace("Timeline already exists.", e)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
post
|
post
|
||||||
} catch (e: ExposedSQLException) {
|
} catch (_: ExposedSQLException) {
|
||||||
logger.warn("FAILED Save to post. url: ${post.apId}", e)
|
|
||||||
postQueryService.findByApId(post.apId)
|
postQueryService.findByApId(post.apId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
package dev.usbharu.hideout.core.service.resource
|
|
||||||
|
|
||||||
interface CacheManager {
|
|
||||||
suspend fun putCache(key: String, block: suspend () -> ResolveResponse)
|
|
||||||
suspend fun getOrWait(key: String): ResolveResponse
|
|
||||||
}
|
|
|
@ -1,31 +0,0 @@
|
||||||
package dev.usbharu.hideout.core.service.resource
|
|
||||||
|
|
||||||
import io.ktor.client.statement.*
|
|
||||||
import io.ktor.util.*
|
|
||||||
import io.ktor.utils.io.jvm.javaio.*
|
|
||||||
import java.io.InputStream
|
|
||||||
|
|
||||||
class KtorResolveResponse(val ktorHttpResponse: HttpResponse) : ResolveResponse {
|
|
||||||
|
|
||||||
private lateinit var _bodyAsText: String
|
|
||||||
private lateinit var _bodyAsBytes: ByteArray
|
|
||||||
|
|
||||||
override suspend fun body(): InputStream = ktorHttpResponse.bodyAsChannel().toInputStream()
|
|
||||||
override suspend fun bodyAsText(): String {
|
|
||||||
if (!this::_bodyAsText.isInitialized) {
|
|
||||||
_bodyAsText = ktorHttpResponse.bodyAsText()
|
|
||||||
}
|
|
||||||
return _bodyAsText
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun bodyAsBytes(): ByteArray {
|
|
||||||
if (!this::_bodyAsBytes.isInitialized) {
|
|
||||||
_bodyAsBytes = ktorHttpResponse.readBytes()
|
|
||||||
}
|
|
||||||
return _bodyAsBytes
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun header(): Map<String, List<String>> = ktorHttpResponse.headers.toMap()
|
|
||||||
override suspend fun status(): Int = ktorHttpResponse.status.value
|
|
||||||
override suspend fun statusMessage(): String = ktorHttpResponse.status.description
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
package dev.usbharu.hideout.core.service.resource
|
|
||||||
|
|
||||||
import io.ktor.client.*
|
|
||||||
import io.ktor.client.request.*
|
|
||||||
import org.springframework.stereotype.Service
|
|
||||||
|
|
||||||
@Service
|
|
||||||
open class KtorResourceResolveService(private val httpClient: HttpClient, private val cacheManager: CacheManager) :
|
|
||||||
ResourceResolveService {
|
|
||||||
override suspend fun resolve(url: String): ResolveResponse {
|
|
||||||
cacheManager.putCache(getCacheKey(url)) {
|
|
||||||
runResolve(url)
|
|
||||||
}
|
|
||||||
return cacheManager.getOrWait(getCacheKey(url))
|
|
||||||
}
|
|
||||||
|
|
||||||
protected suspend fun runResolve(url: String): ResolveResponse {
|
|
||||||
val httpResponse = httpClient.get(url)
|
|
||||||
|
|
||||||
return KtorResolveResponse(httpResponse)
|
|
||||||
}
|
|
||||||
|
|
||||||
protected suspend fun getCacheKey(url: String) = url
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
package dev.usbharu.hideout.core.service.resource
|
|
||||||
|
|
||||||
import java.io.InputStream
|
|
||||||
|
|
||||||
interface ResolveResponse {
|
|
||||||
suspend fun body(): InputStream
|
|
||||||
suspend fun bodyAsText(): String
|
|
||||||
suspend fun bodyAsBytes(): ByteArray
|
|
||||||
suspend fun header(): Map<String, List<String>>
|
|
||||||
suspend fun status(): Int
|
|
||||||
suspend fun statusMessage(): String
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
package dev.usbharu.hideout.core.service.resource
|
|
||||||
|
|
||||||
interface ResourceResolveService {
|
|
||||||
suspend fun resolve(url: String): ResolveResponse
|
|
||||||
}
|
|
|
@ -1,54 +0,0 @@
|
||||||
package dev.usbharu.hideout.core.service.timeline
|
|
||||||
|
|
||||||
import dev.usbharu.hideout.core.infrastructure.exposedrepository.Timelines
|
|
||||||
import dev.usbharu.hideout.domain.mastodon.model.generated.Status
|
|
||||||
import dev.usbharu.hideout.mastodon.interfaces.api.status.StatusQuery
|
|
||||||
import dev.usbharu.hideout.mastodon.query.StatusQueryService
|
|
||||||
import org.jetbrains.exposed.sql.SortOrder
|
|
||||||
import org.jetbrains.exposed.sql.andWhere
|
|
||||||
import org.jetbrains.exposed.sql.selectAll
|
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
|
|
||||||
import org.springframework.stereotype.Service
|
|
||||||
|
|
||||||
@Service
|
|
||||||
@ConditionalOnProperty("hideout.use-mongodb", havingValue = "false", matchIfMissing = true)
|
|
||||||
class ExposedGenerateTimelineService(private val statusQueryService: StatusQueryService) : GenerateTimelineService {
|
|
||||||
override suspend fun getTimeline(
|
|
||||||
forUserId: Long?,
|
|
||||||
localOnly: Boolean,
|
|
||||||
mediaOnly: Boolean,
|
|
||||||
maxId: Long?,
|
|
||||||
minId: Long?,
|
|
||||||
sinceId: Long?,
|
|
||||||
limit: Int
|
|
||||||
): List<Status> {
|
|
||||||
val query = Timelines.selectAll()
|
|
||||||
|
|
||||||
if (forUserId != null) {
|
|
||||||
query.andWhere { Timelines.userId eq forUserId }
|
|
||||||
}
|
|
||||||
if (localOnly) {
|
|
||||||
query.andWhere { Timelines.isLocal eq true }
|
|
||||||
}
|
|
||||||
if (maxId != null) {
|
|
||||||
query.andWhere { Timelines.id lessEq maxId }
|
|
||||||
}
|
|
||||||
if (minId != null) {
|
|
||||||
query.andWhere { Timelines.id greaterEq minId }
|
|
||||||
}
|
|
||||||
val result = query
|
|
||||||
.limit(limit)
|
|
||||||
.orderBy(Timelines.createdAt, SortOrder.DESC)
|
|
||||||
|
|
||||||
val statusQueries = result.map {
|
|
||||||
StatusQuery(
|
|
||||||
it[Timelines.postId],
|
|
||||||
it[Timelines.replyId],
|
|
||||||
it[Timelines.repostId],
|
|
||||||
it[Timelines.mediaIds].split(",").mapNotNull { it.toLongOrNull() }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return statusQueryService.findByPostIdsWithMediaIds(statusQueries)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -6,7 +6,6 @@ import dev.usbharu.hideout.core.domain.model.timeline.Timeline
|
||||||
import dev.usbharu.hideout.core.domain.model.timeline.TimelineRepository
|
import dev.usbharu.hideout.core.domain.model.timeline.TimelineRepository
|
||||||
import dev.usbharu.hideout.core.query.FollowerQueryService
|
import dev.usbharu.hideout.core.query.FollowerQueryService
|
||||||
import dev.usbharu.hideout.core.query.UserQueryService
|
import dev.usbharu.hideout.core.query.UserQueryService
|
||||||
import org.slf4j.LoggerFactory
|
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
|
@ -59,10 +58,5 @@ class TimelineService(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
timelineRepository.saveAll(timelines)
|
timelineRepository.saveAll(timelines)
|
||||||
logger.debug("SUCCESS Timeline published. {}", timelines.size)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private val logger = LoggerFactory.getLogger(TimelineService::class.java)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,5 @@ data class RemoteUserCreateDto(
|
||||||
val publicKey: String,
|
val publicKey: String,
|
||||||
val keyId: String,
|
val keyId: String,
|
||||||
val followers: String?,
|
val followers: String?,
|
||||||
val following: String?,
|
val following: String?
|
||||||
val sharedInbox: String?
|
|
||||||
)
|
)
|
||||||
|
|
|
@ -8,9 +8,7 @@ import dev.usbharu.hideout.core.domain.model.user.UserRepository
|
||||||
import dev.usbharu.hideout.core.query.FollowerQueryService
|
import dev.usbharu.hideout.core.query.FollowerQueryService
|
||||||
import dev.usbharu.hideout.core.query.UserQueryService
|
import dev.usbharu.hideout.core.query.UserQueryService
|
||||||
import dev.usbharu.hideout.core.service.follow.SendFollowDto
|
import dev.usbharu.hideout.core.service.follow.SendFollowDto
|
||||||
import dev.usbharu.hideout.core.service.instance.InstanceService
|
|
||||||
import org.jetbrains.exposed.exceptions.ExposedSQLException
|
import org.jetbrains.exposed.exceptions.ExposedSQLException
|
||||||
import org.slf4j.LoggerFactory
|
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
|
@ -22,8 +20,7 @@ class UserServiceImpl(
|
||||||
private val userQueryService: UserQueryService,
|
private val userQueryService: UserQueryService,
|
||||||
private val followerQueryService: FollowerQueryService,
|
private val followerQueryService: FollowerQueryService,
|
||||||
private val userBuilder: User.UserBuilder,
|
private val userBuilder: User.UserBuilder,
|
||||||
private val applicationConfig: ApplicationConfig,
|
private val applicationConfig: ApplicationConfig
|
||||||
private val instanceService: InstanceService
|
|
||||||
) :
|
) :
|
||||||
UserService {
|
UserService {
|
||||||
|
|
||||||
|
@ -52,20 +49,12 @@ class UserServiceImpl(
|
||||||
createdAt = Instant.now(),
|
createdAt = Instant.now(),
|
||||||
following = "$userUrl/following",
|
following = "$userUrl/following",
|
||||||
followers = "$userUrl/followers",
|
followers = "$userUrl/followers",
|
||||||
keyId = "$userUrl#pubkey",
|
keyId = "$userUrl#pubkey"
|
||||||
instance = null
|
|
||||||
)
|
)
|
||||||
return userRepository.save(userEntity)
|
return userRepository.save(userEntity)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun createRemoteUser(user: RemoteUserCreateDto): User {
|
override suspend fun createRemoteUser(user: RemoteUserCreateDto): User {
|
||||||
val instance = try {
|
|
||||||
instanceService.fetchInstance(user.url, user.sharedInbox)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
logger.warn("FAILED to fetch instance. url: {}", user.url, e)
|
|
||||||
null
|
|
||||||
}
|
|
||||||
|
|
||||||
val nextId = userRepository.nextId()
|
val nextId = userRepository.nextId()
|
||||||
val userEntity = userBuilder.of(
|
val userEntity = userBuilder.of(
|
||||||
id = nextId,
|
id = nextId,
|
||||||
|
@ -80,8 +69,7 @@ class UserServiceImpl(
|
||||||
createdAt = Instant.now(),
|
createdAt = Instant.now(),
|
||||||
followers = user.followers,
|
followers = user.followers,
|
||||||
following = user.following,
|
following = user.following,
|
||||||
keyId = user.keyId,
|
keyId = user.keyId
|
||||||
instance = instance?.id
|
|
||||||
)
|
)
|
||||||
return try {
|
return try {
|
||||||
userRepository.save(userEntity)
|
userRepository.save(userEntity)
|
||||||
|
@ -118,8 +106,4 @@ class UserServiceImpl(
|
||||||
followerQueryService.removeFollower(id, followerId)
|
followerQueryService.removeFollower(id, followerId)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
|
||||||
private val logger = LoggerFactory.getLogger(UserServiceImpl::class.java)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,6 @@ class AppApiServiceImpl(
|
||||||
private val passwordEncoder: PasswordEncoder,
|
private val passwordEncoder: PasswordEncoder,
|
||||||
private val transaction: Transaction
|
private val transaction: Transaction
|
||||||
) : AppApiService {
|
) : AppApiService {
|
||||||
|
|
||||||
override suspend fun createApp(appsRequest: AppsRequest): Application {
|
override suspend fun createApp(appsRequest: AppsRequest): Application {
|
||||||
return transaction.transaction {
|
return transaction.transaction {
|
||||||
val id = UUID.randomUUID().toString()
|
val id = UUID.randomUUID().toString()
|
||||||
|
@ -66,84 +65,5 @@ class AppApiServiceImpl(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseScope(string: String): Set<String> {
|
private fun parseScope(string: String): Set<String> = string.split(" ").toSet()
|
||||||
return string.split(" ")
|
|
||||||
.flatMap {
|
|
||||||
when (it) {
|
|
||||||
"read" -> READ_SCOPES
|
|
||||||
"write" -> WRITE_SCOPES
|
|
||||||
"follow" -> FOLLOW_SCOPES
|
|
||||||
"admin" -> ADMIN_SCOPES
|
|
||||||
"admin:write" -> ADMIN_WRITE_SCOPES
|
|
||||||
"admin:read" -> ADMIN_READ_SCOPES
|
|
||||||
else -> listOfNotNull(it.takeIf { ALL_SCOPES.contains(it) })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.toSet()
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private val READ_SCOPES = listOf(
|
|
||||||
"read:accounts",
|
|
||||||
"read:blocks",
|
|
||||||
"read:bookmarks",
|
|
||||||
"read:favourites",
|
|
||||||
"read:filters",
|
|
||||||
"read:follows",
|
|
||||||
"read:lists",
|
|
||||||
"read:mutes",
|
|
||||||
"read:notifications",
|
|
||||||
"read:search",
|
|
||||||
"read:statuses"
|
|
||||||
)
|
|
||||||
|
|
||||||
private val WRITE_SCOPES = listOf(
|
|
||||||
"write:accounts",
|
|
||||||
"write:blocks",
|
|
||||||
"write:bookmarks",
|
|
||||||
"write:conversations",
|
|
||||||
"write:favourites",
|
|
||||||
"write:filters",
|
|
||||||
"write:follows",
|
|
||||||
"write:lists",
|
|
||||||
"write:media",
|
|
||||||
"write:mutes",
|
|
||||||
"write:notifications",
|
|
||||||
"write:reports",
|
|
||||||
"write:statuses"
|
|
||||||
)
|
|
||||||
|
|
||||||
private val FOLLOW_SCOPES = listOf(
|
|
||||||
"read:blocks",
|
|
||||||
"write:blocks",
|
|
||||||
"read:follows",
|
|
||||||
"write:follows",
|
|
||||||
"read:mutes",
|
|
||||||
"write:mutes"
|
|
||||||
)
|
|
||||||
|
|
||||||
private val ADMIN_READ_SCOPES = listOf(
|
|
||||||
"admin:read:accounts",
|
|
||||||
"admin:read:reports",
|
|
||||||
"admin:read:domain_allows",
|
|
||||||
"admin:read:domain_blocks",
|
|
||||||
"admin:read:ip_blocks",
|
|
||||||
"admin:read:email_domain_blocks",
|
|
||||||
"admin:read:canonical_email_blocks"
|
|
||||||
)
|
|
||||||
|
|
||||||
private val ADMIN_WRITE_SCOPES = listOf(
|
|
||||||
"admin:write:accounts",
|
|
||||||
"admin:write:reports",
|
|
||||||
"admin:write:domain_allows",
|
|
||||||
"admin:write:domain_blocks",
|
|
||||||
"admin:write:ip_blocks",
|
|
||||||
"admin:write:email_domain_blocks",
|
|
||||||
"admin:write:canonical_email_blocks"
|
|
||||||
)
|
|
||||||
|
|
||||||
private val ADMIN_SCOPES = ADMIN_READ_SCOPES + ADMIN_WRITE_SCOPES
|
|
||||||
|
|
||||||
private val ALL_SCOPES = READ_SCOPES + WRITE_SCOPES + FOLLOW_SCOPES + ADMIN_SCOPES
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
hideout:
|
hideout:
|
||||||
url: "https://test-hideout.usbharu.dev"
|
url: "https://test-hideout.usbharu.dev"
|
||||||
use-mongodb: false
|
use-mongodb: true
|
||||||
security:
|
security:
|
||||||
jwt:
|
jwt:
|
||||||
generate: true
|
generate: true
|
||||||
|
@ -19,15 +19,15 @@ spring:
|
||||||
default-property-inclusion: always
|
default-property-inclusion: always
|
||||||
datasource:
|
datasource:
|
||||||
driver-class-name: org.h2.Driver
|
driver-class-name: org.h2.Driver
|
||||||
url: "jdbc:h2:./test-dev3;MODE=POSTGRESQL;TRACE_LEVEL_FILE=4"
|
url: "jdbc:h2:./test-dev3;MODE=POSTGRESQL"
|
||||||
username: ""
|
username: ""
|
||||||
password: ""
|
password: ""
|
||||||
# data:
|
data:
|
||||||
# mongodb:
|
mongodb:
|
||||||
# auto-index-creation: true
|
auto-index-creation: true
|
||||||
# host: localhost
|
host: localhost
|
||||||
# port: 27017
|
port: 27017
|
||||||
# database: hideout
|
database: hideout
|
||||||
# username: hideoutuser
|
# username: hideoutuser
|
||||||
# password: hideoutpass
|
# password: hideoutpass
|
||||||
servlet:
|
servlet:
|
||||||
|
|
Loading…
Reference in New Issue