mirror of https://github.com/usbharu/Hideout.git
commit
fdcf58fca9
|
@ -2,23 +2,114 @@ import kotlinx.kover.gradle.plugin.dsl.CoverageUnit
|
|||
|
||||
plugins {
|
||||
alias(libs.plugins.kotlin.jvm)
|
||||
alias(libs.plugins.detekt)
|
||||
alias(libs.plugins.kover)
|
||||
alias(libs.plugins.spring.boot)
|
||||
alias(libs.plugins.kotlin.spring)
|
||||
}
|
||||
|
||||
apply {
|
||||
plugin("io.spring.dependency-management")
|
||||
}
|
||||
|
||||
group = "dev.usbharu"
|
||||
version = "1.0-SNAPSHOT"
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven {
|
||||
url = uri("https://git.usbharu.dev/api/packages/usbharu/maven")
|
||||
}
|
||||
maven {
|
||||
name = "GitHubPackages"
|
||||
url = uri("https://maven.pkg.github.com/usbharu/activity-streams-serialization")
|
||||
credentials {
|
||||
|
||||
username = project.findProperty("gpr.user") as String? ?: System.getenv("USERNAME")
|
||||
password = project.findProperty("gpr.key") as String? ?: System.getenv("TOKEN")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
detektPlugins(libs.detekt.formatting)
|
||||
implementation(project(":hideout-core"))
|
||||
implementation("org.springframework.boot:spring-boot-starter-web")
|
||||
implementation("org.springframework.boot:spring-boot-starter-security")
|
||||
implementation(libs.bundles.jackson)
|
||||
implementation(libs.owl.producer.api)
|
||||
implementation(libs.owl.producer.embedded)
|
||||
implementation(libs.owl.common.serialize.jackson)
|
||||
implementation(libs.activity.streams.serialization)
|
||||
implementation(libs.jsonld)
|
||||
implementation(libs.coroutines.core)
|
||||
implementation(libs.bundles.exposed)
|
||||
testImplementation("org.springframework.boot:spring-boot-starter-test")
|
||||
testImplementation("org.springframework.security:spring-security-test")
|
||||
testImplementation(libs.bundles.spring.boot.oauth2)
|
||||
testImplementation(libs.kotlin.junit)
|
||||
testImplementation(libs.coroutines.test)
|
||||
testImplementation(libs.h2db)
|
||||
testImplementation(libs.flyway.core)
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
kotlin {
|
||||
jvmToolchain(21)
|
||||
}
|
||||
|
||||
|
||||
configurations {
|
||||
matching { it.name == "detekt" }.all {
|
||||
resolutionStrategy.eachDependency {
|
||||
if (requested.group == "org.jetbrains.kotlin") {
|
||||
useVersion(io.gitlab.arturbosch.detekt.getSupportedKotlinVersion())
|
||||
}
|
||||
}
|
||||
}
|
||||
all {
|
||||
exclude("org.apache.logging.log4j", "log4j-slf4j2-impl")
|
||||
}
|
||||
}
|
||||
|
||||
tasks {
|
||||
|
||||
withType<Test> {
|
||||
withType<io.gitlab.arturbosch.detekt.Detekt> {
|
||||
exclude("**/generated/**")
|
||||
setSource("src/main/kotlin")
|
||||
exclude("build/")
|
||||
configureEach {
|
||||
exclude("**/org/koin/ksp/generated/**", "**/generated/**")
|
||||
}
|
||||
}
|
||||
withType<io.gitlab.arturbosch.detekt.DetektCreateBaselineTask> {
|
||||
configureEach {
|
||||
exclude("**/org/koin/ksp/generated/**", "**/generated/**")
|
||||
}
|
||||
}
|
||||
withType<Test> {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
project.gradle.taskGraph.whenReady {
|
||||
if (this.hasTask(":koverGenerateArtifact")) {
|
||||
val task = this.allTasks.find { it.name == "test" }
|
||||
val verificationTask = task as VerificationTask
|
||||
verificationTask.ignoreFailures = true
|
||||
}
|
||||
}
|
||||
|
||||
detekt {
|
||||
parallel = true
|
||||
config.setFrom(files("../detekt.yml"))
|
||||
buildUponDefaultConfig = true
|
||||
basePath = "${rootDir.absolutePath}/src/main/kotlin"
|
||||
autoCorrect = true
|
||||
}
|
||||
|
||||
kover {
|
||||
currentProject {
|
||||
sources {
|
||||
|
@ -57,3 +148,7 @@ kover {
|
|||
|
||||
}
|
||||
}
|
||||
|
||||
springBoot{
|
||||
buildInfo { }
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
package dev.usbharu
|
||||
|
||||
fun main() {
|
||||
println("Hello World!")
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
package dev.usbharu.hideout.activitypub.application.actor
|
||||
|
||||
import dev.usbharu.activitystreamsserialization.other.JsonLd
|
||||
import dev.usbharu.hideout.activitypub.external.activitystreams.ActorTranslator
|
||||
import dev.usbharu.hideout.core.application.shared.AbstractApplicationService
|
||||
import dev.usbharu.hideout.core.application.shared.Transaction
|
||||
import dev.usbharu.hideout.core.config.ApplicationConfig
|
||||
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
|
||||
import dev.usbharu.hideout.core.domain.model.support.domain.apHost
|
||||
import dev.usbharu.hideout.core.domain.model.support.principal.Principal
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.stereotype.Service
|
||||
|
||||
@Service
|
||||
class GetActorApplicationService(
|
||||
private val actorRepository: ActorRepository,
|
||||
private val applicationConfig: ApplicationConfig,
|
||||
private val actorTranslator: ActorTranslator,
|
||||
transaction: Transaction,
|
||||
) : AbstractApplicationService<String, JsonLd>(
|
||||
transaction,
|
||||
logger,
|
||||
) {
|
||||
override suspend fun internalExecute(command: String, principal: Principal): JsonLd {
|
||||
val actor = actorRepository.findByNameAndDomain(command, applicationConfig.url.apHost)
|
||||
?: throw IllegalArgumentException("Actor $command not found")
|
||||
return actorTranslator.translate(actor, null, null)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val logger = LoggerFactory.getLogger(GetActorApplicationService::class.java)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
package dev.usbharu.hideout.activitypub.config
|
||||
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.core.annotation.Order
|
||||
import org.springframework.http.HttpMethod.GET
|
||||
import org.springframework.http.HttpMethod.POST
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||
import org.springframework.security.config.annotation.web.invoke
|
||||
import org.springframework.security.web.SecurityFilterChain
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher
|
||||
|
||||
@Configuration
|
||||
class ActivityPubSecurityConfig {
|
||||
@Bean
|
||||
@Order(4)
|
||||
fun activityPubSecurityFilterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
http {
|
||||
securityMatcher(
|
||||
RequestMatcher {
|
||||
val accept = it.getHeader("Accept") ?: ""
|
||||
return@RequestMatcher accept == "application/json" || accept == "application/activity+json"
|
||||
}
|
||||
)
|
||||
authorizeHttpRequests {
|
||||
authorize(POST, "/inbox", permitAll)
|
||||
authorize(POST, "/users/{username}/inbox", permitAll)
|
||||
authorize(GET, "/outbox", permitAll)
|
||||
authorize(GET, "/users/{username}/outbox", permitAll)
|
||||
authorize(GET, "/users/{username}", permitAll)
|
||||
}
|
||||
}
|
||||
return http.build()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package dev.usbharu.hideout.activitypub.config
|
||||
|
||||
import dev.usbharu.hideout.activitypub.external.activitystreams.ActivityStreamHttpMessageConverter
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.core.annotation.Order
|
||||
import org.springframework.http.converter.HttpMessageConverter
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer
|
||||
|
||||
@Configuration
|
||||
@Order(2)
|
||||
class ActivityPubWebMvcConfigurer(private val activityStreamsHttpMessageConverter: ActivityStreamHttpMessageConverter) :
|
||||
WebMvcConfigurer {
|
||||
override fun extendMessageConverters(converters: MutableList<HttpMessageConverter<*>>) {
|
||||
converters.add(activityStreamsHttpMessageConverter)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package dev.usbharu.hideout.activitypub.config
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import dev.usbharu.owl.common.property.*
|
||||
import dev.usbharu.owl.producer.api.OWL
|
||||
import dev.usbharu.owl.producer.api.OwlProducer
|
||||
import dev.usbharu.owl.producer.embedded.EMBEDDED
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
|
||||
@Configuration
|
||||
class OwlConfig {
|
||||
@Bean
|
||||
fun owlProducer(objectMapper: ObjectMapper): OwlProducer {
|
||||
return OWL(EMBEDDED) {
|
||||
this.propertySerializerFactory = CustomPropertySerializerFactory(
|
||||
setOf(
|
||||
IntegerPropertySerializer(),
|
||||
StringPropertyValueSerializer(),
|
||||
DoublePropertySerializer(),
|
||||
BooleanPropertySerializer(),
|
||||
LongPropertySerializer(),
|
||||
FloatPropertySerializer(),
|
||||
ObjectPropertySerializer(objectMapper),
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package dev.usbharu.hideout.activitypub.domain.shared.jobqueue
|
||||
|
||||
import dev.usbharu.hideout.activitypub.domain.task.Task
|
||||
|
||||
interface TaskPublisher {
|
||||
suspend fun publish(task: Task<*>)
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package dev.usbharu.hideout.activitypub.domain.task
|
||||
|
||||
import dev.usbharu.hideout.core.domain.model.support.domain.Domain
|
||||
import dev.usbharu.owl.common.task.Task
|
||||
import java.time.Instant
|
||||
|
||||
class Task<out T : TaskBody>(
|
||||
val id: String,
|
||||
val name: String,
|
||||
val publishedOn: Instant,
|
||||
val body: T,
|
||||
val domain: Domain
|
||||
) : Task()
|
|
@ -0,0 +1,7 @@
|
|||
package dev.usbharu.hideout.activitypub.domain.task
|
||||
|
||||
import dev.usbharu.hideout.core.domain.model.support.principal.Principal
|
||||
|
||||
abstract class TaskBody(private val map: Map<String, Any?>, val principal: Principal) {
|
||||
fun toMap(): Map<String, Any?> = map
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package dev.usbharu.hideout.activitypub.external.activitystreams
|
||||
|
||||
import com.github.jsonldjava.core.JsonLdOptions
|
||||
import com.github.jsonldjava.core.JsonLdProcessor
|
||||
import com.github.jsonldjava.utils.JsonUtils
|
||||
import dev.usbharu.activitystreamsserialization.json.impl.JacksonSerializationConverter
|
||||
import dev.usbharu.activitystreamsserialization.other.JsonLd
|
||||
import org.springframework.http.HttpInputMessage
|
||||
import org.springframework.http.HttpOutputMessage
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.http.converter.HttpMessageConverter
|
||||
import org.springframework.stereotype.Component
|
||||
|
||||
@Component
|
||||
class ActivityStreamHttpMessageConverter : HttpMessageConverter<JsonLd> {
|
||||
override fun canRead(clazz: Class<*>, mediaType: MediaType?): Boolean = false
|
||||
|
||||
override fun canWrite(clazz: Class<*>, mediaType: MediaType?): Boolean = JsonLd::class.java.isAssignableFrom(clazz)
|
||||
|
||||
override fun getSupportedMediaTypes(): MutableList<MediaType> = mutableListOf()
|
||||
|
||||
override fun write(t: JsonLd, contentType: MediaType?, outputMessage: HttpOutputMessage) {
|
||||
outputMessage.headers.contentType = MediaType.APPLICATION_JSON
|
||||
outputMessage.body.bufferedWriter()
|
||||
.use {
|
||||
it.write(
|
||||
JsonUtils.toString(
|
||||
JsonLdProcessor.compact(
|
||||
JsonUtils.fromString(JacksonSerializationConverter.convert(t.json).toString()),
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
JsonLdOptions()
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun read(clazz: Class<out JsonLd>, inputMessage: HttpInputMessage): JsonLd {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
package dev.usbharu.hideout.activitypub.external.activitystreams
|
||||
|
||||
import dev.usbharu.activitystreamsserialization.dsl.ActivityBuilder
|
||||
import dev.usbharu.activitystreamsserialization.other.JsonLd
|
||||
import dev.usbharu.hideout.core.domain.model.actor.Actor
|
||||
import dev.usbharu.hideout.core.domain.model.media.Media
|
||||
import org.springframework.stereotype.Service
|
||||
|
||||
@Service
|
||||
class ActorTranslator {
|
||||
fun translate(actor: Actor, iconMedia: Media?, bannerMedia: Media?): JsonLd {
|
||||
// todo actorにbot等の属性が生えてきたら対応する
|
||||
val person = ActivityBuilder().Person {
|
||||
name(actor.name.name)
|
||||
id(actor.url)
|
||||
preferredUsername(actor.name.name)
|
||||
inbox(actor.inbox)
|
||||
outbox(actor.outbox)
|
||||
followers(actor.followersEndpoint)
|
||||
following(actor.followingEndpoint)
|
||||
publicKey {
|
||||
listOf(
|
||||
Key {
|
||||
owner(actor.url)
|
||||
publicKeyPem(actor.publicKey.publicKey)
|
||||
id(actor.keyId.keyId)
|
||||
}
|
||||
)
|
||||
}
|
||||
iconMedia?.let {
|
||||
icon {
|
||||
listOf(
|
||||
Image {
|
||||
url(iconMedia.url)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
bannerMedia?.let {
|
||||
image {
|
||||
listOf(
|
||||
Image {
|
||||
url(bannerMedia.url)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
return person
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package dev.usbharu.hideout.activitypub.infrastructure.owl
|
||||
|
||||
import dev.usbharu.hideout.activitypub.domain.shared.jobqueue.TaskPublisher
|
||||
import dev.usbharu.hideout.activitypub.domain.task.Task
|
||||
import dev.usbharu.owl.producer.api.OwlProducer
|
||||
import org.springframework.stereotype.Service
|
||||
|
||||
@Service
|
||||
class OwlTaskPublisher(private val owlProducer: OwlProducer) : TaskPublisher {
|
||||
override suspend fun publish(task: Task<*>) {
|
||||
owlProducer.publishTask(task)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package dev.usbharu.hideout.activitypub.interfaces.api
|
||||
|
||||
import dev.usbharu.activitystreamsserialization.other.JsonLd
|
||||
import dev.usbharu.hideout.activitypub.application.actor.GetActorApplicationService
|
||||
import dev.usbharu.hideout.core.domain.model.support.principal.Anonymous
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.stereotype.Controller
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.PathVariable
|
||||
|
||||
@Controller
|
||||
class APActorController(private val getActorApplicationService: GetActorApplicationService) {
|
||||
@GetMapping(
|
||||
"/users/{username}",
|
||||
// consumes = ["application/activity+json"],
|
||||
produces = ["application/activity+json"]
|
||||
)
|
||||
suspend fun user(@PathVariable username: String): ResponseEntity<JsonLd> =
|
||||
ResponseEntity.ok(getActorApplicationService.execute(username, Anonymous))
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
package activity
|
||||
|
||||
import dev.usbharu.hideout.SpringApplication
|
||||
import dev.usbharu.hideout.activitypub.interfaces.api.APActorController
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
|
||||
import org.springframework.boot.test.context.SpringBootTest
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity
|
||||
import org.springframework.test.context.jdbc.Sql
|
||||
import org.springframework.test.web.servlet.MockMvc
|
||||
import org.springframework.test.web.servlet.get
|
||||
import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders
|
||||
import org.springframework.transaction.annotation.Transactional
|
||||
import org.springframework.web.context.WebApplicationContext
|
||||
|
||||
@SpringBootTest(classes = [SpringApplication::class, APActorController::class])
|
||||
@AutoConfigureMockMvc
|
||||
@Transactional
|
||||
@Sql("/sql/actors.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_CLASS)
|
||||
class APActorControllerTest {
|
||||
|
||||
@Autowired
|
||||
private lateinit var context: WebApplicationContext
|
||||
|
||||
private lateinit var mockMvc: MockMvc
|
||||
|
||||
@BeforeEach
|
||||
fun setUp() {
|
||||
mockMvc = MockMvcBuilders.webAppContextSetup(context)
|
||||
.apply<DefaultMockMvcBuilder>(springSecurity())
|
||||
.build()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun user() {
|
||||
mockMvc
|
||||
.get("/users/test") {
|
||||
accept(MediaType("application", "activity+json"))
|
||||
}
|
||||
.asyncDispatch()
|
||||
.andDo { print() }
|
||||
.andExpect { status { isOk() } }
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
package dev.usbharu.hideout.activitypub.external.activitystreams
|
||||
|
||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||
import com.github.jsonldjava.core.JsonLdOptions
|
||||
import com.github.jsonldjava.core.JsonLdProcessor
|
||||
import com.github.jsonldjava.utils.JsonUtils
|
||||
import dev.usbharu.activitystreamsserialization.json.impl.JacksonSerializationConverter
|
||||
import org.junit.jupiter.api.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class ActorTranslatorTest {
|
||||
@Test
|
||||
fun translate() {
|
||||
val actor = TestActorFactory.create()
|
||||
val translate = ActorTranslator().translate(actor, null, null)
|
||||
println(translate)
|
||||
val compact = JsonLdProcessor.compact(
|
||||
JsonUtils.fromString(JacksonSerializationConverter.convert(translate.json).toString()),
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
JsonLdOptions()
|
||||
)
|
||||
println(JsonUtils.toPrettyString(compact))
|
||||
|
||||
val readTree = jacksonObjectMapper().readTree(JsonUtils.toString(compact))
|
||||
|
||||
assertEquals(actor.url.toString(), readTree["id"].asText())
|
||||
|
||||
assertEquals("Person", readTree["type"].asText())
|
||||
|
||||
// inbox, outbox のテスト
|
||||
assertEquals(actor.inbox.toString(), readTree["inbox"].asText())
|
||||
assertEquals(actor.outbox.toString(), readTree["outbox"].asText())
|
||||
|
||||
// followers, following のテスト
|
||||
assertEquals(actor.followersEndpoint.toString(), readTree["followers"].asText())
|
||||
assertEquals(actor.followingEndpoint.toString(), readTree["following"].asText())
|
||||
|
||||
// preferredUsername のテスト
|
||||
assertEquals(actor.name.name, readTree["preferredUsername"].asText())
|
||||
|
||||
// name のテスト
|
||||
assertEquals(actor.screenName.screenName, readTree["name"].asText())
|
||||
|
||||
// publicKey のテスト
|
||||
val publicKeyNode = readTree["https://w3id.org/security#publicKey"]
|
||||
assertTrue(publicKeyNode.isObject) // publicKey がオブジェクトか確認
|
||||
assertEquals(actor.keyId.keyId, publicKeyNode["id"].asText())
|
||||
assertEquals(actor.url.toString(), publicKeyNode["https://w3id.org/security#owner"].asText())
|
||||
assertEquals(actor.publicKey.publicKey, publicKeyNode["https://w3id.org/security#publicKeyPem"].asText())
|
||||
|
||||
// @context のテスト
|
||||
assertEquals("https://www.w3.org/ns/activitystreams", readTree["@context"].asText())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
package dev.usbharu.hideout.activitypub.external.activitystreams
|
||||
|
||||
import dev.usbharu.hideout.core.domain.model.actor.*
|
||||
import dev.usbharu.hideout.core.domain.model.emoji.CustomEmojiId
|
||||
import dev.usbharu.hideout.core.domain.model.instance.InstanceId
|
||||
import dev.usbharu.hideout.core.domain.model.media.MediaId
|
||||
import dev.usbharu.hideout.core.domain.model.support.domain.Domain
|
||||
import dev.usbharu.hideout.core.infrastructure.other.TwitterSnowflakeIdGenerateService
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.net.URI
|
||||
import java.time.Instant
|
||||
|
||||
object TestActorFactory {
|
||||
private val idGenerateService = TwitterSnowflakeIdGenerateService
|
||||
|
||||
fun create(
|
||||
id: Long = generateId(),
|
||||
actorName: String = "test-$id",
|
||||
domain: String = "example.com",
|
||||
actorScreenName: String = actorName,
|
||||
description: String = "test description",
|
||||
inbox: URI = URI.create("https://example.com/$id/inbox"),
|
||||
outbox: URI = URI.create("https://example.com/$id/outbox"),
|
||||
uri: URI = URI.create("https://example.com/$id"),
|
||||
publicKey: ActorPublicKey = ActorPublicKey(""),
|
||||
privateKey: ActorPrivateKey? = null,
|
||||
createdAt: Instant = Instant.now(),
|
||||
keyId: String = "https://example.com/$id#key-id",
|
||||
followersEndpoint: URI = URI.create("https://example.com/$id/followers"),
|
||||
followingEndpoint: URI = URI.create("https://example.com/$id/following"),
|
||||
instanceId: Long = 1L,
|
||||
locked: Boolean = false,
|
||||
followersCount: Int = 0,
|
||||
followingCount: Int = 0,
|
||||
postCount: Int = 0,
|
||||
lastPostDate: Instant? = null,
|
||||
lastUpdateAt: Instant = createdAt,
|
||||
suspend: Boolean = false,
|
||||
alsoKnownAs: Set<ActorId> = emptySet(),
|
||||
moveTo: Long? = null,
|
||||
emojiIds: Set<CustomEmojiId> = emptySet(),
|
||||
deleted: Boolean = false,
|
||||
icon: Long? = null,
|
||||
banner: Long? = null,
|
||||
): Actor {
|
||||
return runBlocking {
|
||||
Actor(
|
||||
id = ActorId(id),
|
||||
name = ActorName(actorName),
|
||||
domain = Domain(domain),
|
||||
screenName = ActorScreenName(actorScreenName),
|
||||
description = ActorDescription(description),
|
||||
inbox = inbox,
|
||||
outbox = outbox,
|
||||
url = uri,
|
||||
publicKey = publicKey,
|
||||
privateKey = privateKey,
|
||||
createdAt = createdAt,
|
||||
keyId = ActorKeyId(keyId),
|
||||
followersEndpoint = followersEndpoint,
|
||||
followingEndpoint = followingEndpoint,
|
||||
instance = InstanceId(instanceId),
|
||||
locked = locked,
|
||||
followersCount = ActorRelationshipCount(followersCount),
|
||||
followingCount = ActorRelationshipCount(followingCount),
|
||||
postsCount = ActorPostsCount(postCount),
|
||||
lastPostAt = lastPostDate,
|
||||
lastUpdateAt = lastUpdateAt,
|
||||
suspend = suspend,
|
||||
alsoKnownAs = alsoKnownAs,
|
||||
moveTo = moveTo?.let { ActorId(it) },
|
||||
emojiIds = emojiIds,
|
||||
deleted = deleted,
|
||||
icon = icon?.let { MediaId(it) },
|
||||
banner = banner?.let { MediaId(it) },
|
||||
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateId(): Long = runBlocking {
|
||||
idGenerateService.generateId()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
spring:
|
||||
datasource:
|
||||
url: "jdbc:h2:mem:test;MODE=POSTGRESQL;DB_CLOSE_DELAY=-1;CASE_INSENSITIVE_IDENTIFIERS=true;TRACE_LEVEL_FILE=4;"
|
||||
driver-class-name: org.h2.Driver
|
||||
flyway:
|
||||
clean-disabled: false
|
||||
hideout:
|
||||
url: "https://example.com"
|
||||
security:
|
||||
jwt:
|
||||
generate: true
|
||||
key-id: a
|
||||
private-key: "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC7VJTUt9Us8cKjMzEfYyjiWA4R4/M2bS1GB4t7NXp98C3SC6dVMvDuictGeurT8jNbvJZHtCSuYEvuNMoSfm76oqFvAp8Gy0iz5sxjZmSnXyCdPEovGhLa0VzMaQ8s+CLOyS56YyCFGeJZqgtzJ6GR3eqoYSW9b9UMvkBpZODSctWSNGj3P7jRFDO5VoTwCQAWbFnOjDfH5Ulgp2PKSQnSJP3AJLQNFNe7br1XbrhV//eO+t51mIpGSDCUv3E0DDFcWDTH9cXDTTlRZVEiR2BwpZOOkE/Z0/BVnhZYL71oZV34bKfWjQIt6V/isSMahdsAASACp4ZTGtwiVuNd9tybAgMBAAECggEBAKTmjaS6tkK8BlPXClTQ2vpz/N6uxDeS35mXpqasqskVlaAidgg/sWqpjXDbXr93otIMLlWsM+X0CqMDgSXKejLS2jx4GDjI1ZTXg++0AMJ8sJ74pWzVDOfmCEQ/7wXs3+cbnXhKriO8Z036q92Qc1+N87SI38nkGa0ABH9CN83HmQqt4fB7UdHzuIRe/me2PGhIq5ZBzj6h3BpoPGzEP+x3l9YmK8t/1cN0pqI+dQwYdgfGjackLu/2qH80MCF7IyQaseZUOJyKrCLtSD/Iixv/hzDEUPfOCjFDgTpzf3cwta8+oE4wHCo1iI1/4TlPkwmXx4qSXtmw4aQPz7IDQvECgYEA8KNThCO2gsC2I9PQDM/8Cw0O983WCDY+oi+7JPiNAJwv5DYBqEZB1QYdj06YD16XlC/HAZMsMku1na2TN0driwenQQWzoev3g2S7gRDoS/FCJSI3jJ+kjgtaA7Qmzlgk1TxODN+G1H91HW7t0l7VnL27IWyYo2qRRK3jzxqUiPUCgYEAx0oQs2reBQGMVZnApD1jeq7n4MvNLcPvt8b/eU9iUv6Y4Mj0Suo/AU8lYZXm8ubbqAlwz2VSVunD2tOplHyMUrtCtObAfVDUAhCndKaA9gApgfb3xw1IKbuQ1u4IF1FJl3VtumfQn//LiH1B3rXhcdyo3/vIttEk48RakUKClU8CgYEAzV7W3COOlDDcQd935DdtKBFRAPRPAlspQUnzMi5eSHMD/ISLDY5IiQHbIH83D4bvXq0X7qQoSBSNP7Dvv3HYuqMhf0DaegrlBuJllFVVq9qPVRnKxt1Il2HgxOBvbhOT+9in1BzA+YJ99UzC85O0Qz06A+CmtHEy4aZ2kj5hHjECgYEAmNS4+A8Fkss8Js1RieK2LniBxMgmYml3pfVLKGnzmng7H2+cwPLhPIzIuwytXywh2bzbsYEfYx3EoEVgMEpPhoarQnYPukrJO4gwE2o5Te6T5mJSZGlQJQj9q4ZB2Dfzet6INsK0oG8XVGXSpQvQh3RUYekCZQkBBFcpqWpbIEsCgYAnM3DQf3FJoSnXaMhrVBIovic5l0xFkEHskAjFTevO86Fsz1C2aSeRKSqGFoOQ0tmJzBEs1R6KqnHInicDTQrKhArgLXX4v3CddjfTRJkFWDbE/CkvKZNOrcf1nhaGCPspRJj2KUkj1Fhl9Cncdn/RsYEONbwQSjIfMPkvxF+8HQ=="
|
||||
public-key: "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u+qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyehkd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdgcKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbcmwIDAQAB"
|
|
@ -0,0 +1,35 @@
|
|||
insert into instance(id, name, description, url, icon_url, shared_inbox, software, version, is_blocked, is_muted,
|
||||
moderation_note, created_at)
|
||||
VALUES (1, 'instance', 'description', 'https://example.com', 'https://example.com', 'https://example.com', 'software',
|
||||
'version', false, false, 'note', current_timestamp)
|
||||
, (2, 'instance', 'description', 'https://remote.example.com', 'https://example.com', 'https://remote.example.com',
|
||||
'software',
|
||||
'version', false, false, 'note', current_timestamp)
|
||||
, (3, 'instance', 'description', 'https://remote2.example.com', 'https://example.com',
|
||||
'https://remote2.example.com', 'software',
|
||||
'version', false, false, 'note', current_timestamp);
|
||||
|
||||
insert into actors(id, name, domain, screen_name, description, inbox, outbox, url, public_key, private_key, created_at,
|
||||
key_id, following, followers, instance, locked, following_count, followers_count, posts_count,
|
||||
last_post_at, last_update_at, suspend, move_to, icon, banner)
|
||||
VALUES (1, 'test', 'example.com', 'test-actor', 'actor_description', 'https://example.com/test/inbox',
|
||||
'https://example.com/outbox', 'https://example.com/test', '---BEGIN PUBLIC KEY---', '---BEGIN PRIVATE KEY---',
|
||||
current_timestamp, 'https://example.com/test#main-key', 'https://example.com/test/following',
|
||||
'https://example.com/test/followers', 1, false, 1, 0, 0, null, current_timestamp, false, null, null, null),
|
||||
(2, 'test', 'remote.example.com', 'test-actor', 'actor_description', 'https://remote.example.com/test/inbox',
|
||||
'https://remote.example.com/outbox', 'https://remote.example.com', '---BEGIN PUBLIC KEY---',
|
||||
'---BEGIN PRIVATE KEY---',
|
||||
current_timestamp, 'https://remote.example.com/test#main-key', 'https://remote.example.com/test/following',
|
||||
'https://remote.example.com/test/followers', 2, false, 1, 0, 0, null, current_timestamp, false, null, null,
|
||||
null),
|
||||
(3, 'test', 'remote2.example.com', 'test-actor', 'actor_description', 'https://remote2.example.com/test/inbox',
|
||||
'https://remote2.example.com/test/outbox', 'https://remote2.example.com/test', '---BEGIN PUBLIC KEY---',
|
||||
'---BEGIN PRIVATE KEY---',
|
||||
current_timestamp, 'https://remote2.example.com/test#main-key', 'https://remote2.example.com/test/following',
|
||||
'https://example.com/followers', 3, false, 1, 0, 0, null, current_timestamp, false, null, null, null),
|
||||
(4, 'test2', 'remote2.example.com', 'test-actor', 'actor_description', 'https://example.com/inbox',
|
||||
'https://remote2.example.com/test2/outbox', 'https://remote2.example.com/test2', '---BEGIN PUBLIC KEY---',
|
||||
'---BEGIN PRIVATE KEY---',
|
||||
current_timestamp, 'https://remote2.example.com/test2#main-key', 'https://remote2.example.com/test2/following',
|
||||
'https://remote2.example.com/test2/followers', 3, false, 1, 0, 0, null, current_timestamp, false, null, null,
|
||||
null);
|
|
@ -83,7 +83,7 @@ class SecurityConfig {
|
|||
}
|
||||
|
||||
@Bean
|
||||
@Order(3)
|
||||
@Order(6)
|
||||
fun httpSecurityFilterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
http {
|
||||
authorizeHttpRequests {
|
||||
|
|
|
@ -23,6 +23,7 @@ import dev.usbharu.hideout.generate.JsonOrFormModelMethodProcessor
|
|||
import org.springframework.boot.web.servlet.FilterRegistrationBean
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.core.annotation.Order
|
||||
import org.springframework.http.converter.HttpMessageConverter
|
||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry
|
||||
|
@ -31,10 +32,11 @@ import org.springframework.web.servlet.mvc.method.annotation.RequestResponseBody
|
|||
import org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor
|
||||
|
||||
@Configuration
|
||||
@Order(2)
|
||||
class MvcConfigurer(
|
||||
private val jsonOrFormModelMethodProcessor: JsonOrFormModelMethodProcessor,
|
||||
private val spaInterceptor: SPAInterceptor,
|
||||
private val applicationRequestLogInterceptor: ApplicationRequestLogInterceptor
|
||||
private val applicationRequestLogInterceptor: ApplicationRequestLogInterceptor,
|
||||
) : WebMvcConfigurer {
|
||||
override fun addArgumentResolvers(resolvers: MutableList<HandlerMethodArgumentResolver>) {
|
||||
resolvers.add(jsonOrFormModelMethodProcessor)
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
package dev.usbharu.hideout.core.domain.model.support.principal
|
||||
|
||||
import dev.usbharu.hideout.core.domain.model.actor.ActorId
|
||||
import dev.usbharu.hideout.core.domain.model.support.acct.Acct
|
||||
|
||||
class RemoteUser(actorId: ActorId, acct: Acct?) : Principal(actorId, null, acct)
|
|
@ -25,6 +25,7 @@ import dev.usbharu.hideout.core.domain.model.support.timelineobjectdetail.Timeli
|
|||
import dev.usbharu.hideout.core.domain.model.timeline.Timeline
|
||||
import dev.usbharu.hideout.core.domain.model.timelinerelationship.TimelineRelationship
|
||||
|
||||
@Suppress("TooManyFunctions")
|
||||
interface TimelineStore {
|
||||
suspend fun addPost(post: Post)
|
||||
suspend fun updatePost(post: Post)
|
||||
|
|
|
@ -34,7 +34,7 @@ import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.CustomEmoji a
|
|||
|
||||
@Suppress("IncompleteDestructuring")
|
||||
@Repository
|
||||
class StatusQueryServiceImpl : StatusQueryService {
|
||||
class ExposedStatusQueryServiceImpl : StatusQueryService {
|
||||
|
||||
protected fun authorizedQuery(principal: Principal? = null): QueryAlias {
|
||||
if (principal == null) {
|
||||
|
@ -60,8 +60,16 @@ class StatusQueryServiceImpl : StatusQueryService {
|
|||
.where {
|
||||
Posts.visibility eq Visibility.PUBLIC.name or
|
||||
(Posts.visibility eq Visibility.UNLISTED.name) or
|
||||
(Posts.visibility eq Visibility.DIRECT.name and (PostsVisibleActors.actorId eq principal.actorId.id)) or
|
||||
(Posts.visibility eq Visibility.FOLLOWERS.name and (Relationships.blocking eq false and (relationshipsAlias[Relationships.following] eq true))) or
|
||||
(
|
||||
Posts.visibility eq Visibility.DIRECT.name and
|
||||
(PostsVisibleActors.actorId eq principal.actorId.id)
|
||||
) or
|
||||
(
|
||||
Posts.visibility eq Visibility.FOLLOWERS.name and (
|
||||
Relationships.blocking eq false and
|
||||
(relationshipsAlias[Relationships.following] eq true)
|
||||
)
|
||||
) or
|
||||
(Posts.actorId eq principal.actorId.id)
|
||||
}
|
||||
.alias("authorized_table")
|
||||
|
|
|
@ -19,10 +19,10 @@ import org.springframework.transaction.annotation.Transactional
|
|||
@Sql("/sql/relationships.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
|
||||
@Transactional
|
||||
@SpringBootTest(classes = [SpringApplication::class])
|
||||
class StatusQueryServiceImplTest {
|
||||
class ExposedStatusQueryServiceImplTest {
|
||||
|
||||
@Autowired
|
||||
lateinit var statusQueryServiceImpl: StatusQueryServiceImpl
|
||||
lateinit var statusQueryServiceImpl: ExposedStatusQueryServiceImpl
|
||||
|
||||
@Test
|
||||
fun フォロワー限定をフォロワー以外は見れない() = runTest {
|
|
@ -104,9 +104,12 @@ mockito-kotlin = { module = "org.mockito.kotlin:mockito-kotlin", version = "5.4.
|
|||
|
||||
http-signature = { module = "dev.usbharu:http-signature", version = "1.0.0" }
|
||||
emoji-kt = { module = "dev.usbharu:emoji-kt", version = "2.0.1" }
|
||||
activity-streams-serialization = { module = "dev.usbharu:activity-streams-serialization", version = "0.5.0" }
|
||||
|
||||
logback-ecs-encoder = { module = "co.elastic.logging:logback-ecs-encoder", version = "1.6.0" }
|
||||
|
||||
jsonld = { module = "com.github.jsonld-java:jsonld-java", version = "0.13.5" }
|
||||
|
||||
[bundles]
|
||||
|
||||
exposed = ["exposed-core", "exposed-java-time", "exposed-jdbc", "exposed-spring"]
|
||||
|
|
Loading…
Reference in New Issue