This commit is contained in:
2024-12-19 12:32:35 +09:00
parent 32483abfe0
commit ef0eacf1bf
520 changed files with 611 additions and 1626 deletions
+18
View File
@@ -0,0 +1,18 @@
plugins {
alias(libs.plugins.kotlin.jvm)
}
group = "dev.usbharu.hideout"
version = "unspecified"
repositories {
mavenCentral()
}
dependencies {
testImplementation(kotlin("test"))
}
tasks.test {
useJUnitPlatform()
}
@@ -0,0 +1,115 @@
import kotlinx.kover.gradle.plugin.dsl.CoverageUnit
plugins {
alias(libs.plugins.kotlin.jvm)
alias(libs.plugins.detekt)
alias(libs.plugins.kover)
}
group = "dev.usbharu"
version = "1.0-SNAPSHOT"
repositories {
mavenCentral()
}
dependencies {
testImplementation(kotlin("test"))
detektPlugins(libs.detekt.formatting)
}
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<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 {
}
}
reports {
verify {
rule {
bound {
minValue = 50
coverageUnits = CoverageUnit.INSTRUCTION
}
}
}
total {
xml {
title = "Hideout ActivityPub"
xmlFile = file("$buildDir/reports/kover/hideout-activitypub.xml")
}
}
filters {
excludes {
annotatedBy("org.springframework.context.annotation.Configuration")
annotatedBy("org.springframework.boot.context.properties.ConfigurationProperties")
packages(
"dev.usbharu.hideout.controller.mastodon.generated",
"dev.usbharu.hideout.domain.mastodon.model.generated"
)
packages("org.springframework")
packages("org.jetbrains")
}
}
}
}
@@ -0,0 +1,5 @@
package dev.usbharu
fun main() {
println("Hello World!")
}
@@ -0,0 +1,25 @@
{
"allowedLicenses": [
{
"moduleLicense": "Apache License, Version 2.0"
},
{
"moduleLicense": "MIT License"
},
{
"moduleLicense": "MIT-0"
},
{
"moduleLicense": "The BSD License"
},
{
"moduleLicense": "PUBLIC DOMAIN"
},
{
"moduleLicense": "The 2-Clause BSD License"
},
{
"moduleLicense": "GNU GENERAL PUBLIC LICENSE, Version 2 + Classpath Exception"
}
]
}
+258
View File
@@ -0,0 +1,258 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import com.github.jk1.license.filter.DependencyFilter
import com.github.jk1.license.filter.LicenseBundleNormalizer
import com.github.jk1.license.importer.DependencyDataImporter
import com.github.jk1.license.importer.XmlReportImporter
import com.github.jk1.license.render.*
import kotlinx.kover.gradle.plugin.dsl.CoverageUnit
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
alias(libs.plugins.kotlin.jvm)
alias(libs.plugins.detekt)
alias(libs.plugins.spring.boot)
alias(libs.plugins.kotlin.spring)
alias(libs.plugins.kover)
alias(libs.plugins.license.report)
}
apply {
plugin("io.spring.dependency-management")
}
group = "dev.usbharu"
version = "0.0.1"
kotlin {
jvmToolchain(21)
compilerOptions {
freeCompilerArgs.add("-Xjsr305=strict")
jvmTarget = JvmTarget.JVM_21
}
}
repositories {
mavenCentral()
maven {
url = uri("https://git.usbharu.dev/api/packages/usbharu/maven")
}
maven {
name = "GitHubPackages"
url = uri("https://maven.pkg.github.com/usbharu/http-signature")
credentials {
username = project.findProperty("gpr.user") as String? ?: System.getenv("USERNAME")
password = project.findProperty("gpr.key") as String? ?: System.getenv("TOKEN")
}
}
maven {
name = "GitHubPackages2"
url = uri("https://maven.pkg.github.com/multim-dev/emoji-kt")
credentials {
username = project.findProperty("gpr.user") as String? ?: System.getenv("USERNAME")
password = project.findProperty("gpr.key") as String? ?: System.getenv("TOKEN")
}
}
}
val os = org.gradle.nativeplatform.platform.internal.DefaultNativePlatform.getCurrentOperatingSystem()
dependencies {
developmentOnly(libs.h2db)
developmentOnly("org.springframework.boot:spring-boot-devtools")
detektPlugins(libs.detekt.formatting)
implementation(libs.bundles.exposed)
implementation(libs.bundles.coroutines)
implementation(libs.bundles.ktor.client)
implementation(libs.bundles.apache.tika)
implementation(libs.bundles.openapi)
implementation(libs.bundles.owl.producer)
implementation(libs.bundles.owl.broker)
implementation(libs.bundles.spring.boot.oauth2)
implementation(libs.bundles.spring.boot.data.mongodb)
implementation("org.springframework.boot:spring-boot-starter-actuator")
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-security")
implementation("org.springframework.boot:spring-boot-starter-thymeleaf")
implementation("org.springframework.boot:spring-boot-starter-validation")
annotationProcessor("org.springframework:spring-context-indexer")
implementation(libs.blurhash)
implementation(libs.aws.s3)
implementation(libs.jsoup)
implementation(libs.owasp.java.html.sanitizer)
implementation(libs.postgresql)
implementation(libs.imageio.webp)
implementation(libs.thumbnailator)
implementation(libs.flyway.core)
runtimeOnly(libs.flyway.postgresql)
implementation(libs.owl.common.serialize.jackson)
implementation(libs.javacv) {
exclude(module = "opencv")
exclude(module = "flycapture")
exclude(module = "artoolkitplus")
exclude(module = "libdc1394")
exclude(module = "librealsense")
exclude(module = "librealsense2")
exclude(module = "tesseract")
exclude(module = "libfreenect")
exclude(module = "libfreenect2")
}
if (os.isWindows) {
implementation(variantOf(libs.javacv.ffmpeg) { classifier("windows-x86_64") })
} else {
implementation(variantOf(libs.javacv.ffmpeg) { classifier("linux-x86_64") })
}
implementation(libs.http.signature)
implementation(libs.emoji.kt)
implementation(libs.logback.ecs.encoder)
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation(libs.kotlin.junit)
testImplementation(libs.coroutines.test)
testImplementation(libs.ktor.client.mock)
testImplementation(libs.h2db)
testImplementation(libs.mockito.kotlin)
testImplementation("org.assertj:assertj-db:2.0.2")
testImplementation("com.ninja-squad:DbSetup-kotlin:2.1.0")
}
detekt {
parallel = true
config.setFrom(files("../detekt.yml"))
buildUponDefaultConfig = true
basePath = "${rootDir.absolutePath}/src/main/kotlin"
autoCorrect = true
}
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<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()
doFirst {
jvmArgs = arrayOf(
"--add-opens",
"java.base/java.lang=ALL-UNNAMED",
"--add-opens",
"java.base/java.util=ALL-UNNAMED",
"--add-opens",
"java.naming/javax.naming=ALL-UNNAMED",
"--add-opens",
"java.base/java.util.concurrent.locks=ALL-UNNAMED"
).toMutableList()
}
}
}
project.gradle.taskGraph.whenReady {
if (this.hasTask(":koverGenerateArtifact")) {
val task = this.allTasks.find { it.name == "test" }
val verificationTask = task as VerificationTask
verificationTask.ignoreFailures = true
}
}
kover {
currentProject {
sources {
}
}
reports {
verify {
rule {
bound {
minValue = 50
coverageUnits = CoverageUnit.INSTRUCTION
}
}
}
total {
xml {
title = "Hideout Core"
xmlFile = file("$buildDir/reports/kover/hideout-core.xml")
}
}
filters {
excludes {
annotatedBy("org.springframework.context.annotation.Configuration")
annotatedBy("org.springframework.boot.context.properties.ConfigurationProperties")
packages(
"dev.usbharu.hideout.controller.mastodon.generated",
"dev.usbharu.hideout.domain.mastodon.model.generated"
)
packages("org.springframework")
packages("org.jetbrains")
}
}
}
}
springBoot {
buildInfo {
}
}
licenseReport {
excludeOwnGroup = true
importers = arrayOf<DependencyDataImporter>(XmlReportImporter("hideout", File("$projectDir/license-list.xml")))
renderers = arrayOf<ReportRenderer>(
InventoryHtmlReportRenderer(), CsvReportRenderer(), JsonReportRenderer(), XmlReportRenderer()
)
filters = arrayOf<DependencyFilter>(LicenseBundleNormalizer("$projectDir/license-normalizer-bundle.json", true))
allowedLicensesFile = File("$projectDir/allowed-licenses.json")
configurations = arrayOf("productionRuntimeClasspath")
}
@@ -0,0 +1,56 @@
{
"bundles": [
{
"bundleName": "Apache-2.0",
"licenseName": "Apache License, Version 2.0",
"licenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
},
{
"bundleName": "EPL-1.0",
"licenseName": "Eclipse Public License - v 1.0",
"licenseUrl": "http://www.eclipse.org/legal/epl-v10.html"
}
],
"transformationRules": [
{
"bundleName": "Apache-2.0",
"licenseNamePattern": ".*The Apache Software License, Version 2.0.*"
},
{
"bundleName": "Apache-2.0",
"licenseNamePattern": "Apache 2"
},
{
"bundleName": "Apache-2.0",
"licenseUrlPattern": "http://www.apache.org/licenses/LICENSE-2.0.txt"
},
{
"bundleName": "Apache-2.0",
"licenseUrlPattern": "https://www.apache.org/licenses/LICENSE-2.0"
},
{
"bundleName": "Apache-2.0",
"licenseUrlPattern": "https://aws.amazon.com/apache2.0"
},
{
"bundleName": "Apache-2.0",
"licenseUrlPattern": "https://www.apache.org/licenses/LICENSE-2.0.txt"
},
{
"bundleName": "Apache-2.0",
"licenseNamePattern": "Special Apache"
},
{
"bundleName": "Apache-2.0",
"licenseNamePattern": "Keep this name"
},
{
"bundleName": "Apache-2.0",
"licenseNamePattern": "The Apache Software License, Version 2.0"
},
{
"bundleName": "EPL-1.0",
"licenseNamePattern": "EPL 1.0"
}
]
}
@@ -0,0 +1,32 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.context.properties.ConfigurationPropertiesScan
import org.springframework.boot.runApplication
import org.springframework.cache.annotation.EnableCaching
@SpringBootApplication
@ConfigurationPropertiesScan
@EnableCaching
class SpringApplication
@Suppress("SpreadOperator")
fun main(args: Array<String>) {
runApplication<SpringApplication>(*args)
}
@@ -0,0 +1,73 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.application.actor
import dev.usbharu.hideout.core.application.model.ActorDetail
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.ActorId
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
import dev.usbharu.hideout.core.domain.model.media.MediaRepository
import dev.usbharu.hideout.core.domain.model.support.acct.Acct
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.Component
@Component
class GetActorDetailApplicationService(
private val actorRepository: ActorRepository,
private val mediaRepository: MediaRepository,
private val applicationConfig: ApplicationConfig,
transaction: Transaction
) :
AbstractApplicationService<GetActorDetail, ActorDetail>(
transaction,
logger
) {
override suspend fun internalExecute(command: GetActorDetail, principal: Principal): ActorDetail {
val actor = if (command.id != null) {
actorRepository.findById(ActorId(command.id))
?: throw IllegalArgumentException("Actor ${command.id} not found.")
} else if (command.actorName != null) {
val host = if (command.actorName.host.isEmpty()) {
applicationConfig.url.apHost
} else {
command.actorName.host
}
actorRepository.findByNameAndDomain(command.actorName.userpart, host)
?: throw IllegalArgumentException("Actor ${command.actorName} not found.")
} else {
throw IllegalArgumentException("id and actorName are null.")
}
val iconUrl = actor.icon?.let { mediaRepository.findById(it) }
val bannerUrl = actor.banner?.let { mediaRepository.findById(it) }
return ActorDetail.of(actor, iconUrl, bannerUrl)
}
companion object {
private val logger = LoggerFactory.getLogger(GetActorDetailApplicationService::class.java)
}
}
data class GetActorDetail(
val actorName: Acct? = null,
val id: Long? = null
)
@@ -0,0 +1,55 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.application.actor
import dev.usbharu.hideout.core.application.exception.InternalServerException
import dev.usbharu.hideout.core.application.model.UserDetail
import dev.usbharu.hideout.core.application.shared.AbstractApplicationService
import dev.usbharu.hideout.core.application.shared.Transaction
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
import dev.usbharu.hideout.core.domain.model.emoji.CustomEmojiRepository
import dev.usbharu.hideout.core.domain.model.support.principal.Principal
import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailId
import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailRepository
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
@Service
class GetUserDetailApplicationService(
private val actorRepository: ActorRepository,
private val userDetailRepository: UserDetailRepository,
private val customEmojiRepository: CustomEmojiRepository,
transaction: Transaction,
) :
AbstractApplicationService<GetUserDetail, UserDetail>(transaction, Companion.logger) {
override suspend fun internalExecute(command: GetUserDetail, principal: Principal): UserDetail {
val userDetail = userDetailRepository.findById(UserDetailId(command.id))
?: throw IllegalArgumentException("User ${command.id} does not exist")
val actor = actorRepository.findById(userDetail.actorId)
?: throw InternalServerException("Actor ${userDetail.actorId} not found")
val emojis = customEmojiRepository.findByIds(actor.emojis.map { it.emojiId })
return UserDetail.of(actor, userDetail, emojis)
}
companion object {
val logger = LoggerFactory.getLogger(GetUserDetailApplicationService::class.java)
}
}
data class GetUserDetail(val id: Long)
@@ -0,0 +1,78 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.application.actor
import dev.usbharu.hideout.core.application.exception.InternalServerException
import dev.usbharu.hideout.core.application.exception.PermissionDeniedException
import dev.usbharu.hideout.core.application.shared.LocalUserAbstractApplicationService
import dev.usbharu.hideout.core.application.shared.Transaction
import dev.usbharu.hideout.core.domain.model.actor.ActorId
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
import dev.usbharu.hideout.core.domain.model.support.principal.LocalUser
import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailRepository
import dev.usbharu.hideout.core.domain.service.actor.local.AccountMigrationCheck.*
import dev.usbharu.hideout.core.domain.service.actor.local.LocalActorMigrationCheckDomainService
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
@Service
class MigrationLocalActorApplicationService(
private val actorRepository: ActorRepository,
private val localActorMigrationCheckDomainService: LocalActorMigrationCheckDomainService,
transaction: Transaction,
private val userDetailRepository: UserDetailRepository,
) : LocalUserAbstractApplicationService<MigrationLocalActor, Unit>(transaction, logger) {
override suspend fun internalExecute(command: MigrationLocalActor, principal: LocalUser) {
if (command.from != principal.actorId.id) {
throw PermissionDeniedException()
}
val userDetail = userDetailRepository.findById(principal.userDetailId)
?: throw InternalServerException("User detail ${principal.userDetailId} not found.")
val fromActorId = ActorId(command.from)
val toActorId = ActorId(command.to)
val fromActor =
actorRepository.findById(fromActorId) ?: throw IllegalArgumentException("Actor ${command.from} not found.")
val toActor =
actorRepository.findById(toActorId) ?: throw IllegalArgumentException("Actor ${command.to} not found.")
val canAccountMigration =
localActorMigrationCheckDomainService.canAccountMigration(userDetail, fromActor, toActor)
if (canAccountMigration.canMigration) {
fromActor.setMoveTo(toActorId)
actorRepository.save(fromActor)
} else {
when (canAccountMigration) {
is AlreadyMoved -> throw IllegalArgumentException(canAccountMigration.message)
is CanAccountMigration -> throw InternalServerException()
is CircularReferences -> throw IllegalArgumentException(canAccountMigration.message)
is SelfReferences -> throw IllegalArgumentException("Self references are not supported")
is AlsoKnownAsNotFound -> throw IllegalArgumentException(canAccountMigration.message)
is MigrationCoolDown -> throw IllegalArgumentException(canAccountMigration.message)
}
}
}
companion object {
private val logger = LoggerFactory.getLogger(MigrationLocalActorApplicationService::class.java)
}
}
data class MigrationLocalActor(val from: Long, val to: Long)
@@ -0,0 +1,83 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.application.actor
import dev.usbharu.hideout.core.application.exception.InternalServerException
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.instance.InstanceRepository
import dev.usbharu.hideout.core.domain.model.support.principal.Principal
import dev.usbharu.hideout.core.domain.model.userdetails.UserDetail
import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailId
import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailRepository
import dev.usbharu.hideout.core.domain.service.actor.local.LocalActorDomainService
import dev.usbharu.hideout.core.domain.service.userdetail.UserDetailDomainService
import dev.usbharu.hideout.core.domain.shared.id.IdGenerateService
import dev.usbharu.hideout.core.infrastructure.factory.ActorFactoryImpl
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
import java.net.URI
@Service
class RegisterLocalActorApplicationService(
transaction: Transaction,
private val actorDomainService: LocalActorDomainService,
private val actorRepository: ActorRepository,
private val actorFactoryImpl: ActorFactoryImpl,
private val instanceRepository: InstanceRepository,
private val applicationConfig: ApplicationConfig,
private val userDetailDomainService: UserDetailDomainService,
private val userDetailRepository: UserDetailRepository,
private val idGenerateService: IdGenerateService,
) : AbstractApplicationService<RegisterLocalActor, URI>(transaction, Companion.logger) {
override suspend fun internalExecute(command: RegisterLocalActor, principal: Principal): URI {
if (actorDomainService.usernameAlreadyUse(command.name)) {
throw IllegalArgumentException("Username already exists")
}
val instance = instanceRepository.findByUrl(applicationConfig.url)
?: throw InternalServerException("Local instance not found.")
val actor = actorFactoryImpl.createLocal(
command.name,
actorDomainService.generateKeyPair(),
instance.id
)
actorRepository.save(actor)
val userDetail = UserDetail.create(
id = UserDetailId(idGenerateService.generateId()),
actorId = actor.id,
password = userDetailDomainService.hashPassword(command.password),
autoAcceptFolloweeFollowRequest = false,
lastMigration = null,
homeTimelineId = null
)
userDetailRepository.save(userDetail)
return actor.url
}
companion object {
private val logger = LoggerFactory.getLogger(RegisterLocalActorApplicationService::class.java)
}
}
data class RegisterLocalActor(
val name: String,
val password: String,
)
@@ -0,0 +1,49 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.application.actor
import dev.usbharu.hideout.core.application.exception.InternalServerException
import dev.usbharu.hideout.core.application.exception.PermissionDeniedException
import dev.usbharu.hideout.core.application.shared.LocalUserAbstractApplicationService
import dev.usbharu.hideout.core.application.shared.Transaction
import dev.usbharu.hideout.core.domain.model.actor.ActorId
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
import dev.usbharu.hideout.core.domain.model.support.principal.LocalUser
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
@Service
class StartDeleteLocalActorApplicationService(
transaction: Transaction,
private val actorRepository: ActorRepository,
) : LocalUserAbstractApplicationService<DeleteLocalActor, Unit>(transaction, logger) {
override suspend fun internalExecute(command: DeleteLocalActor, principal: LocalUser) {
if (command.actorId != principal.actorId) {
throw PermissionDeniedException()
}
val findById = actorRepository.findById(command.actorId)
?: throw InternalServerException("Actor ${command.actorId} Not found")
findById.delete()
actorRepository.save(findById)
}
companion object {
private val logger = LoggerFactory.getLogger(StartDeleteLocalActorApplicationService::class.java)
}
}
data class DeleteLocalActor(val actorId: ActorId)
@@ -0,0 +1,46 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.application.actor
import dev.usbharu.hideout.core.application.shared.LocalUserAbstractApplicationService
import dev.usbharu.hideout.core.application.shared.Transaction
import dev.usbharu.hideout.core.domain.model.actor.ActorId
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
import dev.usbharu.hideout.core.domain.model.support.principal.LocalUser
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
@Service
class SuspendLocalActorApplicationService(
transaction: Transaction,
private val actorRepository: ActorRepository,
) : LocalUserAbstractApplicationService<SuspendLocalActor, Unit>(transaction, logger) {
override suspend fun internalExecute(command: SuspendLocalActor, principal: LocalUser) {
val id = ActorId(command.actorId)
val actor =
actorRepository.findById(id) ?: throw IllegalArgumentException("Actor ${command.actorId} not found.")
actor.suspend = true
actorRepository.save(actor)
}
companion object {
private val logger = LoggerFactory.getLogger(SuspendLocalActorApplicationService::class.java)
}
}
data class SuspendLocalActor(val actorId: Long)
@@ -0,0 +1,46 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.application.actor
import dev.usbharu.hideout.core.application.shared.LocalUserAbstractApplicationService
import dev.usbharu.hideout.core.application.shared.Transaction
import dev.usbharu.hideout.core.domain.model.actor.ActorId
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
import dev.usbharu.hideout.core.domain.model.support.principal.LocalUser
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
@Service
class UnsuspendLocalActorApplicationService(
transaction: Transaction,
private val actorRepository: ActorRepository,
) : LocalUserAbstractApplicationService<UnsuspendLocalActor, Unit>(transaction, logger) {
override suspend fun internalExecute(command: UnsuspendLocalActor, principal: LocalUser) {
val findById = actorRepository.findById(ActorId(command.actorId))
?: throw IllegalArgumentException("Actor ${command.actorId} not found.")
findById.suspend = false
actorRepository.save(findById)
}
companion object {
private val logger = LoggerFactory.getLogger(UnsuspendLocalActorApplicationService::class.java)
}
}
data class UnsuspendLocalActor(val actorId: Long)
@@ -0,0 +1,111 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.application.application
import dev.usbharu.hideout.core.application.shared.AbstractApplicationService
import dev.usbharu.hideout.core.application.shared.Transaction
import dev.usbharu.hideout.core.domain.model.application.Application
import dev.usbharu.hideout.core.domain.model.application.ApplicationId
import dev.usbharu.hideout.core.domain.model.application.ApplicationName
import dev.usbharu.hideout.core.domain.model.application.ApplicationRepository
import dev.usbharu.hideout.core.domain.model.support.principal.Principal
import dev.usbharu.hideout.core.domain.service.userdetail.PasswordEncoder
import dev.usbharu.hideout.core.domain.shared.id.IdGenerateService
import dev.usbharu.hideout.core.infrastructure.springframework.oauth2.SecureTokenGenerator
import org.slf4j.LoggerFactory
import org.springframework.security.oauth2.core.AuthorizationGrantType
import org.springframework.security.oauth2.core.ClientAuthenticationMethod
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings
import org.springframework.stereotype.Service
import java.net.URI
import java.time.Duration
@Service
class RegisterApplicationApplicationService(
private val idGenerateService: IdGenerateService,
private val passwordEncoder: PasswordEncoder,
private val secureTokenGenerator: SecureTokenGenerator,
private val registeredClientRepository: RegisteredClientRepository,
transaction: Transaction,
private val applicationRepository: ApplicationRepository,
) : AbstractApplicationService<RegisterApplication, dev.usbharu.hideout.core.application.model.Application>(
transaction,
logger
) {
override suspend fun internalExecute(
command: RegisterApplication,
principal: Principal
): dev.usbharu.hideout.core.application.model.Application {
val id = idGenerateService.generateId()
val clientSecret = secureTokenGenerator.generate()
val registeredClient = RegisteredClient
.withId(id.toString())
.clientId(id.toString())
.clientSecret(passwordEncoder.encode(clientSecret))
.clientName(command.name)
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST)
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT)
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.apply {
if (command.useRefreshToken) {
authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
} else {
tokenSettings(
TokenSettings
.builder()
.accessTokenTimeToLive(Duration.ofSeconds(31536000000))
.build()
)
}
}
.redirectUris { set ->
set.addAll(command.redirectUris.map { it.toString() })
}
.clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
.scopes { it.addAll(command.scopes) }
.build()
registeredClientRepository.save(registeredClient)
val application = Application(ApplicationId(id), ApplicationName(command.name))
applicationRepository.save(application)
return dev.usbharu.hideout.core.application.model.Application(
id = id,
name = command.name,
clientSecret = clientSecret,
clientId = id.toString(),
redirectUris = command.redirectUris
)
}
companion object {
private val logger = LoggerFactory.getLogger(RegisterApplicationApplicationService::class.java)
}
}
data class RegisterApplication(
val name: String,
val redirectUris: Set<URI>,
val useRefreshToken: Boolean,
val scopes: Set<String>,
)
@@ -0,0 +1,27 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.application.domainevent.subscribers
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEvent
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventBody
interface DomainEventSubscriber {
fun <T : DomainEventBody> subscribe(eventName: String, domainEventConsumer: DomainEventConsumer<T>)
fun getSubscribers(): Map<String, List<DomainEventConsumer<*>>>
}
typealias DomainEventConsumer<T> = suspend (DomainEvent<T>) -> Unit
@@ -0,0 +1,21 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.application.domainevent.subscribers
data class RegisterHomeTimeline(
val userDetailId: Long
)
@@ -0,0 +1,41 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.application.domainevent.subscribers
import dev.usbharu.hideout.core.application.timeline.UserRegisterHomeTimelineApplicationService
import dev.usbharu.hideout.core.domain.event.userdetail.UserDetailEvent
import dev.usbharu.hideout.core.domain.event.userdetail.UserDetailEventBody
import dev.usbharu.hideout.core.domain.model.support.principal.Anonymous
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEvent
import org.springframework.stereotype.Component
@Component
class RegisterLocalUserSetHomeTimelineSubscriber(
private val domainEventSubscriber: DomainEventSubscriber,
private val userRegisterHomeTimelineApplicationService: UserRegisterHomeTimelineApplicationService
) : Subscriber, DomainEventConsumer<UserDetailEventBody> {
override fun init() {
domainEventSubscriber.subscribe<UserDetailEventBody>(UserDetailEvent.CREATE.eventName, this)
}
override suspend fun invoke(p1: DomainEvent<UserDetailEventBody>) {
userRegisterHomeTimelineApplicationService.execute(
RegisterHomeTimeline(p1.body.getUserDetail().id),
Anonymous
)
}
}
@@ -0,0 +1,40 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.application.domainevent.subscribers
import dev.usbharu.hideout.core.application.timeline.SetTimelineToTimelineStoreApplicationService
import dev.usbharu.hideout.core.application.timeline.SetTimleineStore
import dev.usbharu.hideout.core.domain.event.timeline.TimelineEvent
import dev.usbharu.hideout.core.domain.event.timeline.TimelineEventBody
import dev.usbharu.hideout.core.domain.model.support.principal.Anonymous
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEvent
import org.springframework.stereotype.Component
@Component
class RegisterTimelineSetTimelineStoreSubscriber(
private val domainEventSubscriber: DomainEventSubscriber,
private val setTimelineToTimelineStoreApplicationService: SetTimelineToTimelineStoreApplicationService
) : Subscriber, DomainEventConsumer<TimelineEventBody> {
override fun init() {
domainEventSubscriber.subscribe<TimelineEventBody>(TimelineEvent.CREATE.eventName, this)
}
override suspend fun invoke(p1: DomainEvent<TimelineEventBody>) {
setTimelineToTimelineStoreApplicationService.execute(SetTimleineStore(p1.body.getTimelineId()), Anonymous)
}
}
@@ -0,0 +1,21 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.application.domainevent.subscribers
interface Subscriber {
fun init()
}
@@ -0,0 +1,62 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.application.domainevent.subscribers
import org.slf4j.LoggerFactory
import org.springframework.boot.ApplicationArguments
import org.springframework.boot.ApplicationRunner
import org.springframework.stereotype.Component
@Component
class SubscriberRunner(
private val subscribers: List<Subscriber>,
private val domainEventSubscriber: DomainEventSubscriber
) : ApplicationRunner {
override fun run(args: ApplicationArguments?) {
subscribers.forEach { it.init() }
if (logger.isDebugEnabled) {
val stringListMap = domainEventSubscriber.getSubscribers()
val header = """
|====== Domain Event Subscribers Report =====
|
|
|
""".trimMargin()
val value = stringListMap.map {
it.key + "\n\t" + it.value.joinToString("\n", "[", "]") { suspendFunction1 ->
suspendFunction1::class.qualifiedName.orEmpty()
}
}.joinToString("\n\n\n")
val footer = """
|
|
|
|===== Domain Event Subscribers Report =====
""".trimMargin()
logger.debug("{}{}{}", header, value, footer)
}
}
companion object {
private val logger = LoggerFactory.getLogger(SubscriberRunner::class.java)
}
}
@@ -0,0 +1,39 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.application.domainevent.subscribers
import dev.usbharu.hideout.core.application.timeline.AddPost
import dev.usbharu.hideout.core.application.timeline.TimelineAddPostApplicationService
import dev.usbharu.hideout.core.domain.event.post.PostEvent
import dev.usbharu.hideout.core.domain.event.post.PostEventBody
import dev.usbharu.hideout.core.domain.model.support.principal.Anonymous
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEvent
import org.springframework.stereotype.Component
@Component
class TimelinePostCreateSubscriber(
private val timelineAddPostApplicationService: TimelineAddPostApplicationService,
private val domainEventSubscriber: DomainEventSubscriber,
) : Subscriber, DomainEventConsumer<PostEventBody> {
override fun init() {
domainEventSubscriber.subscribe<PostEventBody>(PostEvent.CREATE.eventName, this)
}
override suspend fun invoke(p1: DomainEvent<PostEventBody>) {
timelineAddPostApplicationService.execute(AddPost(p1.body.getPostId()), Anonymous)
}
}
@@ -0,0 +1,64 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.application.domainevent.subscribers
import dev.usbharu.hideout.core.application.exception.InternalServerException
import dev.usbharu.hideout.core.application.timeline.AddTimelineRelationship
import dev.usbharu.hideout.core.application.timeline.UserAddTimelineRelationshipApplicationService
import dev.usbharu.hideout.core.domain.event.relationship.RelationshipEvent
import dev.usbharu.hideout.core.domain.event.relationship.RelationshipEventBody
import dev.usbharu.hideout.core.domain.model.timelinerelationship.Visible
import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailRepository
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEvent
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Component
@Component
class TimelineRelationshipFollowSubscriber(
private val userAddTimelineRelationshipApplicationService: UserAddTimelineRelationshipApplicationService,
private val userDetailRepository: UserDetailRepository,
private val domainEventSubscriber: DomainEventSubscriber
) : Subscriber, DomainEventConsumer<RelationshipEventBody> {
override fun init() {
domainEventSubscriber.subscribe<RelationshipEventBody>(RelationshipEvent.ACCEPT_FOLLOW.eventName, this)
}
override suspend fun invoke(p1: DomainEvent<RelationshipEventBody>) {
val relationship = p1.body.getRelationship()
val userDetail = userDetailRepository.findByActorId(relationship.actorId.id)
?: throw InternalServerException("Userdetail ${relationship.actorId} not found by actorid.")
if (userDetail.homeTimelineId == null) {
logger.warn("Home timeline for ${relationship.actorId} is not found")
return
}
@Suppress("UnsafeCallOnNullableType")
userAddTimelineRelationshipApplicationService.execute(
AddTimelineRelationship(
userDetail.homeTimelineId!!,
relationship.targetActorId,
Visible.FOLLOWERS
),
p1.body.principal
)
}
companion object {
private val logger = LoggerFactory.getLogger(TimelineRelationshipFollowSubscriber::class.java)
}
}
@@ -0,0 +1,65 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.application.domainevent.subscribers
import dev.usbharu.hideout.core.application.timeline.RemoveTimelineRelationship
import dev.usbharu.hideout.core.application.timeline.UserRemoveTimelineRelationshipApplicationService
import dev.usbharu.hideout.core.domain.event.relationship.RelationshipEvent
import dev.usbharu.hideout.core.domain.event.relationship.RelationshipEventBody
import dev.usbharu.hideout.core.domain.model.timelinerelationship.TimelineRelationshipRepository
import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailRepository
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEvent
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Component
@Component
class TimelineRelationshipUnfollowSubscriber(
private val domainEventSubscriber: DomainEventSubscriber,
private val userRemoveTimelineRelationshipApplicationService: UserRemoveTimelineRelationshipApplicationService,
private val userDetailRepository: UserDetailRepository,
private val timelineRelationshipRepository: TimelineRelationshipRepository,
) : Subscriber, DomainEventConsumer<RelationshipEventBody> {
override fun init() {
domainEventSubscriber.subscribe<RelationshipEventBody>(RelationshipEvent.UNFOLLOW.eventName, this)
}
override suspend fun invoke(p1: DomainEvent<RelationshipEventBody>) {
val relationship = p1.body.getRelationship()
val userDetail = userDetailRepository.findByActorId(relationship.actorId.id) ?: throw IllegalStateException(
"UserDetail ${relationship.actorId} not found by actorId."
)
if (userDetail.homeTimelineId == null) {
logger.warn("HomeTimeline for ${userDetail.id} not found.")
return
}
val timelineRelationship = timelineRelationshipRepository.findByTimelineIdAndActorId(
userDetail.homeTimelineId!!,
relationship.targetActorId
)
?: throw IllegalStateException("TimelineRelationship ${userDetail.homeTimelineId} to ${relationship.targetActorId} not found by timelineId and ActorId")
userRemoveTimelineRelationshipApplicationService.execute(
RemoveTimelineRelationship(timelineRelationship.id),
p1.body.principal
)
}
companion object {
private val logger = LoggerFactory.getLogger(TimelineRelationshipUnfollowSubscriber::class.java)
}
}
@@ -0,0 +1,30 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.application.exception
class InternalServerException : RuntimeException {
constructor() : super()
constructor(message: String?) : super(message)
constructor(message: String?, cause: Throwable?) : super(message, cause)
constructor(cause: Throwable?) : super(cause)
constructor(message: String?, cause: Throwable?, enableSuppression: Boolean, writableStackTrace: Boolean) : super(
message,
cause,
enableSuppression,
writableStackTrace
)
}
@@ -0,0 +1,33 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.application.exception
import dev.usbharu.hideout.core.domain.model.support.principal.Principal
class PermissionDeniedException : RuntimeException {
constructor(principal: Principal) : super("Permission Denied $principal")
constructor() : super()
constructor(message: String?) : super(message)
constructor(message: String?, cause: Throwable?) : super(message, cause)
constructor(cause: Throwable?) : super(cause)
constructor(message: String?, cause: Throwable?, enableSuppression: Boolean, writableStackTrace: Boolean) : super(
message,
cause,
enableSuppression,
writableStackTrace
)
}
@@ -0,0 +1,48 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.application.filter
import dev.usbharu.hideout.core.application.exception.PermissionDeniedException
import dev.usbharu.hideout.core.application.shared.LocalUserAbstractApplicationService
import dev.usbharu.hideout.core.application.shared.Transaction
import dev.usbharu.hideout.core.domain.model.filter.FilterId
import dev.usbharu.hideout.core.domain.model.filter.FilterRepository
import dev.usbharu.hideout.core.domain.model.support.principal.LocalUser
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
@Service
class UserDeleteFilterApplicationService(private val filterRepository: FilterRepository, transaction: Transaction) :
LocalUserAbstractApplicationService<DeleteFilter, Unit>(
transaction,
logger
) {
override suspend fun internalExecute(command: DeleteFilter, principal: LocalUser) {
val filter =
filterRepository.findByFilterId(FilterId(command.filterId)) ?: throw IllegalArgumentException("not found")
if (filter.userDetailId != principal.userDetailId) {
throw PermissionDeniedException()
}
filterRepository.delete(filter)
}
companion object {
private val logger = LoggerFactory.getLogger(UserDeleteFilterApplicationService::class.java)
}
}
data class DeleteFilter(val filterId: Long)
@@ -0,0 +1,50 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.application.filter
import dev.usbharu.hideout.core.application.exception.PermissionDeniedException
import dev.usbharu.hideout.core.application.model.Filter
import dev.usbharu.hideout.core.application.shared.LocalUserAbstractApplicationService
import dev.usbharu.hideout.core.application.shared.Transaction
import dev.usbharu.hideout.core.domain.model.filter.FilterId
import dev.usbharu.hideout.core.domain.model.filter.FilterRepository
import dev.usbharu.hideout.core.domain.model.support.principal.LocalUser
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
@Service
class UserGetFilterApplicationService(private val filterRepository: FilterRepository, transaction: Transaction) :
LocalUserAbstractApplicationService<GetFilter, Filter>(
transaction,
logger
) {
override suspend fun internalExecute(command: GetFilter, principal: LocalUser): Filter {
val filter =
filterRepository.findByFilterId(FilterId(command.filterId))
?: throw IllegalArgumentException("Filter ${command.filterId} not found.")
if (filter.userDetailId != principal.userDetailId) {
throw PermissionDeniedException()
}
return Filter.of(filter)
}
companion object {
private val logger = LoggerFactory.getLogger(UserGetFilterApplicationService::class.java)
}
}
data class GetFilter(val filterId: Long)
@@ -0,0 +1,75 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.application.filter
import dev.usbharu.hideout.core.application.model.Filter
import dev.usbharu.hideout.core.application.shared.LocalUserAbstractApplicationService
import dev.usbharu.hideout.core.application.shared.Transaction
import dev.usbharu.hideout.core.domain.model.filter.*
import dev.usbharu.hideout.core.domain.model.support.principal.LocalUser
import dev.usbharu.hideout.core.domain.shared.id.IdGenerateService
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
@Service
class UserRegisterFilterApplicationService(
private val idGenerateService: IdGenerateService,
private val filterRepository: FilterRepository,
transaction: Transaction,
) :
LocalUserAbstractApplicationService<RegisterFilter, Filter>(
transaction,
logger
) {
override suspend fun internalExecute(command: RegisterFilter, principal: LocalUser): Filter {
val filter = dev.usbharu.hideout.core.domain.model.filter.Filter.create(
id = FilterId(idGenerateService.generateId()),
userDetailId = principal.userDetailId,
name = FilterName(command.filterName),
filterContext = command.filterContext,
filterAction = command.filterAction,
filterKeywords = command.filterKeywords
.map {
FilterKeyword(
FilterKeywordId(idGenerateService.generateId()),
FilterKeywordKeyword(it.keyword),
it.filterMode
)
}.toSet()
)
filterRepository.save(filter)
return Filter.of(filter)
}
companion object {
private val logger = LoggerFactory.getLogger(UserRegisterFilterApplicationService::class.java)
}
}
data class RegisterFilter(
val filterName: String,
val filterContext: Set<FilterContext>,
val filterAction: FilterAction,
val filterKeywords: Set<RegisterFilterKeyword>,
)
data class RegisterFilterKeyword(
val keyword: String,
val filterMode: FilterMode,
)
@@ -0,0 +1,61 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.application.instance
import dev.usbharu.hideout.core.application.exception.InternalServerException
import dev.usbharu.hideout.core.application.model.Instance
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.instance.InstanceRepository
import dev.usbharu.hideout.core.domain.model.support.principal.Principal
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
@Service
class GetLocalInstanceApplicationService(
private val applicationConfig: ApplicationConfig,
private val instanceRepository: InstanceRepository,
transaction: Transaction
) :
AbstractApplicationService<Unit, Instance>(
transaction,
logger
) {
private var cachedInstance: Instance? = null
override suspend fun internalExecute(command: Unit, principal: Principal): Instance {
if (cachedInstance != null) {
logger.trace("Use cache {}", cachedInstance)
@Suppress("UnsafeCallOnNullableType")
return cachedInstance!!
}
val instance = (
instanceRepository.findByUrl(applicationConfig.url)
?: throw InternalServerException("Local instance not found.")
)
cachedInstance = Instance.of(instance)
@Suppress("UnsafeCallOnNullableType")
return cachedInstance!!
}
companion object {
private val logger = LoggerFactory.getLogger(GetLocalInstanceApplicationService::class.java)
}
}
@@ -0,0 +1,60 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.application.instance
import dev.usbharu.hideout.core.application.shared.Transaction
import dev.usbharu.hideout.core.config.ApplicationConfig
import dev.usbharu.hideout.core.domain.model.instance.*
import dev.usbharu.hideout.core.domain.model.support.domain.apHost
import dev.usbharu.hideout.core.domain.shared.id.IdGenerateService
import org.springframework.boot.context.event.ApplicationReadyEvent
import org.springframework.boot.info.BuildProperties
import org.springframework.context.event.EventListener
import org.springframework.stereotype.Service
import java.time.Instant
@Service
class InitLocalInstanceApplicationService(
private val applicationConfig: ApplicationConfig,
private val instanceRepository: InstanceRepository,
private val idGenerateService: IdGenerateService,
private val buildProperties: BuildProperties,
private val transaction: Transaction,
) {
@EventListener(ApplicationReadyEvent::class)
suspend fun init() = transaction.transaction {
val findByUrl = instanceRepository.findByUrl(applicationConfig.url)
if (findByUrl == null) {
val instance = Instance(
id = InstanceId(idGenerateService.generateId()),
name = InstanceName(applicationConfig.url.apHost),
description = InstanceDescription(""),
url = applicationConfig.url,
iconUrl = applicationConfig.url,
sharedInbox = null,
software = InstanceSoftware("hideout"),
version = InstanceVersion(buildProperties.version),
isBlocked = false,
isMuted = false,
moderationNote = InstanceModerationNote(""),
createdAt = Instant.now(),
)
instanceRepository.save(instance)
}
}
}
@@ -0,0 +1,83 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.application.media
import dev.usbharu.hideout.core.application.model.MediaDetail
import dev.usbharu.hideout.core.application.shared.LocalUserAbstractApplicationService
import dev.usbharu.hideout.core.application.shared.Transaction
import dev.usbharu.hideout.core.domain.model.media.*
import dev.usbharu.hideout.core.domain.model.support.principal.LocalUser
import dev.usbharu.hideout.core.domain.shared.id.IdGenerateService
import dev.usbharu.hideout.core.external.media.MediaProcessor
import dev.usbharu.hideout.core.external.mediastore.MediaStore
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.stereotype.Service
import java.net.URI
import java.nio.file.Path
import dev.usbharu.hideout.core.domain.model.media.Media as MediaModel
@Service
class UploadMediaApplicationService(
@Qualifier("delegate") private val mediaProcessor: MediaProcessor,
private val mediaStore: MediaStore,
private val mediaRepository: MediaRepository,
private val idGenerateService: IdGenerateService,
transaction: Transaction
) : LocalUserAbstractApplicationService<UploadMedia, MediaDetail>(
transaction,
logger
) {
override suspend fun internalExecute(command: UploadMedia, principal: LocalUser): MediaDetail {
val process = mediaProcessor.process(command.path, command.name, null)
val id = idGenerateService.generateId()
val thumbnailUri = if (process.thumbnailPath != null) {
mediaStore.upload(process.thumbnailPath, "thumbnail-$id.${process.mimeType.subtype}")
} else {
null
}
val uri = mediaStore.upload(process.path, "$id.${process.mimeType.subtype}")
val media = MediaModel(
id = MediaId(id),
name = MediaName(command.name),
url = uri,
remoteUrl = command.remoteUri,
thumbnailUrl = thumbnailUri,
type = process.fileType,
mimeType = process.mimeType,
blurHash = process.blurHash?.let { MediaBlurHash(it) },
description = command.description?.let { MediaDescription(it) },
actorId = principal.actorId
)
mediaRepository.save(media)
return MediaDetail.of(media)
}
companion object {
private val logger = LoggerFactory.getLogger(UploadMediaApplicationService::class.java)
}
}
data class UploadMedia(
val path: Path,
val name: String,
val remoteUri: URI?,
val description: String?
)
@@ -0,0 +1,58 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.application.model
import dev.usbharu.hideout.core.domain.model.actor.Actor
import dev.usbharu.hideout.core.domain.model.media.Media
import dev.usbharu.hideout.core.domain.model.support.domain.apHost
import java.net.URI
data class ActorDetail(
val id: Long,
val name: String,
val screenName: String,
val host: String,
val instanceId: Long,
val remoteUrl: URI?,
val locked: Boolean,
val description: String,
val postsCount: Int,
val iconUrl: URI?,
val bannerURL: URI?,
val followingCount: Int?,
val followersCount: Int?,
) {
companion object {
fun of(actor: Actor, iconMedia: Media?, bannerMedia: Media?): ActorDetail {
return ActorDetail(
id = actor.id.id,
name = actor.name.name,
screenName = actor.screenName.screenName,
host = actor.url.apHost,
instanceId = actor.instance.instanceId,
remoteUrl = actor.url,
locked = actor.locked,
description = actor.description.description,
postsCount = actor.postsCount.postsCount,
iconUrl = iconMedia?.url,
bannerURL = bannerMedia?.url,
followingCount = actor.followingCount?.relationshipCount,
followersCount = actor.followersCount?.relationshipCount,
)
}
}
}
@@ -0,0 +1,27 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.application.model
import java.net.URI
data class Application(
val id: Long,
val name: String,
val redirectUris: Set<URI>,
val clientSecret: String,
val clientId: String,
)
@@ -0,0 +1,49 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.application.model
import dev.usbharu.hideout.core.domain.model.filter.Filter
import dev.usbharu.hideout.core.domain.model.filter.FilterAction
import dev.usbharu.hideout.core.domain.model.filter.FilterContext
data class Filter(
val filterId: Long,
val userDetailId: Long,
val name: String,
val filterContext: Set<FilterContext>,
val filterAction: FilterAction,
val filterKeywords: Set<FilterKeyword>,
) {
companion object {
fun of(filter: Filter): dev.usbharu.hideout.core.application.model.Filter {
return Filter(
filterId = filter.id.id,
userDetailId = filter.userDetailId.id,
name = filter.name.name,
filterContext = filter.filterContext,
filterAction = filter.filterAction,
filterKeywords = filter.filterKeywords.map {
FilterKeyword(
it.id.id,
it.keyword.keyword,
it.mode
)
}.toSet()
)
}
}
}
@@ -0,0 +1,25 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.application.model
import dev.usbharu.hideout.core.domain.model.filter.FilterMode
data class FilterKeyword(
val id: Long,
val keyword: String,
val filterMode: FilterMode,
)
@@ -0,0 +1,33 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.application.model
import dev.usbharu.hideout.core.domain.model.instance.Instance
import java.net.URI
data class Instance(val id: Long, val name: String, val url: URI, val description: String) {
companion object {
fun of(instance: Instance): dev.usbharu.hideout.core.application.model.Instance {
return Instance(
instance.id.instanceId,
instance.name.name,
instance.url,
instance.description.description
)
}
}
}
@@ -0,0 +1,46 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.application.model
import dev.usbharu.hideout.core.domain.model.media.Media
import java.net.URI
data class MediaDetail(
val mediaId: Long,
val type: String,
val url: URI,
val thumbnailUrl: URI?,
val sensitive: Boolean,
val description: String,
val blurhash: String,
val actorId: Long
) {
companion object {
fun of(media: Media): MediaDetail {
return MediaDetail(
mediaId = media.id.id,
type = media.type.name,
url = media.url,
thumbnailUrl = media.thumbnailUrl,
sensitive = false,
description = media.description?.description.orEmpty(),
blurhash = media.blurHash?.hash.orEmpty(),
actorId = media.actorId.id
)
}
}
}
@@ -0,0 +1,81 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.application.model
import dev.usbharu.hideout.core.domain.model.actor.Actor
import dev.usbharu.hideout.core.domain.model.media.Media
import dev.usbharu.hideout.core.domain.model.post.Post
import dev.usbharu.hideout.core.domain.model.post.Visibility
import java.net.URI
import java.time.Instant
data class PostDetail(
val id: Long,
val actor: ActorDetail,
val overview: String?,
val text: String,
val content: String,
val createdAt: Instant,
val visibility: Visibility,
val pureRepost: Boolean,
val url: URI,
val apId: URI,
val repost: PostDetail?,
val reply: PostDetail?,
val sensitive: Boolean,
val deleted: Boolean,
val mediaDetailList: List<MediaDetail>,
val moveTo: PostDetail?,
val reactionsList: List<Reactions>,
val favourited: Boolean
) {
companion object {
@Suppress("LongParameterList")
fun of(
post: Post,
actor: Actor,
iconMedia: Media?,
mediaList: List<Media>,
reply: PostDetail? = null,
repost: PostDetail? = null,
moveTo: PostDetail? = null,
reactionsList: List<Reactions>,
favourited: Boolean
): PostDetail {
return PostDetail(
id = post.id.id,
actor = ActorDetail.of(actor, iconMedia, null),
overview = post.overview?.overview,
text = post.text,
content = post.content.content,
createdAt = post.createdAt,
visibility = post.visibility,
pureRepost = post.isPureRepost,
url = post.url,
apId = post.apId,
repost = repost,
reply = reply,
sensitive = post.sensitive,
deleted = false,
mediaDetailList = mediaList.map { MediaDetail.of(it) },
moveTo = moveTo,
reactionsList = reactionsList,
favourited = favourited
)
}
}
}
@@ -0,0 +1,44 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.application.model
import dev.usbharu.hideout.core.domain.model.emoji.CustomEmoji
import dev.usbharu.hideout.core.domain.model.reaction.Reaction
import java.net.URI
data class Reactions(
val postId: Long,
val count: Int,
val name: String,
val domain: String,
val url: URI?,
val actorIds: List<Long>,
) {
companion object {
fun of(reactionList: List<Reaction>, customEmoji: CustomEmoji?): Reactions {
val first = reactionList.first()
return Reactions(
postId = first.id.value,
count = reactionList.size,
name = customEmoji?.name ?: first.unicodeEmoji.name,
domain = customEmoji?.domain?.domain ?: first.unicodeEmoji.domain.domain,
url = customEmoji?.url,
actorIds = reactionList.map { it.actorId.id }
)
}
}
}
@@ -0,0 +1,58 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.application.model
import dev.usbharu.hideout.core.domain.model.actorinstancerelationship.ActorInstanceRelationship
import dev.usbharu.hideout.core.domain.model.relationship.Relationship
data class Relationship(
val actorId: Long,
val targetId: Long,
val following: Boolean,
val followedBy: Boolean,
val blocking: Boolean,
val blockedBy: Boolean,
val muting: Boolean,
val followRequesting: Boolean,
val followRequestedBy: Boolean,
val domainBlocking: Boolean,
val domainMuting: Boolean,
val domainDoNotSendPrivate: Boolean,
) {
companion object {
fun of(
relationship: Relationship,
relationship2: Relationship,
actorInstanceRelationship: ActorInstanceRelationship,
): dev.usbharu.hideout.core.application.model.Relationship {
return Relationship(
actorId = relationship.actorId.id,
targetId = relationship.targetActorId.id,
following = relationship.following,
followedBy = relationship2.following,
blocking = relationship.blocking,
blockedBy = relationship2.blocking,
muting = relationship.muting,
followRequesting = relationship.followRequesting,
followRequestedBy = relationship2.followRequesting,
domainBlocking = actorInstanceRelationship.blocking,
domainMuting = actorInstanceRelationship.muting,
domainDoNotSendPrivate = actorInstanceRelationship.doNotSendPrivate
)
}
}
}
@@ -0,0 +1,39 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.application.model
import dev.usbharu.hideout.core.domain.model.timeline.TimelineVisibility
data class Timeline(
val id: Long,
val userDetailId: Long,
val name: String,
val visibility: TimelineVisibility,
val isSystem: Boolean
) {
companion object {
fun of(timeline: dev.usbharu.hideout.core.domain.model.timeline.Timeline): Timeline {
return Timeline(
timeline.id.value,
timeline.userDetailId.id,
timeline.name.value,
timeline.visibility,
timeline.isSystem
)
}
}
}
@@ -0,0 +1,70 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.application.model
import dev.usbharu.hideout.core.domain.model.actor.Actor
import dev.usbharu.hideout.core.domain.model.emoji.CustomEmoji
import dev.usbharu.hideout.core.domain.model.userdetails.UserDetail
import java.time.Instant
data class UserDetail(
val id: Long,
val userDetailId: Long,
val name: String,
val domain: String,
val screenName: String,
val url: String,
val iconUrl: String,
val description: String,
val locked: Boolean,
val emojis: List<CustomEmoji>,
val createdAt: Instant,
val lastPostAt: Instant?,
val postsCount: Int,
val followingCount: Int?,
val followersCount: Int?,
val moveTo: Long?,
val suspend: Boolean,
) {
companion object {
fun of(
actor: Actor,
userDetail: UserDetail,
customEmojis: List<CustomEmoji>,
): dev.usbharu.hideout.core.application.model.UserDetail {
return UserDetail(
id = actor.id.id,
userDetailId = userDetail.id.id,
name = actor.name.name,
domain = actor.domain.domain,
screenName = actor.screenName.screenName,
url = actor.url.toString(),
iconUrl = actor.url.toString(),
description = actor.description.description,
locked = actor.locked,
emojis = customEmojis,
createdAt = actor.createdAt,
lastPostAt = actor.lastPostAt,
postsCount = actor.postsCount.postsCount,
followingCount = actor.followingCount?.relationshipCount,
followersCount = actor.followersCount?.relationshipCount,
moveTo = actor.moveTo?.id,
suspend = actor.suspend
)
}
}
}
@@ -0,0 +1,54 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.application.post
import dev.usbharu.hideout.core.application.exception.InternalServerException
import dev.usbharu.hideout.core.application.exception.PermissionDeniedException
import dev.usbharu.hideout.core.application.shared.LocalUserAbstractApplicationService
import dev.usbharu.hideout.core.application.shared.Transaction
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
import dev.usbharu.hideout.core.domain.model.post.PostId
import dev.usbharu.hideout.core.domain.model.post.PostRepository
import dev.usbharu.hideout.core.domain.model.support.principal.LocalUser
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
@Service
class DeleteLocalPostApplicationService(
private val postRepository: PostRepository,
private val actorRepository: ActorRepository,
transaction: Transaction,
) : LocalUserAbstractApplicationService<DeleteLocalPost, Unit>(transaction, logger) {
override suspend fun internalExecute(command: DeleteLocalPost, principal: LocalUser) {
val findById = postRepository.findById(PostId(command.postId))
?: throw IllegalArgumentException("Post ${command.postId} not found.")
if (findById.actorId != principal.actorId) {
throw PermissionDeniedException()
}
val actor = actorRepository.findById(principal.actorId)
?: throw InternalServerException("Actor ${principal.actorId} not found.")
findById.delete(actor)
postRepository.save(findById)
}
companion object {
private val logger = LoggerFactory.getLogger(DeleteLocalPostApplicationService::class.java)
}
}
data class DeleteLocalPost(val postId: Long)
@@ -0,0 +1,118 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.application.post
import dev.usbharu.hideout.core.application.exception.InternalServerException
import dev.usbharu.hideout.core.application.model.PostDetail
import dev.usbharu.hideout.core.application.shared.AbstractApplicationService
import dev.usbharu.hideout.core.application.shared.Transaction
import dev.usbharu.hideout.core.domain.model.actor.Actor
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
import dev.usbharu.hideout.core.domain.model.media.Media
import dev.usbharu.hideout.core.domain.model.media.MediaRepository
import dev.usbharu.hideout.core.domain.model.post.PostId
import dev.usbharu.hideout.core.domain.model.post.PostRepository
import dev.usbharu.hideout.core.domain.model.reaction.ReactionRepository
import dev.usbharu.hideout.core.domain.model.support.principal.Principal
import dev.usbharu.hideout.core.domain.service.post.IPostReadAccessControl
import dev.usbharu.hideout.core.query.reactions.ReactionsQueryService
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
@Service
class GetPostDetailApplicationService(
transaction: Transaction,
private val postRepository: PostRepository,
private val actorRepository: ActorRepository,
private val mediaRepository: MediaRepository,
private val iPostReadAccessControl: IPostReadAccessControl,
private val reactionsQueryService: ReactionsQueryService,
private val reactionRepository: ReactionRepository,
) : AbstractApplicationService<GetPostDetail, PostDetail>(
transaction,
logger
) {
override suspend fun internalExecute(command: GetPostDetail, principal: Principal): PostDetail {
val post = postRepository.findById(PostId(command.postId))
?: throw IllegalArgumentException("Post ${command.postId} not found.")
if (iPostReadAccessControl.isAllow(post, principal).not()) {
throw IllegalArgumentException("Post ${command.postId} not found.")
}
val actor =
actorRepository.findById(post.actorId) ?: throw InternalServerException("Actor ${post.actorId} not found.")
val iconMedia = actor.icon?.let { mediaRepository.findById(it) }
val mediaList = mediaRepository.findByIdIn(post.mediaIds)
val reactions = reactionsQueryService.findAllByPostId(post.id)
val favourited = reactionRepository.existsByPostIdAndActorIdAndCustomEmojiIdOrUnicodeEmoji(
post.id,
principal.actorId,
null,
"❤"
)
return PostDetail.of(
post = post,
actor = actor,
iconMedia = iconMedia,
mediaList = mediaList,
reply = post.replyId?.let { fetchChild(it, actor, iconMedia, principal) },
repost = post.repostId?.let { fetchChild(it, actor, iconMedia, principal) },
moveTo = post.moveTo?.let { fetchChild(it, actor, iconMedia, principal) },
reactionsList = reactions,
favourited
)
}
private suspend fun fetchChild(
postId: PostId,
actor: Actor,
iconMedia: Media?,
principal: Principal
): PostDetail? {
val post = postRepository.findById(postId) ?: return null
if (iPostReadAccessControl.isAllow(post, principal).not()) {
return null
}
val (first, third) = if (actor.id != post.actorId) {
(actorRepository.findById(post.actorId) ?: return null) to actor.icon?.let { mediaRepository.findById(it) }
} else {
actor to iconMedia
}
val mediaList = mediaRepository.findByIdIn(post.mediaIds)
return PostDetail.of(
post = post,
actor = first,
iconMedia = third,
mediaList = mediaList,
reactionsList = emptyList(),
favourited = false
)
}
companion object {
private val logger = LoggerFactory.getLogger(GetPostDetailApplicationService::class.java)
}
}
data class GetPostDetail(val postId: Long)
@@ -0,0 +1,77 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.application.post
import dev.usbharu.hideout.core.application.exception.InternalServerException
import dev.usbharu.hideout.core.application.shared.LocalUserAbstractApplicationService
import dev.usbharu.hideout.core.application.shared.Transaction
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
import dev.usbharu.hideout.core.domain.model.media.MediaId
import dev.usbharu.hideout.core.domain.model.post.PostId
import dev.usbharu.hideout.core.domain.model.post.PostOverview
import dev.usbharu.hideout.core.domain.model.post.PostRepository
import dev.usbharu.hideout.core.domain.model.post.Visibility
import dev.usbharu.hideout.core.domain.model.support.principal.LocalUser
import dev.usbharu.hideout.core.infrastructure.factory.PostFactoryImpl
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
@Service
class RegisterLocalPostApplicationService(
private val postFactory: PostFactoryImpl,
private val actorRepository: ActorRepository,
private val postRepository: PostRepository,
transaction: Transaction,
) : LocalUserAbstractApplicationService<RegisterLocalPost, Long>(transaction, Companion.logger) {
override suspend fun internalExecute(command: RegisterLocalPost, principal: LocalUser): Long {
val actorId = principal.actorId
val actor = actorRepository.findById(actorId) ?: throw InternalServerException("Actor $actorId not found.")
val post = postFactory.createLocal(
actor = actor,
actorName = actor.name,
overview = command.overview?.let { PostOverview(it) },
content = command.content,
visibility = command.visibility,
repostId = command.repostId?.let { PostId(it) },
replyId = command.replyId?.let { PostId(it) },
sensitive = command.sensitive,
mediaIds = command.mediaIds.map { MediaId(it) },
)
postRepository.save(post)
return post.id.id
}
companion object {
val logger: Logger = LoggerFactory.getLogger(RegisterLocalPostApplicationService::class.java)
}
}
data class RegisterLocalPost(
val content: String,
val overview: String?,
val visibility: Visibility,
val repostId: Long?,
val replyId: Long?,
val sensitive: Boolean,
val mediaIds: List<Long>,
)
@@ -0,0 +1,74 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.application.post
import dev.usbharu.hideout.core.application.exception.InternalServerException
import dev.usbharu.hideout.core.application.exception.PermissionDeniedException
import dev.usbharu.hideout.core.application.shared.LocalUserAbstractApplicationService
import dev.usbharu.hideout.core.application.shared.Transaction
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
import dev.usbharu.hideout.core.domain.model.media.MediaId
import dev.usbharu.hideout.core.domain.model.post.PostId
import dev.usbharu.hideout.core.domain.model.post.PostOverview
import dev.usbharu.hideout.core.domain.model.post.PostRepository
import dev.usbharu.hideout.core.domain.model.support.principal.LocalUser
import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailRepository
import dev.usbharu.hideout.core.infrastructure.factory.PostContentFactoryImpl
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
@Service
class UpdateLocalNoteApplicationService(
transaction: Transaction,
private val postRepository: PostRepository,
private val postContentFactoryImpl: PostContentFactoryImpl,
private val userDetailRepository: UserDetailRepository,
private val actorRepository: ActorRepository,
) : LocalUserAbstractApplicationService<UpdateLocalNote, Unit>(transaction, logger) {
override suspend fun internalExecute(command: UpdateLocalNote, principal: LocalUser) {
val post = postRepository.findById(PostId(command.postId))
?: throw IllegalArgumentException("Post ${command.postId} not found.")
if (post.actorId != principal.actorId) {
throw PermissionDeniedException()
}
val userDetail = userDetailRepository.findById(principal.userDetailId)
?: throw InternalServerException("User detail ${principal.userDetailId} not found.")
val actor = actorRepository.findById(userDetail.actorId)
?: throw InternalServerException("Actor ${principal.actorId} not found.")
post.setContent(postContentFactoryImpl.create(command.content), actor)
post.setOverview(command.overview?.let { PostOverview(it) }, actor)
post.addMediaIds(command.mediaIds.map { MediaId(it) }, actor)
post.setSensitive(command.sensitive, actor)
postRepository.save(post)
}
companion object {
private val logger = LoggerFactory.getLogger(UpdateLocalNoteApplicationService::class.java)
}
}
data class UpdateLocalNote(
val postId: Long,
val overview: String?,
val content: String,
val sensitive: Boolean,
val mediaIds: List<Long>
)
@@ -0,0 +1,83 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.application.reaction
import dev.usbharu.hideout.core.application.exception.PermissionDeniedException
import dev.usbharu.hideout.core.application.shared.LocalUserAbstractApplicationService
import dev.usbharu.hideout.core.application.shared.Transaction
import dev.usbharu.hideout.core.domain.model.emoji.CustomEmojiRepository
import dev.usbharu.hideout.core.domain.model.emoji.UnicodeEmoji
import dev.usbharu.hideout.core.domain.model.post.PostId
import dev.usbharu.hideout.core.domain.model.post.PostRepository
import dev.usbharu.hideout.core.domain.model.reaction.Reaction
import dev.usbharu.hideout.core.domain.model.reaction.ReactionId
import dev.usbharu.hideout.core.domain.model.reaction.ReactionRepository
import dev.usbharu.hideout.core.domain.model.support.principal.LocalUser
import dev.usbharu.hideout.core.domain.service.emoji.UnicodeEmojiService
import dev.usbharu.hideout.core.domain.service.post.IPostReadAccessControl
import dev.usbharu.hideout.core.domain.shared.id.IdGenerateService
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
import java.time.Instant
@Service
class UserCreateReactionApplicationService(
transaction: Transaction,
private val idGenerateService: IdGenerateService,
private val reactionRepository: ReactionRepository,
private val postReadAccessControl: IPostReadAccessControl,
private val postRepository: PostRepository,
private val customEmojiRepository: CustomEmojiRepository,
private val unicodeEmojiService: UnicodeEmojiService
) :
LocalUserAbstractApplicationService<CreateReaction, Unit>(
transaction,
logger
) {
override suspend fun internalExecute(command: CreateReaction, principal: LocalUser) {
val postId = PostId(command.postId)
val post = postRepository.findById(postId) ?: throw IllegalArgumentException("Post $postId not found.")
if (postReadAccessControl.isAllow(post, principal).not()) {
throw PermissionDeniedException()
}
val customEmoji = command.customEmojiId?.let { customEmojiRepository.findById(it) }
val unicodeEmoji = if (unicodeEmojiService.isUnicodeEmoji(command.unicodeEmoji)) {
command.unicodeEmoji
} else {
"❤"
}
val reaction = Reaction.create(
id = ReactionId(idGenerateService.generateId()),
postId = postId,
actorId = principal.actorId,
customEmojiId = customEmoji?.id,
unicodeEmoji = UnicodeEmoji(unicodeEmoji),
createdAt = Instant.now()
)
reactionRepository.save(reaction)
}
companion object {
private val logger = LoggerFactory.getLogger(UserCreateReactionApplicationService::class.java)
}
}
data class CreateReaction(val postId: Long, val customEmojiId: Long?, val unicodeEmoji: String)
@@ -0,0 +1,73 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.application.reaction
import dev.usbharu.hideout.core.application.shared.LocalUserAbstractApplicationService
import dev.usbharu.hideout.core.application.shared.Transaction
import dev.usbharu.hideout.core.domain.model.emoji.CustomEmojiRepository
import dev.usbharu.hideout.core.domain.model.post.PostId
import dev.usbharu.hideout.core.domain.model.reaction.ReactionRepository
import dev.usbharu.hideout.core.domain.model.support.principal.LocalUser
import dev.usbharu.hideout.core.domain.service.emoji.UnicodeEmojiService
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
@Service
class UserRemoveReactionApplicationService(
transaction: Transaction,
private val customEmojiRepository: CustomEmojiRepository,
private val reactionRepository: ReactionRepository,
private val unicodeEmojiService: UnicodeEmojiService
) :
LocalUserAbstractApplicationService<RemoveReaction, Unit>(
transaction,
logger
) {
override suspend fun internalExecute(command: RemoveReaction, principal: LocalUser) {
val postId = PostId(command.postId)
val customEmoji = command.customEmojiId?.let { customEmojiRepository.findById(it) }
val unicodeEmoji = if (unicodeEmojiService.isUnicodeEmoji(command.unicodeEmoji)) {
command.unicodeEmoji
} else {
"❤"
}
val reaction =
reactionRepository.findByPostIdAndActorIdAndCustomEmojiIdOrUnicodeEmoji(
postId,
principal.actorId,
customEmoji?.id,
unicodeEmoji
)
?: throw IllegalArgumentException("Reaction $postId ${principal.actorId} ${customEmoji?.id} $unicodeEmoji not found.")
reaction.delete()
reactionRepository.delete(reaction)
}
companion object {
private val logger = LoggerFactory.getLogger(UserRemoveReactionApplicationService::class.java)
}
}
data class RemoveReaction(
val postId: Long,
val customEmojiId: Long?,
val unicodeEmoji: String
)
@@ -0,0 +1,74 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.application.relationship
import dev.usbharu.hideout.core.application.exception.InternalServerException
import dev.usbharu.hideout.core.application.model.Relationship
import dev.usbharu.hideout.core.application.shared.LocalUserAbstractApplicationService
import dev.usbharu.hideout.core.application.shared.Transaction
import dev.usbharu.hideout.core.domain.model.actor.ActorId
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
import dev.usbharu.hideout.core.domain.model.actorinstancerelationship.ActorInstanceRelationship
import dev.usbharu.hideout.core.domain.model.actorinstancerelationship.ActorInstanceRelationshipRepository
import dev.usbharu.hideout.core.domain.model.relationship.RelationshipRepository
import dev.usbharu.hideout.core.domain.model.support.principal.LocalUser
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
@Service
class GetRelationshipApplicationService(
private val relationshipRepository: RelationshipRepository,
private val actorRepository: ActorRepository,
private val actorInstanceRelationshipRepository: ActorInstanceRelationshipRepository,
transaction: Transaction,
) :
LocalUserAbstractApplicationService<GetRelationship, Relationship>(
transaction,
logger
) {
override suspend fun internalExecute(command: GetRelationship, principal: LocalUser): Relationship {
val actor = actorRepository.findById(principal.actorId)
?: throw InternalServerException("Actor ${principal.actorId} not found.")
val targetId = ActorId(command.targetActorId)
val target = actorRepository.findById(targetId)
?: throw IllegalArgumentException("Actor ${command.targetActorId} not found.")
val relationship = (
relationshipRepository.findByActorIdAndTargetId(actor.id, targetId)
?: dev.usbharu.hideout.core.domain.model.relationship.Relationship.default(actor.id, targetId)
)
val relationship1 = (
relationshipRepository.findByActorIdAndTargetId(targetId, actor.id)
?: dev.usbharu.hideout.core.domain.model.relationship.Relationship.default(targetId, actor.id)
)
val actorInstanceRelationship =
actorInstanceRelationshipRepository.findByActorIdAndInstanceId(actor.id, target.instance)
?: ActorInstanceRelationship.default(
actor.id,
target.instance
)
return Relationship.of(relationship, relationship1, actorInstanceRelationship)
}
companion object {
private val logger = LoggerFactory.getLogger(GetRelationshipApplicationService::class.java)
}
}
data class GetRelationship(val targetActorId: Long)
@@ -0,0 +1,55 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.application.relationship
import dev.usbharu.hideout.core.application.exception.InternalServerException
import dev.usbharu.hideout.core.application.shared.LocalUserAbstractApplicationService
import dev.usbharu.hideout.core.application.shared.Transaction
import dev.usbharu.hideout.core.domain.model.actor.ActorId
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
import dev.usbharu.hideout.core.domain.model.relationship.RelationshipRepository
import dev.usbharu.hideout.core.domain.model.support.principal.LocalUser
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
@Service
class UserAcceptFollowRequestApplicationService(
private val relationshipRepository: RelationshipRepository,
transaction: Transaction,
private val actorRepository: ActorRepository,
) :
LocalUserAbstractApplicationService<AcceptFollowRequest, Unit>(transaction, logger) {
override suspend fun internalExecute(command: AcceptFollowRequest, principal: LocalUser) {
val actor = actorRepository.findById(principal.actorId)
?: throw InternalServerException("Actor ${principal.actorId} not found")
val targetId = ActorId(command.sourceActorId)
val relationship = relationshipRepository.findByActorIdAndTargetId(targetId, actor.id)
?: throw InternalServerException("Follow request not found")
relationship.acceptFollowRequest()
relationshipRepository.save(relationship)
}
companion object {
private val logger = LoggerFactory.getLogger(UserBlockApplicationService::class.java)
}
}
data class AcceptFollowRequest(val sourceActorId: Long)
@@ -0,0 +1,65 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.application.relationship
import dev.usbharu.hideout.core.application.shared.LocalUserAbstractApplicationService
import dev.usbharu.hideout.core.application.shared.Transaction
import dev.usbharu.hideout.core.domain.model.actor.ActorId
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
import dev.usbharu.hideout.core.domain.model.relationship.Relationship
import dev.usbharu.hideout.core.domain.model.relationship.RelationshipRepository
import dev.usbharu.hideout.core.domain.model.support.principal.LocalUser
import dev.usbharu.hideout.core.domain.service.relationship.RelationshipDomainService
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
@Service
class UserBlockApplicationService(
private val relationshipRepository: RelationshipRepository,
transaction: Transaction,
private val actorRepository: ActorRepository,
private val relationshipDomainService: RelationshipDomainService,
) :
LocalUserAbstractApplicationService<Block, Unit>(transaction, logger) {
override suspend fun internalExecute(command: Block, principal: LocalUser) {
val actor = actorRepository.findById(principal.actorId)
?: throw IllegalStateException("Actor ${principal.actorId} not found")
val targetId = ActorId(command.targetActorId)
val relationship = relationshipRepository.findByActorIdAndTargetId(actor.id, targetId) ?: Relationship.default(
actor.id,
targetId
)
val inverseRelationship =
relationshipRepository.findByActorIdAndTargetId(targetId, actor.id) ?: Relationship.default(
targetId,
actor.id
)
relationshipDomainService.block(relationship, inverseRelationship)
relationshipRepository.save(relationship)
relationshipRepository.save(inverseRelationship)
}
companion object {
private val logger = LoggerFactory.getLogger(UserBlockApplicationService::class.java)
}
}
data class Block(val targetActorId: Long)
@@ -0,0 +1,66 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.application.relationship
import dev.usbharu.hideout.core.application.exception.InternalServerException
import dev.usbharu.hideout.core.application.shared.LocalUserAbstractApplicationService
import dev.usbharu.hideout.core.application.shared.Transaction
import dev.usbharu.hideout.core.domain.model.actor.ActorId
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
import dev.usbharu.hideout.core.domain.model.relationship.Relationship
import dev.usbharu.hideout.core.domain.model.relationship.RelationshipRepository
import dev.usbharu.hideout.core.domain.model.support.principal.LocalUser
import dev.usbharu.hideout.core.domain.service.relationship.RelationshipDomainService
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
@Service
class UserFollowRequestApplicationService(
private val relationshipRepository: RelationshipRepository,
transaction: Transaction,
private val actorRepository: ActorRepository,
private val relationshipDomainService: RelationshipDomainService
) : LocalUserAbstractApplicationService<FollowRequest, Unit>(
transaction,
logger
) {
override suspend fun internalExecute(command: FollowRequest, principal: LocalUser) {
val actor = actorRepository.findById(principal.actorId)
?: throw InternalServerException("Actor ${principal.actorId} not found.")
val targetId = ActorId(command.targetActorId)
val relationship = relationshipRepository.findByActorIdAndTargetId(actor.id, targetId) ?: Relationship.default(
actor.id, targetId
)
val inverseRelationship =
relationshipRepository.findByActorIdAndTargetId(targetId, actor.id) ?: Relationship.default(
targetId, actor.id
)
relationshipDomainService.followRequest(relationship, inverseRelationship)
relationshipRepository.save(relationship)
}
companion object {
private val logger = LoggerFactory.getLogger(UserFollowRequestApplicationService::class.java)
}
}
data class FollowRequest(val targetActorId: Long)
@@ -0,0 +1,57 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.application.relationship
import dev.usbharu.hideout.core.application.exception.InternalServerException
import dev.usbharu.hideout.core.application.shared.LocalUserAbstractApplicationService
import dev.usbharu.hideout.core.application.shared.Transaction
import dev.usbharu.hideout.core.domain.model.actor.ActorId
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
import dev.usbharu.hideout.core.domain.model.relationship.Relationship
import dev.usbharu.hideout.core.domain.model.relationship.RelationshipRepository
import dev.usbharu.hideout.core.domain.model.support.principal.LocalUser
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
@Service
class UserMuteApplicationService(
private val relationshipRepository: RelationshipRepository,
transaction: Transaction,
private val actorRepository: ActorRepository,
) :
LocalUserAbstractApplicationService<Mute, Unit>(transaction, logger) {
override suspend fun internalExecute(command: Mute, principal: LocalUser) {
val actor = actorRepository.findById(principal.actorId)
?: throw InternalServerException("Actor ${principal.actorId} not found.")
val targetId = ActorId(command.targetActorId)
val relationship = relationshipRepository.findByActorIdAndTargetId(actor.id, targetId) ?: Relationship.default(
actor.id,
targetId
)
relationship.mute()
relationshipRepository.save(relationship)
}
companion object {
private val logger = LoggerFactory.getLogger(UserBlockApplicationService::class.java)
}
}
data class Mute(val targetActorId: Long)
@@ -0,0 +1,55 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.application.relationship
import dev.usbharu.hideout.core.application.exception.InternalServerException
import dev.usbharu.hideout.core.application.shared.LocalUserAbstractApplicationService
import dev.usbharu.hideout.core.application.shared.Transaction
import dev.usbharu.hideout.core.domain.model.actor.ActorId
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
import dev.usbharu.hideout.core.domain.model.relationship.RelationshipRepository
import dev.usbharu.hideout.core.domain.model.support.principal.LocalUser
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
@Service
class UserRejectFollowRequestApplicationService(
private val relationshipRepository: RelationshipRepository,
transaction: Transaction,
private val actorRepository: ActorRepository,
) :
LocalUserAbstractApplicationService<RejectFollowRequest, Unit>(transaction, logger) {
override suspend fun internalExecute(command: RejectFollowRequest, principal: LocalUser) {
val actor = actorRepository.findById(principal.actorId)
?: throw InternalServerException("Actor ${principal.actorId} not found.")
val targetId = ActorId(command.sourceActorId)
val relationship = relationshipRepository.findByActorIdAndTargetId(targetId, actor.id)
?: throw IllegalArgumentException("Follow request not found")
relationship.rejectFollowRequest()
relationshipRepository.save(relationship)
}
companion object {
private val logger = LoggerFactory.getLogger(UserBlockApplicationService::class.java)
}
}
data class RejectFollowRequest(val sourceActorId: Long)
@@ -0,0 +1,57 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.application.relationship
import dev.usbharu.hideout.core.application.exception.InternalServerException
import dev.usbharu.hideout.core.application.shared.LocalUserAbstractApplicationService
import dev.usbharu.hideout.core.application.shared.Transaction
import dev.usbharu.hideout.core.domain.model.actor.ActorId
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
import dev.usbharu.hideout.core.domain.model.relationship.Relationship
import dev.usbharu.hideout.core.domain.model.relationship.RelationshipRepository
import dev.usbharu.hideout.core.domain.model.support.principal.LocalUser
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
@Service
class UserRemoveFromFollowersApplicationService(
private val relationshipRepository: RelationshipRepository,
transaction: Transaction,
private val actorRepository: ActorRepository,
) :
LocalUserAbstractApplicationService<RemoveFromFollowers, Unit>(transaction, logger) {
override suspend fun internalExecute(command: RemoveFromFollowers, principal: LocalUser) {
val actor = actorRepository.findById(principal.actorId)
?: throw InternalServerException("Actor ${principal.actorId} not found.")
val targetId = ActorId(command.targetActorId)
val relationship = relationshipRepository.findByActorIdAndTargetId(targetId, actor.id) ?: Relationship.default(
targetId,
actor.id
)
relationship.unfollow()
relationshipRepository.save(relationship)
}
companion object {
private val logger = LoggerFactory.getLogger(UserBlockApplicationService::class.java)
}
}
data class RemoveFromFollowers(val targetActorId: Long)
@@ -0,0 +1,57 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.application.relationship
import dev.usbharu.hideout.core.application.exception.InternalServerException
import dev.usbharu.hideout.core.application.shared.LocalUserAbstractApplicationService
import dev.usbharu.hideout.core.application.shared.Transaction
import dev.usbharu.hideout.core.domain.model.actor.ActorId
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
import dev.usbharu.hideout.core.domain.model.relationship.Relationship
import dev.usbharu.hideout.core.domain.model.relationship.RelationshipRepository
import dev.usbharu.hideout.core.domain.model.support.principal.LocalUser
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
@Service
class UserUnblockApplicationService(
private val relationshipRepository: RelationshipRepository,
transaction: Transaction,
private val actorRepository: ActorRepository,
) :
LocalUserAbstractApplicationService<Unblock, Unit>(transaction, logger) {
override suspend fun internalExecute(command: Unblock, principal: LocalUser) {
val actor = actorRepository.findById(principal.actorId)
?: throw InternalServerException("Actor ${principal.actorId} not found.")
val targetId = ActorId(command.targetActorId)
val relationship = relationshipRepository.findByActorIdAndTargetId(actor.id, targetId) ?: Relationship.default(
actor.id,
targetId
)
relationship.unblock()
relationshipRepository.save(relationship)
}
companion object {
private val logger = LoggerFactory.getLogger(UserBlockApplicationService::class.java)
}
}
data class Unblock(val targetActorId: Long)
@@ -0,0 +1,57 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.application.relationship
import dev.usbharu.hideout.core.application.exception.InternalServerException
import dev.usbharu.hideout.core.application.shared.LocalUserAbstractApplicationService
import dev.usbharu.hideout.core.application.shared.Transaction
import dev.usbharu.hideout.core.domain.model.actor.ActorId
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
import dev.usbharu.hideout.core.domain.model.relationship.Relationship
import dev.usbharu.hideout.core.domain.model.relationship.RelationshipRepository
import dev.usbharu.hideout.core.domain.model.support.principal.LocalUser
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
@Service
class UserUnfollowApplicationService(
private val relationshipRepository: RelationshipRepository,
transaction: Transaction,
private val actorRepository: ActorRepository,
) :
LocalUserAbstractApplicationService<Unfollow, Unit>(transaction, logger) {
override suspend fun internalExecute(command: Unfollow, principal: LocalUser) {
val actor = actorRepository.findById(principal.actorId)
?: throw InternalServerException("Actor ${principal.actorId} not found.")
val targetId = ActorId(command.targetActorId)
val relationship = relationshipRepository.findByActorIdAndTargetId(actor.id, targetId) ?: Relationship.default(
actor.id,
targetId
)
relationship.unfollow()
relationshipRepository.save(relationship)
}
companion object {
private val logger = LoggerFactory.getLogger(UserBlockApplicationService::class.java)
}
}
data class Unfollow(val targetActorId: Long)
@@ -0,0 +1,57 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.application.relationship
import dev.usbharu.hideout.core.application.exception.InternalServerException
import dev.usbharu.hideout.core.application.shared.LocalUserAbstractApplicationService
import dev.usbharu.hideout.core.application.shared.Transaction
import dev.usbharu.hideout.core.domain.model.actor.ActorId
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
import dev.usbharu.hideout.core.domain.model.relationship.Relationship
import dev.usbharu.hideout.core.domain.model.relationship.RelationshipRepository
import dev.usbharu.hideout.core.domain.model.support.principal.LocalUser
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
@Service
class UserUnmuteApplicationService(
private val relationshipRepository: RelationshipRepository,
transaction: Transaction,
private val actorRepository: ActorRepository,
) :
LocalUserAbstractApplicationService<Unmute, Unit>(transaction, logger) {
override suspend fun internalExecute(command: Unmute, principal: LocalUser) {
val actor = actorRepository.findById(principal.actorId)
?: throw InternalServerException("Actor ${principal.actorId} not found.")
val targetId = ActorId(command.targetActorId)
val relationship = relationshipRepository.findByActorIdAndTargetId(actor.id, targetId) ?: Relationship.default(
actor.id,
targetId
)
relationship.unmute()
relationshipRepository.save(relationship)
}
companion object {
private val logger = LoggerFactory.getLogger(UserBlockApplicationService::class.java)
}
}
data class Unmute(val targetActorId: Long)
@@ -0,0 +1,50 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.application.shared
import dev.usbharu.hideout.core.domain.model.support.principal.Principal
import kotlinx.coroutines.CancellationException
import org.slf4j.Logger
import org.slf4j.MDC
abstract class AbstractApplicationService<T : Any, R>(
protected val transaction: Transaction,
protected val logger: Logger,
) : ApplicationService<T, R> {
override suspend fun execute(command: T, principal: Principal): R {
return try {
MDC.put("applicationService", this::class.simpleName)
logger.debug("START {}", command::class.simpleName)
val response = transaction.transaction<R> {
internalExecute(command, principal)
}
logger.info("SUCCESS $command $response")
response
} catch (e: CancellationException) {
logger.debug("Coroutine canceled", e)
throw e
} catch (@Suppress("TooGenericExceptionCaught") e: Exception) {
logger.warn("Command execution error", e)
throw e
} finally {
MDC.remove("applicationService")
}
}
protected abstract suspend fun internalExecute(command: T, principal: Principal): R
}
@@ -0,0 +1,23 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.application.shared
import dev.usbharu.hideout.core.domain.model.support.principal.Principal
interface ApplicationService<T : Any, R> {
suspend fun execute(command: T, principal: Principal): R
}
@@ -0,0 +1,34 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.application.shared
import dev.usbharu.hideout.core.application.exception.PermissionDeniedException
import dev.usbharu.hideout.core.domain.model.support.principal.LocalUser
import dev.usbharu.hideout.core.domain.model.support.principal.Principal
import org.slf4j.Logger
abstract class LocalUserAbstractApplicationService<T : Any, R>(transaction: Transaction, logger: Logger) :
AbstractApplicationService<T, R>(transaction, logger) {
override suspend fun internalExecute(command: T, principal: Principal): R {
if (principal !is LocalUser) {
throw PermissionDeniedException()
}
return internalExecute(command, principal)
}
protected abstract suspend fun internalExecute(command: T, principal: LocalUser): R
}
@@ -0,0 +1,25 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.application.shared
import org.springframework.stereotype.Service
@Service
interface Transaction {
suspend fun <T> transaction(block: suspend () -> T): T
suspend fun <T> transaction(transactionLevel: Int, block: suspend () -> T): T
}
@@ -0,0 +1,72 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.application.timeline
import dev.usbharu.hideout.core.application.model.PostDetail
import dev.usbharu.hideout.core.application.shared.AbstractApplicationService
import dev.usbharu.hideout.core.application.shared.Transaction
import dev.usbharu.hideout.core.domain.model.actor.ActorId
import dev.usbharu.hideout.core.domain.model.post.PostId
import dev.usbharu.hideout.core.domain.model.post.PostRepository
import dev.usbharu.hideout.core.domain.model.post.Visibility
import dev.usbharu.hideout.core.domain.model.support.page.Page
import dev.usbharu.hideout.core.domain.model.support.page.PaginationList
import dev.usbharu.hideout.core.domain.model.support.principal.Principal
import dev.usbharu.hideout.core.query.usertimeline.UserTimelineQueryService
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
@Service
class GetUserTimelineApplicationService(
private val userTimelineQueryService: UserTimelineQueryService,
private val postRepository: PostRepository,
transaction: Transaction
) :
AbstractApplicationService<GetUserTimeline, PaginationList<PostDetail, PostId>>(transaction, logger) {
override suspend fun internalExecute(
command: GetUserTimeline,
principal: Principal
): PaginationList<PostDetail, PostId> {
val postList = postRepository.findByActorIdAndVisibilityInList(
ActorId(command.id),
listOf(Visibility.PUBLIC, Visibility.UNLISTED, Visibility.FOLLOWERS),
command.page
)
val postIdList =
postList.mapNotNull { it.repostId } + postList.mapNotNull { it.replyId } + postList.map { it.id }
val postDetailMap = userTimelineQueryService.findByIdAll(postIdList, principal).associateBy { it.id }
return PaginationList(
postList.mapNotNull {
postDetailMap[it.id.id]?.copy(
repost = postDetailMap[it.repostId?.id],
reply = postDetailMap[it.replyId?.id]
)
},
postList.next,
postList.prev
)
}
companion object {
private val logger = LoggerFactory.getLogger(GetUserTimelineApplicationService::class.java)
}
}
data class GetUserTimeline(val id: Long, val page: Page)
@@ -0,0 +1,115 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.application.timeline
import dev.usbharu.hideout.core.application.model.PostDetail
import dev.usbharu.hideout.core.application.shared.AbstractApplicationService
import dev.usbharu.hideout.core.application.shared.Transaction
import dev.usbharu.hideout.core.domain.model.post.PostId
import dev.usbharu.hideout.core.domain.model.support.page.Page
import dev.usbharu.hideout.core.domain.model.support.page.PaginationList
import dev.usbharu.hideout.core.domain.model.support.principal.Principal
import dev.usbharu.hideout.core.domain.model.timeline.TimelineId
import dev.usbharu.hideout.core.domain.model.timeline.TimelineRepository
import dev.usbharu.hideout.core.external.timeline.ReadTimelineOption
import dev.usbharu.hideout.core.external.timeline.TimelineStore
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
@Service
class ReadTimelineApplicationService(
private val timelineStore: TimelineStore,
private val timelineRepository: TimelineRepository,
transaction: Transaction
) :
AbstractApplicationService<ReadTimeline, PaginationList<PostDetail, PostId>>(transaction, logger) {
override suspend fun internalExecute(
command: ReadTimeline,
principal: Principal
): PaginationList<PostDetail, PostId> {
val findById = timelineRepository.findById(TimelineId(command.timelineId))
?: throw IllegalArgumentException("Timeline ${command.timelineId} not found.")
val readTimelineOption = ReadTimelineOption(
command.mediaOnly,
command.localOnly,
command.remoteOnly
)
val timeline = timelineStore.readTimeline(
findById,
readTimelineOption,
command.page,
principal,
)
val postDetailList = timeline.map {
val reply = if (it.replyPost != null) {
@Suppress("UnsafeCallOnNullableType")
PostDetail.of(
post = it.replyPost,
actor = it.replyPostActor!!,
iconMedia = it.replyPostActorIconMedia,
mediaList = it.replyPostMedias.orEmpty(),
reactionsList = emptyList(),
favourited = false,
)
} else {
null
}
val repost = if (it.repostPost != null) {
@Suppress("UnsafeCallOnNullableType")
PostDetail.of(
post = it.repostPost,
actor = it.repostPostActor!!,
iconMedia = it.repostPostActorIconMedia,
mediaList = it.repostPostMedias.orEmpty(),
reactionsList = emptyList(),
favourited = false
)
} else {
null
}
PostDetail.of(
post = it.post,
actor = it.postActor,
iconMedia = it.postActorIconMedia,
mediaList = it.postMedias,
reply = reply,
repost = repost,
reactionsList = emptyList(),
favourited = it.favourited
)
}
return PaginationList(postDetailList, timeline.next, timeline.prev)
}
companion object {
private val logger = LoggerFactory.getLogger(ReadTimelineApplicationService::class.java)
}
}
data class ReadTimeline(
val timelineId: Long,
val mediaOnly: Boolean,
val localOnly: Boolean,
val remoteOnly: Boolean,
val page: Page
)
@@ -0,0 +1,49 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.application.timeline
import dev.usbharu.hideout.core.application.shared.AbstractApplicationService
import dev.usbharu.hideout.core.application.shared.Transaction
import dev.usbharu.hideout.core.domain.model.support.principal.Principal
import dev.usbharu.hideout.core.domain.model.timeline.TimelineId
import dev.usbharu.hideout.core.domain.model.timeline.TimelineRepository
import dev.usbharu.hideout.core.external.timeline.TimelineStore
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Component
@Component
class SetTimelineToTimelineStoreApplicationService(
transaction: Transaction,
private val timelineStore: TimelineStore,
private val timelineRepository: TimelineRepository
) :
AbstractApplicationService<SetTimleineStore, Unit>(
transaction,
logger
) {
override suspend fun internalExecute(command: SetTimleineStore, principal: Principal) {
val findById = timelineRepository.findById(command.timelineId)
?: throw IllegalArgumentException("Timeline ${command.timelineId} not found")
timelineStore.addTimeline(findById, emptyList())
}
companion object {
private val logger = LoggerFactory.getLogger(SetTimelineToTimelineStoreApplicationService::class.java)
}
}
data class SetTimleineStore(val timelineId: TimelineId)
@@ -0,0 +1,48 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.application.timeline
import dev.usbharu.hideout.core.application.shared.AbstractApplicationService
import dev.usbharu.hideout.core.application.shared.Transaction
import dev.usbharu.hideout.core.domain.model.post.PostId
import dev.usbharu.hideout.core.domain.model.post.PostRepository
import dev.usbharu.hideout.core.domain.model.support.principal.Principal
import dev.usbharu.hideout.core.external.timeline.TimelineStore
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Component
@Component
class TimelineAddPostApplicationService(
private val timelineStore: TimelineStore,
private val postRepository: PostRepository,
transaction: Transaction
) : AbstractApplicationService<AddPost, Unit>(
transaction,
logger
) {
override suspend fun internalExecute(command: AddPost, principal: Principal) {
val findById = postRepository.findById(command.postId)
?: throw IllegalArgumentException("Post ${command.postId} not found.")
timelineStore.addPost(findById)
}
companion object {
private val logger = LoggerFactory.getLogger(TimelineAddPostApplicationService::class.java)
}
}
data class AddPost(val postId: PostId)
@@ -0,0 +1,72 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.application.timeline
import dev.usbharu.hideout.core.application.exception.PermissionDeniedException
import dev.usbharu.hideout.core.application.shared.LocalUserAbstractApplicationService
import dev.usbharu.hideout.core.application.shared.Transaction
import dev.usbharu.hideout.core.domain.model.actor.ActorId
import dev.usbharu.hideout.core.domain.model.support.principal.LocalUser
import dev.usbharu.hideout.core.domain.model.timeline.TimelineId
import dev.usbharu.hideout.core.domain.model.timeline.TimelineRepository
import dev.usbharu.hideout.core.domain.model.timelinerelationship.TimelineRelationship
import dev.usbharu.hideout.core.domain.model.timelinerelationship.TimelineRelationshipId
import dev.usbharu.hideout.core.domain.model.timelinerelationship.TimelineRelationshipRepository
import dev.usbharu.hideout.core.domain.model.timelinerelationship.Visible
import dev.usbharu.hideout.core.domain.shared.id.IdGenerateService
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
@Service
class UserAddTimelineRelationshipApplicationService(
private val timelineRelationshipRepository: TimelineRelationshipRepository,
private val timelineRepository: TimelineRepository,
private val idGenerateService: IdGenerateService,
transaction: Transaction
) :
LocalUserAbstractApplicationService<AddTimelineRelationship, Unit>(
transaction,
logger
) {
override suspend fun internalExecute(command: AddTimelineRelationship, principal: LocalUser) {
val timeline = timelineRepository.findById(command.timelineId)
?: throw IllegalArgumentException("Timeline ${command.timelineId} not found.")
if (timeline.userDetailId != principal.userDetailId) {
throw PermissionDeniedException()
}
timelineRelationshipRepository.save(
TimelineRelationship(
TimelineRelationshipId(idGenerateService.generateId()),
command.timelineId,
command.actorId,
command.visible
)
)
}
companion object {
private val logger = LoggerFactory.getLogger(UserAddTimelineRelationshipApplicationService::class.java)
}
}
data class AddTimelineRelationship(
val timelineId: TimelineId,
val actorId: ActorId,
val visible: Visible
)
@@ -0,0 +1,55 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.application.timeline
import dev.usbharu.hideout.core.application.model.Timeline
import dev.usbharu.hideout.core.application.shared.AbstractApplicationService
import dev.usbharu.hideout.core.application.shared.Transaction
import dev.usbharu.hideout.core.domain.model.support.principal.Principal
import dev.usbharu.hideout.core.domain.model.timeline.TimelineRepository
import dev.usbharu.hideout.core.domain.model.timeline.TimelineVisibility
import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailId
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
@Service
class UserGetTimelinesApplicationService(transaction: Transaction, private val timelineRepository: TimelineRepository) :
AbstractApplicationService<GetTimelines, List<Timeline>>(
transaction,
logger
) {
override suspend fun internalExecute(command: GetTimelines, principal: Principal): List<Timeline> {
val userDetailId = UserDetailId(command.userDetailId)
val timelineVisibility = if (userDetailId == principal.userDetailId) {
listOf(TimelineVisibility.PUBLIC, TimelineVisibility.UNLISTED, TimelineVisibility.PRIVATE)
} else {
listOf(TimelineVisibility.PUBLIC)
}
val timelineList =
timelineRepository.findAllByUserDetailIdAndVisibilityIn(userDetailId, timelineVisibility)
return timelineList.map { Timeline.of(it) }
}
companion object {
private val logger = LoggerFactory.getLogger(UserGetTimelinesApplicationService::class.java)
}
}
data class GetTimelines(val userDetailId: Long)
@@ -0,0 +1,73 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.application.timeline
import dev.usbharu.hideout.core.application.domainevent.subscribers.RegisterHomeTimeline
import dev.usbharu.hideout.core.application.shared.AbstractApplicationService
import dev.usbharu.hideout.core.application.shared.Transaction
import dev.usbharu.hideout.core.domain.model.support.principal.Principal
import dev.usbharu.hideout.core.domain.model.timeline.*
import dev.usbharu.hideout.core.domain.model.timelinerelationship.TimelineRelationship
import dev.usbharu.hideout.core.domain.model.timelinerelationship.TimelineRelationshipId
import dev.usbharu.hideout.core.domain.model.timelinerelationship.TimelineRelationshipRepository
import dev.usbharu.hideout.core.domain.model.timelinerelationship.Visible
import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailId
import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailRepository
import dev.usbharu.hideout.core.domain.shared.id.IdGenerateService
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Component
@Component
class UserRegisterHomeTimelineApplicationService(
private val userDetailRepository: UserDetailRepository,
private val timelineRepository: TimelineRepository,
private val idGenerateService: IdGenerateService,
transaction: Transaction,
private val timelineRelationshipRepository: TimelineRelationshipRepository
) : AbstractApplicationService<RegisterHomeTimeline, Unit>(transaction, logger) {
override suspend fun internalExecute(command: RegisterHomeTimeline, principal: Principal) {
val userDetail = (
userDetailRepository.findById(UserDetailId(command.userDetailId))
?: throw IllegalArgumentException("UserDetail ${command.userDetailId} not found.")
)
val timeline = Timeline.create(
TimelineId(idGenerateService.generateId()),
UserDetailId(command.userDetailId),
TimelineName("System-LocalUser-HomeTimeline-${command.userDetailId}"),
TimelineVisibility.PRIVATE,
true
)
timelineRepository.save(timeline)
userDetail.homeTimelineId = timeline.id
val timelineRelationship = TimelineRelationship(
TimelineRelationshipId(idGenerateService.generateId()),
timeline.id,
userDetail.actorId,
Visible.DIRECT
)
timelineRelationshipRepository.save(timelineRelationship)
userDetailRepository.save(userDetail)
}
companion object {
private val logger = LoggerFactory.getLogger(UserRegisterHomeTimelineApplicationService::class.java)
}
}
@@ -0,0 +1,55 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.application.timeline
import dev.usbharu.hideout.core.application.shared.LocalUserAbstractApplicationService
import dev.usbharu.hideout.core.application.shared.Transaction
import dev.usbharu.hideout.core.domain.model.support.principal.LocalUser
import dev.usbharu.hideout.core.domain.model.timeline.*
import dev.usbharu.hideout.core.domain.shared.id.IdGenerateService
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Component
@Component
class UserRegisterTimelineApplicationService(
private val idGenerateService: IdGenerateService,
private val timelineRepository: TimelineRepository,
transaction: Transaction
) :
LocalUserAbstractApplicationService<RegisterTimeline, TimelineId>(transaction, logger) {
override suspend fun internalExecute(command: RegisterTimeline, principal: LocalUser): TimelineId {
val timeline = Timeline.create(
id = TimelineId(idGenerateService.generateId()),
userDetailId = principal.userDetailId,
name = TimelineName(command.timelineName),
visibility = command.visibility,
isSystem = false
)
timelineRepository.save(timeline)
return timeline.id
}
companion object {
private val logger = LoggerFactory.getLogger(UserRegisterTimelineApplicationService::class.java)
}
}
data class RegisterTimeline(
val timelineName: String,
val visibility: TimelineVisibility
)
@@ -0,0 +1,63 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.application.timeline
import dev.usbharu.hideout.core.application.exception.PermissionDeniedException
import dev.usbharu.hideout.core.application.shared.LocalUserAbstractApplicationService
import dev.usbharu.hideout.core.application.shared.Transaction
import dev.usbharu.hideout.core.domain.model.support.principal.LocalUser
import dev.usbharu.hideout.core.domain.model.timeline.TimelineRepository
import dev.usbharu.hideout.core.domain.model.timelinerelationship.TimelineRelationshipId
import dev.usbharu.hideout.core.domain.model.timelinerelationship.TimelineRelationshipRepository
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
@Service
class UserRemoveTimelineRelationshipApplicationService(
transaction: Transaction,
private val timelineRelationshipRepository: TimelineRelationshipRepository,
private val timelineRepository: TimelineRepository
) :
LocalUserAbstractApplicationService<RemoveTimelineRelationship, Unit>(
transaction,
logger
) {
override suspend fun internalExecute(command: RemoveTimelineRelationship, principal: LocalUser) {
val timelineRelationship = (
timelineRelationshipRepository.findById(command.timelineRelationshipId)
?: throw IllegalArgumentException("TimelineRelationship ${command.timelineRelationshipId} not found.")
)
val timeline = (
timelineRepository.findById(timelineRelationship.timelineId)
?: throw IllegalArgumentException("Timeline ${timelineRelationship.timelineId} not found.")
)
if (timeline.userDetailId != principal.userDetailId) {
throw PermissionDeniedException()
}
timelineRelationshipRepository.delete(timelineRelationship)
}
companion object {
private val logger = LoggerFactory.getLogger(UserRemoveTimelineRelationshipApplicationService::class.java)
}
}
data class RemoveTimelineRelationship(val timelineRelationshipId: TimelineRelationshipId)
@@ -0,0 +1,27 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.config
import org.springframework.boot.context.properties.ConfigurationProperties
import java.net.URI
@ConfigurationProperties("hideout")
data class ApplicationConfig(
val url: URI,
val private: Boolean = true,
val keySize: Int = 2048,
)
@@ -0,0 +1,24 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.config
import org.springframework.boot.context.properties.ConfigurationProperties
@ConfigurationProperties("hideout.timeline.default")
data class DefaultTimelineStoreConfig(
val actorPostsCount: Int = 500
)
@@ -0,0 +1,33 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.config
import org.bytedeco.ffmpeg.global.avcodec
import org.springframework.boot.context.properties.ConfigurationProperties
@ConfigurationProperties("hideout.media.video.ffmpeg")
data class FFmpegVideoConfig(
val frameRate: Int = 60,
val maxWidth: Int = 1920,
val maxHeight: Int = 1080,
val format: String = "mp4",
val videoCodec: Int = avcodec.AV_CODEC_ID_H264,
val audioCodec: Int = avcodec.AV_CODEC_ID_AAC,
val videoQuality: Double = 1.0,
val videoOption: List<String> = listOf("preset", "ultrafast"),
val maxBitrate: Int = 1300000,
)
@@ -0,0 +1,32 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.config
import org.springframework.boot.autoconfigure.flyway.FlywayMigrationStrategy
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
@Configuration
class FlywayConfig {
@Bean
fun cleanMigrateStrategy(): FlywayMigrationStrategy {
return FlywayMigrationStrategy { migrate ->
migrate.repair()
migrate.migrate()
}
}
}
@@ -0,0 +1,37 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.config
import org.owasp.html.HtmlPolicyBuilder
import org.owasp.html.PolicyFactory
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
@Configuration
class HtmlSanitizeConfig {
@Bean
fun policy(): PolicyFactory {
return HtmlPolicyBuilder()
.allowElements("p")
.allowElements("a")
.allowElements("br")
.allowAttributes("href").onElements("a")
.allowUrlProtocols("http", "https")
.allowElements({ _, _ -> return@allowElements "p" }, "h1", "h2", "h3", "h4", "h5", "h6")
.toFactory()
}
}
@@ -0,0 +1,26 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.config
import org.springframework.boot.context.properties.ConfigurationProperties
@ConfigurationProperties("hideout.media.image.imageio")
data class ImageIOImageConfig(
val thumbnailsWidth: Int = 1000,
val thumbnailsHeight: Int = 1000,
val format: String = "jpeg"
)
@@ -0,0 +1,33 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.config
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
import org.springframework.boot.context.properties.ConfigurationProperties
/**
* メディアの保存にローカルファイルシステムを使用する際のコンフィグ
*
* @property path フォゾンする場所へのパス。 /から始めると絶対パスとなります。
* @property publicUrl 公開用URL 省略可能 指定するとHideoutがファイルを配信しなくなります。
*/
@ConfigurationProperties("hideout.storage.local")
@ConditionalOnProperty("hideout.storage.type", havingValue = "local", matchIfMissing = true)
data class LocalStorageConfig(
val path: String = "files",
val publicUrl: String?
)
@@ -0,0 +1,42 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.config
import org.springframework.boot.autoconfigure.context.MessageSourceProperties
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.context.MessageSource
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Profile
import org.springframework.context.support.ReloadableResourceBundleMessageSource
@Configuration
@Profile("dev")
class MessageSourceConfig {
@Bean
fun messageSource(messageSourceProperties: MessageSourceProperties): MessageSource {
val reloadableResourceBundleMessageSource = ReloadableResourceBundleMessageSource()
reloadableResourceBundleMessageSource.setBasename("classpath:" + messageSourceProperties.basename)
reloadableResourceBundleMessageSource.setCacheSeconds(0)
return reloadableResourceBundleMessageSource
}
@Bean
@Profile("dev")
@ConfigurationProperties(prefix = "spring.messages")
fun messageSourceProperties(): MessageSourceProperties = MessageSourceProperties()
}
@@ -0,0 +1,50 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.config
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials
import software.amazon.awssdk.regions.Region
import software.amazon.awssdk.services.s3.S3Client
import java.net.URI
@ConfigurationProperties("hideout.storage.s3")
@ConditionalOnProperty("hideout.storage.type", havingValue = "s3")
data class S3StorageConfig(
val endpoint: String,
val publicUrl: String,
val bucket: String,
val region: String,
val accessKey: String,
val secretKey: String
)
@Configuration
class AwsConfig {
@Bean
@ConditionalOnProperty("hideout.storage.type", havingValue = "s3")
fun s3Client(awsConfig: S3StorageConfig): S3Client {
return S3Client.builder()
.endpointOverride(URI.create(awsConfig.endpoint))
.region(Region.of(awsConfig.region))
.credentialsProvider { AwsBasicCredentials.create(awsConfig.accessKey, awsConfig.secretKey) }
.build()
}
}
@@ -0,0 +1,254 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.config
import com.nimbusds.jose.jwk.JWKSet
import com.nimbusds.jose.jwk.RSAKey
import com.nimbusds.jose.jwk.source.ImmutableJWKSet
import com.nimbusds.jose.jwk.source.JWKSource
import com.nimbusds.jose.proc.SecurityContext
import dev.usbharu.hideout.core.infrastructure.springframework.oauth2.HideoutUserDetails
import dev.usbharu.hideout.util.RsaUtil
import org.slf4j.LoggerFactory
import org.springframework.boot.context.properties.ConfigurationProperties
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.jdbc.core.JdbcOperations
import org.springframework.security.access.hierarchicalroles.RoleHierarchy
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl
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.invoke
import org.springframework.security.core.Authentication
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.security.oauth2.core.AuthorizationGrantType
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationConsentService
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings
import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer
import org.springframework.security.web.SecurityFilterChain
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint
import java.security.KeyPairGenerator
import java.security.interfaces.RSAPrivateKey
import java.security.interfaces.RSAPublicKey
import java.util.*
@Configuration
@EnableWebSecurity(debug = false)
class SecurityConfig {
@Bean
fun passwordEncoder(): PasswordEncoder = BCryptPasswordEncoder()
@Bean
@Order(1)
fun oauth2Provider(http: HttpSecurity): SecurityFilterChain {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http)
http {
exceptionHandling {
authenticationEntryPoint = LoginUrlAuthenticationEntryPoint("/auth/sign_in")
}
}
return http.build()
}
@Bean
@Order(3)
fun httpSecurityFilterChain(http: HttpSecurity): SecurityFilterChain {
http {
authorizeHttpRequests {
authorize("/error", permitAll)
authorize("/auth/sign_in", permitAll)
authorize(GET, "/.well-known/**", permitAll)
authorize(GET, "/nodeinfo/2.0", permitAll)
authorize(GET, "/auth/sign_up", hasRole("ANONYMOUS"))
authorize(POST, "/auth/sign_up", permitAll)
authorize(GET, "/users/{username}/posts/{postId}", permitAll)
authorize(GET, "/users/{userid}", permitAll)
authorize(GET, "/files/*", permitAll)
authorize(POST, "/publish", authenticated)
authorize(GET, "/publish", authenticated)
authorize(GET, "/", permitAll)
authorize(anyRequest, authenticated)
}
formLogin {
loginPage = "/auth/sign_in"
loginProcessingUrl = "/login"
defaultSuccessUrl("/home", false)
}
logout {
logoutUrl = "/auth/sign_out"
logoutSuccessUrl = "/auth/sign_in"
}
}
return http.build()
}
@Bean
fun registeredClientRepository(jdbcOperations: JdbcOperations): RegisteredClientRepository =
JdbcRegisteredClientRepository(jdbcOperations)
@Bean
@Suppress("FunctionMaxLength")
fun oauth2AuthorizationConsentService(
jdbcOperations: JdbcOperations,
registeredClientRepository: RegisteredClientRepository,
): OAuth2AuthorizationConsentService =
JdbcOAuth2AuthorizationConsentService(jdbcOperations, registeredClientRepository)
@Bean
fun authorizationServerSettings(): AuthorizationServerSettings {
return AuthorizationServerSettings.builder().authorizationEndpoint("/oauth/authorize")
.tokenEndpoint("/oauth/token").tokenRevocationEndpoint("/oauth/revoke").build()
}
@Bean
fun jwtTokenCustomizer(): OAuth2TokenCustomizer<JwtEncodingContext> {
return OAuth2TokenCustomizer { context: JwtEncodingContext ->
if (OAuth2TokenType.ACCESS_TOKEN == context.tokenType &&
context.authorization?.authorizationGrantType == AuthorizationGrantType.AUTHORIZATION_CODE
) {
val userDetailsImpl = context.getPrincipal<Authentication>().principal as HideoutUserDetails
context.claims.claim("uid", userDetailsImpl.userDetailsId.toString())
}
}
}
@Bean
@Suppress("UnsafeCallOnNullableType")
fun loadJwkSource(jwkConfig: JwkConfig, applicationConfig: ApplicationConfig): JWKSource<SecurityContext> {
if (jwkConfig.keyId == null) {
logger.error("hideout.security.jwt.keyId is null.")
}
if (jwkConfig.publicKey == null) {
logger.error("hideout.security.jwt.publicKey is null.")
}
if (jwkConfig.privateKey == null) {
logger.error("hideout.security.jwt.privateKey is null.")
}
if (jwkConfig.keyId == null || jwkConfig.publicKey == null || jwkConfig.privateKey == null) {
val keyPairGenerator = KeyPairGenerator.getInstance("RSA")
keyPairGenerator.initialize(applicationConfig.keySize)
val generateKeyPair = keyPairGenerator.generateKeyPair()
jwkConfig.keyId = UUID.randomUUID().toString()
jwkConfig.publicKey = RsaUtil.encodeRsaPublicKey(generateKeyPair.public as RSAPublicKey)
jwkConfig.privateKey = RsaUtil.encodeRsaPrivateKey(generateKeyPair.private as RSAPrivateKey)
logger.error(
"""
|==============
|==============
|
|**Write the following settings in application.yml**
|
|hideout:
| security:
| jwt:
| keyId: ${jwkConfig.keyId}
| publicKey: ${jwkConfig.publicKey}
| privateKey: ${jwkConfig.privateKey}
|
|==============
|==============
""".trimMargin()
)
}
val rsaKey = RSAKey.Builder(RsaUtil.decodeRsaPublicKey(jwkConfig.publicKey!!))
.privateKey(RsaUtil.decodeRsaPrivateKey(jwkConfig.privateKey!!)).keyID(jwkConfig.keyId).build()
return ImmutableJWKSet(JWKSet(rsaKey))
}
@ConfigurationProperties("hideout.security.jwt")
data class JwkConfig(
var keyId: String?,
var publicKey: String?,
var privateKey: String?,
)
@Bean
fun roleHierarchy(): RoleHierarchy {
val roleHierarchyImpl = RoleHierarchyImpl.fromHierarchy(
"""
SCOPE_read > SCOPE_read:accounts
SCOPE_read > SCOPE_read:accounts
SCOPE_read > SCOPE_read:blocks
SCOPE_read > SCOPE_read:bookmarks
SCOPE_read > SCOPE_read:favourites
SCOPE_read > SCOPE_read:filters
SCOPE_read > SCOPE_read:follows
SCOPE_read > SCOPE_read:lists
SCOPE_read > SCOPE_read:mutes
SCOPE_read > SCOPE_read:notifications
SCOPE_read > SCOPE_read:search
SCOPE_read > SCOPE_read:statuses
SCOPE_write > SCOPE_write:accounts
SCOPE_write > SCOPE_write:blocks
SCOPE_write > SCOPE_write:bookmarks
SCOPE_write > SCOPE_write:conversations
SCOPE_write > SCOPE_write:favourites
SCOPE_write > SCOPE_write:filters
SCOPE_write > SCOPE_write:follows
SCOPE_write > SCOPE_write:lists
SCOPE_write > SCOPE_write:media
SCOPE_write > SCOPE_write:mutes
SCOPE_write > SCOPE_write:notifications
SCOPE_write > SCOPE_write:reports
SCOPE_write > SCOPE_write:statuses
SCOPE_follow > SCOPE_write:blocks
SCOPE_follow > SCOPE_write:follows
SCOPE_follow > SCOPE_write:mutes
SCOPE_follow > SCOPE_read:blocks
SCOPE_follow > SCOPE_read:follows
SCOPE_follow > SCOPE_read:mutes
SCOPE_admin > SCOPE_admin:read
SCOPE_admin > SCOPE_admin:write
SCOPE_admin:read > SCOPE_admin:read:accounts
SCOPE_admin:read > SCOPE_admin:read:reports
SCOPE_admin:read > SCOPE_admin:read:domain_allows
SCOPE_admin:read > SCOPE_admin:read:domain_blocks
SCOPE_admin:read > SCOPE_admin:read:ip_blocks
SCOPE_admin:read > SCOPE_admin:read:email_domain_blocks
SCOPE_admin:read > SCOPE_admin:read:canonical_email_blocks
SCOPE_admin:write > SCOPE_admin:write:accounts
SCOPE_admin:write > SCOPE_admin:write:reports
SCOPE_admin:write > SCOPE_admin:write:domain_allows
SCOPE_admin:write > SCOPE_admin:write:domain_blocks
SCOPE_admin:write > SCOPE_admin:write:ip_blocks
SCOPE_admin:write > SCOPE_admin:write:email_domain_blocks
SCOPE_admin:write > SCOPE_admin:write:canonical_email_blocks
""".trimIndent()
)
return roleHierarchyImpl
}
companion object {
private val logger = LoggerFactory.getLogger(SecurityConfig::class.java)
}
}
@@ -0,0 +1,69 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.config
import ch.qos.logback.classic.helpers.MDCInsertingServletFilter
import dev.usbharu.hideout.core.infrastructure.springframework.ApplicationRequestLogInterceptor
import dev.usbharu.hideout.core.infrastructure.springframework.SPAInterceptor
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.http.converter.HttpMessageConverter
import org.springframework.web.method.support.HandlerMethodArgumentResolver
import org.springframework.web.servlet.config.annotation.InterceptorRegistry
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer
import org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor
import org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor
@Configuration
class MvcConfigurer(
private val jsonOrFormModelMethodProcessor: JsonOrFormModelMethodProcessor,
private val spaInterceptor: SPAInterceptor,
private val applicationRequestLogInterceptor: ApplicationRequestLogInterceptor
) : WebMvcConfigurer {
override fun addArgumentResolvers(resolvers: MutableList<HandlerMethodArgumentResolver>) {
resolvers.add(jsonOrFormModelMethodProcessor)
}
override fun addInterceptors(registry: InterceptorRegistry) {
registry.addInterceptor(spaInterceptor)
registry.addInterceptor(applicationRequestLogInterceptor)
}
@Bean
fun mdcFilter(): FilterRegistrationBean<MDCInsertingServletFilter> {
val bean = FilterRegistrationBean<MDCInsertingServletFilter>()
bean.filter = MDCInsertingServletFilter()
bean.addUrlPatterns("/*")
bean.order = Int.MIN_VALUE
return bean
}
}
@Configuration
class JsonOrFormModelMethodProcessorConfig {
@Bean
fun jsonOrFormModelMethodProcessor(converter: List<HttpMessageConverter<*>>): JsonOrFormModelMethodProcessor {
return JsonOrFormModelMethodProcessor(
ServletModelAttributeMethodProcessor(true),
RequestResponseBodyMethodProcessor(
converter
)
)
}
}
@@ -0,0 +1,47 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.domain.event.actor
import dev.usbharu.hideout.core.domain.model.actor.Actor
import dev.usbharu.hideout.core.domain.model.actor.ActorId
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEvent
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventBody
class ActorDomainEventFactory(private val actor: Actor) {
fun createEvent(actorEvent: ActorEvent): DomainEvent<ActorEventBody> {
return DomainEvent.create(
actorEvent.eventName,
ActorEventBody(actor.id),
actorEvent.collectable
)
}
}
class ActorEventBody(actor: ActorId) : DomainEventBody(
mapOf(
"actor" to actor
),
)
enum class ActorEvent(val eventName: String, val collectable: Boolean = true) {
UPDATE("ActorUpdate"),
DELETE("ActorDelete"),
CHECK_UPDATE("ActorCheckUpdate"),
MOVE("ActorMove"),
ACTOR_SUSPEND("ActorSuspend"),
ACTOR_UNSUSPEND("ActorUnsuspend"),
}
@@ -0,0 +1,49 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.domain.event.actorinstancerelationship
import dev.usbharu.hideout.core.domain.model.actorinstancerelationship.ActorInstanceRelationship
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEvent
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventBody
class ActorInstanceRelationshipDomainEventFactory(private val actorInstanceRelationship: ActorInstanceRelationship) {
fun createEvent(
actorInstanceRelationshipEvent: ActorInstanceRelationshipEvent
): DomainEvent<ActorInstanceRelationshipEventBody> {
return DomainEvent.create(
actorInstanceRelationshipEvent.eventName,
ActorInstanceRelationshipEventBody(actorInstanceRelationship)
)
}
}
class ActorInstanceRelationshipEventBody(actorInstanceRelationship: ActorInstanceRelationship) :
DomainEventBody(
mapOf(
"actorId" to actorInstanceRelationship.actorId,
"instanceId" to actorInstanceRelationship.instanceId,
"muting" to actorInstanceRelationship.muting,
"blocking" to actorInstanceRelationship.blocking,
"doNotSendPrivate" to actorInstanceRelationship.doNotSendPrivate,
),
)
enum class ActorInstanceRelationshipEvent(val eventName: String) {
BLOCK("ActorInstanceBlock"),
MUTE("ActorInstanceMute"),
UNMUTE("ActorInstanceUnmute"),
}
@@ -0,0 +1,36 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.domain.event.instance
import dev.usbharu.hideout.core.domain.model.instance.Instance
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEvent
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventBody
class InstanceEventFactory(private val instance: Instance) {
fun createEvent(event: InstanceEvent): DomainEvent<InstanceEventBody> {
return DomainEvent.create(
event.eventName,
InstanceEventBody(instance)
)
}
}
class InstanceEventBody(instance: Instance) : DomainEventBody(mapOf("instance" to instance))
enum class InstanceEvent(val eventName: String) {
UPDATE("InstanceUpdate")
}
@@ -0,0 +1,45 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.domain.event.post
import dev.usbharu.hideout.core.domain.model.actor.Actor
import dev.usbharu.hideout.core.domain.model.actor.ActorId
import dev.usbharu.hideout.core.domain.model.post.Post
import dev.usbharu.hideout.core.domain.model.post.PostId
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEvent
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventBody
class PostDomainEventFactory(private val post: Post, private val actor: Actor? = null) {
fun createEvent(postEvent: PostEvent): DomainEvent<PostEventBody> {
return DomainEvent.create(
postEvent.eventName,
PostEventBody(post.id, actor?.id)
)
}
}
class PostEventBody(post: PostId, actor: ActorId?) : DomainEventBody(mapOf("post" to post, "actor" to actor)) {
fun getPostId(): PostId = toMap()["post"] as PostId
fun getActorId(): ActorId? = toMap()["actor"] as? ActorId
}
enum class PostEvent(val eventName: String) {
DELETE("PostDelete"),
UPDATE("PostUpdate"),
CREATE("PostCreate"),
CHECK_UPDATE("PostCheckUpdate"),
}
@@ -0,0 +1,38 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.domain.event.reaction
import dev.usbharu.hideout.core.domain.model.reaction.Reaction
import dev.usbharu.hideout.core.domain.model.reaction.ReactionId
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEvent
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventBody
class ReactionEventFactory(private val reaction: Reaction) {
fun createEvent(reactionEvent: ReactionEvent): DomainEvent<ReactionEventBody> =
DomainEvent.create(reactionEvent.eventName, ReactionEventBody(reaction))
}
class ReactionEventBody(
reaction: Reaction
) : DomainEventBody(mapOf("reactionId" to reaction.id)) {
fun getReactionId(): ReactionId = toMap()["reactionId"] as ReactionId
}
enum class ReactionEvent(val eventName: String) {
CREATE("ReactionCreate"),
DELETE("ReactionDelete"),
}
@@ -0,0 +1,47 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.domain.event.relationship
import dev.usbharu.hideout.core.domain.model.relationship.Relationship
import dev.usbharu.hideout.core.domain.model.support.principal.Anonymous
import dev.usbharu.hideout.core.domain.model.support.principal.Principal
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEvent
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventBody
class RelationshipEventFactory(private val relationship: Relationship, private val principal: Principal = Anonymous) {
fun createEvent(relationshipEvent: RelationshipEvent): DomainEvent<RelationshipEventBody> =
DomainEvent.create(relationshipEvent.eventName, RelationshipEventBody(relationship, principal))
}
class RelationshipEventBody(
relationship: Relationship,
override val principal: Principal
) : DomainEventBody(mapOf("relationship" to relationship), principal) {
fun getRelationship(): Relationship = toMap()["relationship"] as Relationship
}
enum class RelationshipEvent(val eventName: String) {
ACCEPT_FOLLOW("RelationshipFollow"),
REJECT_FOLLOW("RelationshipRejectFollow"),
UNFOLLOW("RelationshipUnfollow"),
BLOCK("RelationshipBlock"),
UNBLOCK("RelationshipUnblock"),
MUTE("RelationshipMute"),
UNMUTE("RelationshipUnmute"),
FOLLOW_REQUEST("RelationshipFollowRequest"),
UNFOLLOW_REQUEST("RelationshipUnfollowRequest"),
}
@@ -0,0 +1,36 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.domain.event.timeline
import dev.usbharu.hideout.core.domain.model.timeline.Timeline
import dev.usbharu.hideout.core.domain.model.timeline.TimelineId
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEvent
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventBody
class TimelineEventFactory(private val timeline: Timeline) {
fun createEvent(timelineEvent: TimelineEvent): DomainEvent<TimelineEventBody> =
DomainEvent.create(timelineEvent.eventName, TimelineEventBody(timeline.id))
}
class TimelineEventBody(timelineId: TimelineId) : DomainEventBody(mapOf("timeline" to timelineId)) {
fun getTimelineId(): TimelineId = toMap()["timeline"] as TimelineId
}
enum class TimelineEvent(val eventName: String) {
CHANGE_VISIBILITY("ChangeVisibility"),
CREATE("TimelineCreate")
}
@@ -0,0 +1,43 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.domain.event.userdetail
import dev.usbharu.hideout.core.domain.model.userdetails.UserDetail
import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailId
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEvent
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventBody
class UserDetailDomainEventFactory(private val userDetail: UserDetail) {
fun createEvent(userDetailEvent: UserDetailEvent): DomainEvent<UserDetailEventBody> {
return DomainEvent.create(
userDetailEvent.eventName,
UserDetailEventBody(userDetail.id)
)
}
}
class UserDetailEventBody(userDetail: UserDetailId) : DomainEventBody(
mapOf(
"userDetail" to userDetail
)
) {
fun getUserDetail(): UserDetailId = toMap()["userDetail"] as UserDetailId
}
enum class UserDetailEvent(val eventName: String) {
CREATE("UserDetailCreate"),
}
@@ -0,0 +1,37 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.domain.exception
import java.io.Serial
open class HideoutException : RuntimeException {
constructor() : super()
constructor(message: String?) : super(message)
constructor(message: String?, cause: Throwable?) : super(message, cause)
constructor(cause: Throwable?) : super(cause)
constructor(message: String?, cause: Throwable?, enableSuppression: Boolean, writableStackTrace: Boolean) : super(
message,
cause,
enableSuppression,
writableStackTrace
)
companion object {
@Serial
private const val serialVersionUID: Long = 8506638570017469956L
}
}
@@ -0,0 +1,23 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.domain.exception
import dev.usbharu.hideout.core.domain.exception.resource.ResourceAccessException
interface SQLExceptionTranslator {
fun translate(message: String, sql: String? = null, exception: Exception): ResourceAccessException
}
@@ -0,0 +1,35 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.domain.exception
import dev.usbharu.hideout.core.domain.exception.resource.DuplicateException
import dev.usbharu.hideout.core.domain.exception.resource.ResourceAccessException
import org.springframework.dao.DataAccessException
import org.springframework.dao.DuplicateKeyException
class SpringDataAccessExceptionSQLExceptionTranslator : SQLExceptionTranslator {
override fun translate(message: String, sql: String?, exception: Exception): ResourceAccessException {
if (exception !is DataAccessException) {
throw IllegalArgumentException("exception must be DataAccessException.")
}
return when (exception) {
is DuplicateKeyException -> DuplicateException(message, exception.rootCause)
else -> ResourceAccessException(message, exception.rootCause)
}
}
}
@@ -0,0 +1,37 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.domain.exception.resource
import java.io.Serial
class DuplicateException : ResourceAccessException {
constructor() : super()
constructor(message: String?) : super(message)
constructor(message: String?, cause: Throwable?) : super(message, cause)
constructor(cause: Throwable?) : super(cause)
constructor(message: String?, cause: Throwable?, enableSuppression: Boolean, writableStackTrace: Boolean) : super(
message,
cause,
enableSuppression,
writableStackTrace
)
companion object {
@Serial
private const val serialVersionUID: Long = 7092046653037974417L
}
}
@@ -0,0 +1,30 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.domain.exception.resource
open class NotFoundException : ResourceAccessException {
constructor() : super()
constructor(message: String?) : super(message)
constructor(message: String?, cause: Throwable?) : super(message, cause)
constructor(cause: Throwable?) : super(cause)
constructor(message: String?, cause: Throwable?, enableSuppression: Boolean, writableStackTrace: Boolean) : super(
message,
cause,
enableSuppression,
writableStackTrace
)
}
@@ -0,0 +1,32 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.domain.exception.resource
import dev.usbharu.hideout.core.domain.exception.HideoutException
open class ResourceAccessException : HideoutException {
constructor() : super()
constructor(message: String?) : super(message)
constructor(message: String?, cause: Throwable?) : super(message, cause)
constructor(cause: Throwable?) : super(cause)
constructor(message: String?, cause: Throwable?, enableSuppression: Boolean, writableStackTrace: Boolean) : super(
message,
cause,
enableSuppression,
writableStackTrace
)
}
@@ -0,0 +1,191 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.domain.model.actor
import dev.usbharu.hideout.core.domain.event.actor.ActorDomainEventFactory
import dev.usbharu.hideout.core.domain.event.actor.ActorEvent.*
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.domain.shared.domainevent.DomainEventStorable
import java.net.URI
import java.time.Instant
@Suppress("LongParameterList", "ClassOrdering")
class Actor(
val id: ActorId,
val name: ActorName,
val domain: Domain,
screenName: ActorScreenName,
description: ActorDescription,
val inbox: URI,
val outbox: URI,
val url: URI,
val publicKey: ActorPublicKey,
val privateKey: ActorPrivateKey? = null,
val createdAt: Instant,
val keyId: ActorKeyId,
val followersEndpoint: URI?,
val followingEndpoint: URI?,
val instance: InstanceId,
var locked: Boolean,
var followersCount: ActorRelationshipCount?,
var followingCount: ActorRelationshipCount?,
var postsCount: ActorPostsCount,
var lastPostAt: Instant? = null,
suspend: Boolean,
var lastUpdateAt: Instant = createdAt,
alsoKnownAs: Set<ActorId> = emptySet(),
moveTo: ActorId? = null,
emojiIds: Set<CustomEmojiId>,
deleted: Boolean,
icon: MediaId?,
banner: MediaId?,
) : DomainEventStorable() {
var banner = banner
private set
fun setBannerUrl(banner: MediaId?) {
addDomainEvent(ActorDomainEventFactory(this).createEvent(UPDATE))
this.banner = banner
}
var icon = icon
private set
fun setIconUrl(icon: MediaId?) {
addDomainEvent(ActorDomainEventFactory(this).createEvent(UPDATE))
this.icon = icon
}
var suspend = suspend
set(value) {
if (field != value && value) {
addDomainEvent(ActorDomainEventFactory(this).createEvent(ACTOR_SUSPEND))
} else if (field != value && !value) {
addDomainEvent(ActorDomainEventFactory(this).createEvent(ACTOR_UNSUSPEND))
}
field = value
}
var alsoKnownAs = alsoKnownAs
private set
fun setAlsoKnownAs(alsoKnownAs: Set<ActorId>) {
require(alsoKnownAs.none { it == id })
this.alsoKnownAs = alsoKnownAs
}
var moveTo = moveTo
private set
fun setMoveTo(moveTo: ActorId?) {
require(moveTo != id)
addDomainEvent(ActorDomainEventFactory(this).createEvent(MOVE))
this.moveTo = moveTo
}
var emojis = emojiIds
private set
var description = description
private set
fun setDescription(description: ActorDescription) {
addDomainEvent(ActorDomainEventFactory(this).createEvent(UPDATE))
this.description = description
}
var screenName = screenName
private set
fun setScreenName(screenName: ActorScreenName) {
addDomainEvent(ActorDomainEventFactory(this).createEvent(UPDATE))
this.screenName = screenName
}
var deleted = deleted
private set
fun delete() {
if (deleted.not()) {
addDomainEvent(ActorDomainEventFactory(this).createEvent(DELETE))
screenName = ActorScreenName.empty
description = ActorDescription.empty
emojis = emptySet()
lastPostAt = null
postsCount = ActorPostsCount.ZERO
followersCount = null
followingCount = null
}
}
fun restore() {
deleted = false
checkUpdate()
}
fun checkUpdate() {
addDomainEvent(ActorDomainEventFactory(this).createEvent(CHECK_UPDATE))
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Actor
return id == other.id
}
override fun hashCode(): Int = id.hashCode()
override fun toString(): String {
return "Actor(" +
"id=$id, " +
"name=$name, " +
"domain=$domain, " +
"inbox=$inbox, " +
"outbox=$outbox, " +
"url=$url, " +
"publicKey=$publicKey, " +
"privateKey=$privateKey, " +
"createdAt=$createdAt, " +
"keyId=$keyId, " +
"followersEndpoint=$followersEndpoint, " +
"followingEndpoint=$followingEndpoint, " +
"instance=$instance, " +
"locked=$locked, " +
"followersCount=$followersCount, " +
"followingCount=$followingCount, " +
"postsCount=$postsCount, " +
"lastPostAt=$lastPostAt, " +
"lastUpdateAt=$lastUpdateAt, " +
"banner=$banner, " +
"icon=$icon, " +
"suspend=$suspend, " +
"alsoKnownAs=$alsoKnownAs, " +
"moveTo=$moveTo, " +
"emojis=$emojis, " +
"description=$description, " +
"screenName=$screenName, " +
"deleted=$deleted" +
")"
}
}
@@ -0,0 +1,37 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.domain.model.actor
class ActorDescription(description: String) {
val description: String = description.take(LENGTH)
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as ActorDescription
return description == other.description
}
override fun hashCode(): Int = description.hashCode()
companion object {
const val LENGTH = 10000
val empty = ActorDescription("")
}
}
@@ -0,0 +1,28 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.domain.model.actor
@JvmInline
value class ActorId(val id: Long) {
init {
require(0 <= id)
}
companion object {
val ghost = ActorId(0L)
}
}

Some files were not shown because too many files have changed in this diff Show More